/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2002-2021 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
. */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#ifdef HAVE_SHADOW_H
# include
#endif
#include
#include
#include
#include
#ifdef HAVE_STRINGS_H
# include
#else
# include
#endif
#ifdef HAVE_CRYPT_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "sql.h"
#ifdef USE_SQL
struct mu_sql_module_config mu_sql_module_config;
/* Resource file configuration */
static struct mu_kwd password_encryption[] = {
{ "plain", mu_sql_password_plaintext },
{ "scrambled", mu_sql_password_scrambled },
{ "hash", mu_sql_password_hash },
{ "crypt", mu_sql_password_hash },
{ NULL }
};
static int
cb_password_encryption (void *data, mu_config_value_t *val)
{
int res;
if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
return 1;
if (mu_kwd_xlat_name (password_encryption, val->v.string, &res))
mu_error ("%s", _("unrecognized password encryption"));
else
mu_sql_module_config.password_encryption = res;
return 0;
}
static int
cb_field_map (void *data, mu_config_value_t *val)
{
char *err_term;
int rc = mu_cfg_field_map (val, &mu_sql_module_config.field_map, &err_term);
if (rc)
{
if (err_term)
mu_error (_("error near %s: %s"), err_term, mu_strerror (rc));
else
mu_error ("%s", mu_strerror (rc));
}
return rc;
}
static int
cb_interface (void *data, mu_config_value_t *val)
{
if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
return 1;
mu_sql_module_config.interface = mu_sql_interface_index (val->v.string);
if (mu_sql_module_config.interface == 0)
{
mu_error (_("unknown SQL interface `%s'"), val->v.string);
return 1;
}
return 0;
}
static struct mu_cfg_param mu_sql_param[] = {
{ "interface", mu_cfg_callback, &mu_sql_module_config.interface, 0,
cb_interface,
N_("Set SQL interface to use."),
/* TRANSLATORS: Words to the right of : are keywords - do not translate */
N_("iface: mysql|odbc|postgres") },
{ "getpwnam", mu_c_string, &mu_sql_module_config.getpwnam_query, 0, NULL,
N_("SQL query to use for getpwnam requests."),
N_("query") },
{ "getpwuid", mu_c_string, &mu_sql_module_config.getpwuid_query, 0, NULL,
N_("SQL query to use for getpwuid requests."),
N_("query") },
{ "getpass", mu_c_string, &mu_sql_module_config.getpass_query, 0, NULL,
N_("SQL query returning the user's password."),
N_("query") },
{ "host", mu_c_string, &mu_sql_module_config.host, 0, NULL,
N_("SQL server host name.") },
{ "user", mu_c_string, &mu_sql_module_config.user, 0, NULL,
N_("SQL user name.") },
{ "passwd", mu_c_string, &mu_sql_module_config.passwd, 0, NULL,
N_("Password for the SQL user.") },
{ "port", mu_c_int, &mu_sql_module_config.port, 0, NULL,
N_("SQL server port.") },
{ "db", mu_c_string, &mu_sql_module_config.db, 0, NULL,
N_("Database name.") },
{ "password-encryption", mu_cfg_callback, NULL, 0, cb_password_encryption,
N_("Type of password returned by getpass query."),
/* TRANSLATORS: Words to the right of : are keywords - do not translate */
N_("arg: plain|hash|crypt|scrambled") },
{ "field-map", mu_cfg_callback, NULL, 0, cb_field_map,
N_("Set a field-map for parsing SQL replies. The map is a "
"column-separated list of definitions. Each definition has the "
"following form:\n"
" =\n"
"where is one of the following: name, passwd, uid, gid, "
"gecos, dir, shell, mailbox, quota, and is the name of "
"the corresponding SQL column."),
N_("map: definition") },
{ "param", mu_c_string, &mu_sql_module_config.param, 0, NULL,
N_("Extra parameters for connection (backend-specific)"),
N_("arg") },
{ NULL }
};
static char *
sql_escape_string (const char *ustr)
{
char *str, *q;
const unsigned char *p;
size_t len = strlen (ustr);
#define ESCAPABLE_CHAR "\\'\""
for (p = (const unsigned char *) ustr; *p; p++)
{
if (strchr (ESCAPABLE_CHAR, *p))
len++;
}
str = malloc (len + 1);
if (!str)
return NULL;
for (p = (const unsigned char *) ustr, q = str; *p; p++)
{
if (strchr (ESCAPABLE_CHAR, *p))
*q++ = '\\';
*q++ = *p;
}
*q = 0;
return str;
}
char *
mu_sql_expand_query (const char *query, const char *ustr)
{
int rc;
char *res;
char *esc_ustr;
if (!query)
return NULL;
esc_ustr = sql_escape_string (ustr);
rc = mu_str_vexpand (&res, query, "user", esc_ustr, NULL);
free (esc_ustr);
if (rc)
{
if (rc == MU_ERR_FAILURE)
{
mu_error (_("cannot expand line `%s': %s"), query, res);
free (res);
}
else
mu_error (_("cannot expand line `%s': %s"), query, mu_strerror (rc));
return NULL;
}
return res;
}
static int
get_field (mu_sql_connection_t conn, const char *id, char **ret, int mandatory)
{
int rc;
const char *name = mu_assoc_get (mu_sql_module_config.field_map, id);
if (!name)
name = id;
rc = mu_sql_get_field (conn, 0, name, ret);
if (rc)
{
if (mandatory || rc != MU_ERR_NOENT)
mu_error (_("cannot get SQL field `%s' (`%s'): %s"),
id, name, mu_strerror (rc));
}
else if (!*ret)
{
if (mandatory)
{
mu_error (_("SQL field `%s' (`%s') has NULL value"),
id, name);
rc = MU_ERR_READ;
}
else
rc = MU_ERR_NOENT;
}
return rc;
}
static int
decode_tuple (mu_sql_connection_t conn, int n,
struct mu_auth_data **return_data)
{
int rc;
char *mailbox_name = NULL;
char *name;
char *passwd, *suid, *sgid, *dir, *shell, *gecos, *squota;
mu_off_t quota = 0;
char *p;
uid_t uid;
gid_t gid;
if (get_field (conn, MU_AUTH_NAME, &name, 1)
|| get_field (conn, MU_AUTH_PASSWD, &passwd, 1)
|| get_field (conn, MU_AUTH_UID, &suid, 1)
|| get_field (conn, MU_AUTH_GID, &sgid, 1)
|| get_field (conn, MU_AUTH_DIR, &dir, 1)
|| get_field (conn, MU_AUTH_SHELL, &shell, 1))
return MU_ERR_FAILURE;
if (get_field (conn, MU_AUTH_GECOS, &gecos, 0))
gecos = "SQL user";
uid = strtoul (suid, &p, 0);
if (*p)
{
mu_error (_("invalid value for uid: %s"), suid);
return MU_ERR_FAILURE;
}
gid = strtoul (sgid, &p, 0);
if (*p)
{
mu_error (_("invalid value for gid: %s"), sgid);
return MU_ERR_FAILURE;
}
rc = get_field (conn, MU_AUTH_MAILBOX, &mailbox_name, 0);
switch (rc)
{
case 0:
mailbox_name = strdup (mailbox_name);
if (!mailbox_name)
return ENOMEM;
break;
case MU_ERR_NOENT:
if (mu_construct_user_mailbox_url (&mailbox_name, name))
return MU_ERR_FAILURE;
break;
default:
return MU_ERR_FAILURE;
}
rc = get_field (conn, MU_AUTH_QUOTA, &squota, 0);
if (rc == 0)
{
if (mu_c_strcasecmp (squota, "none") == 0)
quota = 0;
else
{
quota = strtoul (squota, &p, 10);
switch (*p)
{
case 0:
break;
case 'k':
case 'K':
quota *= 1024;
break;
case 'm':
case 'M':
quota *= 1024*1024;
break;
default:
mu_error (_("invalid value for quota: %s"), squota);
free (mailbox_name);
return MU_ERR_FAILURE;
}
}
}
else if (rc == MU_ERR_NOENT)
quota = 0;
else
{
free (mailbox_name);
return MU_ERR_FAILURE;
}
rc = mu_auth_data_alloc (return_data,
name,
passwd,
uid,
gid,
gecos,
dir,
shell,
mailbox_name,
1);
if (rc == 0)
mu_auth_data_set_quota (*return_data, quota);
free (mailbox_name);
return rc;
}
static int
mu_auth_sql_by_name (struct mu_auth_data **return_data,
const void *key,
void *func_data MU_ARG_UNUSED,
void *call_data MU_ARG_UNUSED)
{
int status, rc;
char *query_str = NULL;
mu_sql_connection_t conn;
size_t n;
if (!key)
return EINVAL;
query_str = mu_sql_expand_query (mu_sql_module_config.getpwnam_query, key);
if (!query_str)
return MU_ERR_FAILURE;
status = mu_sql_connection_init (&conn,
mu_sql_module_config.interface,
mu_sql_module_config.host,
mu_sql_module_config.port,
mu_sql_module_config.user,
mu_sql_module_config.passwd,
mu_sql_module_config.db,
mu_sql_module_config.param);
if (status)
{
mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
mu_sql_connection_destroy (&conn);
free (query_str);
return MU_ERR_FAILURE;
}
status = mu_sql_connect (conn);
if (status)
{
mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
mu_sql_connection_destroy (&conn);
free (query_str);
return EAGAIN;
}
status = mu_sql_query (conn, query_str);
free (query_str);
if (status)
{
mu_error (_("SQL query failed: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
status = mu_sql_store_result (conn);
if (status)
{
mu_error (_("cannot store SQL result: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
status = mu_sql_num_tuples (conn, &n);
if (status)
{
mu_error (_("cannot get number of tuples: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_release_result (conn);
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
if (n == 0)
rc = MU_ERR_AUTH_FAILURE;
else
rc = decode_tuple (conn, n, return_data);
mu_sql_release_result (conn);
mu_sql_disconnect (conn);
mu_sql_connection_destroy (&conn);
return rc;
}
static int
mu_auth_sql_by_uid (struct mu_auth_data **return_data,
const void *key,
void *func_data MU_ARG_UNUSED,
void *call_data MU_ARG_UNUSED)
{
char uidstr[64];
int status, rc;
char *query_str = NULL;
mu_sql_connection_t conn;
size_t n;
if (!key)
return EINVAL;
snprintf (uidstr, sizeof (uidstr), "%u", *(uid_t*)key);
query_str = mu_sql_expand_query (mu_sql_module_config.getpwuid_query,
uidstr);
if (!query_str)
return ENOMEM;
status = mu_sql_connection_init (&conn,
mu_sql_module_config.interface,
mu_sql_module_config.host,
mu_sql_module_config.port,
mu_sql_module_config.user,
mu_sql_module_config.passwd,
mu_sql_module_config.db,
mu_sql_module_config.param);
if (status)
{
mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
mu_sql_connection_destroy (&conn);
free (query_str);
return MU_ERR_FAILURE;
}
status = mu_sql_connect (conn);
if (status)
{
mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
mu_sql_connection_destroy (&conn);
free (query_str);
return EAGAIN;
}
status = mu_sql_query (conn, query_str);
free (query_str);
if (status)
{
mu_error (_("SQL query failed: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
status = mu_sql_store_result (conn);
if (status)
{
mu_error (_("cannot store SQL result: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
status = mu_sql_num_tuples (conn, &n);
if (status)
{
mu_error (_("cannot get number of tuples: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_release_result (conn);
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
if (n == 0)
rc = MU_ERR_AUTH_FAILURE;
else
rc = decode_tuple (conn, n, return_data);
mu_sql_release_result (conn);
mu_sql_disconnect (conn);
mu_sql_connection_destroy (&conn);
return rc;
}
int
mu_sql_getpass (const char *username, char **passwd)
{
mu_sql_connection_t conn;
char *query_str;
int status;
char *sql_pass;
size_t nt;
query_str = mu_sql_expand_query (mu_sql_module_config.getpass_query, username);
if (!query_str)
return MU_ERR_FAILURE;
status = mu_sql_connection_init (&conn,
mu_sql_module_config.interface,
mu_sql_module_config.host,
mu_sql_module_config.port,
mu_sql_module_config.user,
mu_sql_module_config.passwd,
mu_sql_module_config.db,
mu_sql_module_config.param);
if (status)
{
mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
mu_sql_connection_destroy (&conn);
free (query_str);
return MU_ERR_FAILURE;
}
status = mu_sql_connect (conn);
if (status)
{
mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
mu_sql_connection_destroy (&conn);
free (query_str);
return EAGAIN;
}
status = mu_sql_query (conn, query_str);
free (query_str);
if (status)
{
mu_error (_("SQL query failed: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
status = mu_sql_store_result (conn);
if (status)
{
mu_error (_("cannot store SQL result: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
status = mu_sql_num_tuples (conn, &nt);
if (status)
{
mu_error (_("cannot get number of tuples: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_release_result (conn);
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
if (nt == 0)
{
mu_sql_release_result (conn);
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
status = mu_sql_get_column (conn, 0, 0, &sql_pass);
if (status)
{
mu_error (_("cannot get password from SQL: %s"),
(status == MU_ERR_SQL) ? mu_sql_strerror (conn) :
mu_strerror (status));
mu_sql_release_result (conn);
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
if (!sql_pass)
{
mu_error (_("SQL returned NULL password"));
mu_sql_release_result (conn);
mu_sql_connection_destroy (&conn);
return MU_ERR_FAILURE;
}
*passwd = strdup (sql_pass);
mu_sql_disconnect (conn);
mu_sql_connection_destroy (&conn);
if (!*passwd)
return ENOMEM;
return 0;
}
static int
mu_sql_authenticate (struct mu_auth_data **return_data MU_ARG_UNUSED,
const void *key,
void *func_data MU_ARG_UNUSED, void *call_data)
{
const struct mu_auth_data *auth_data = key;
char *pass = call_data;
char *sql_pass, *crypt_pass;
int rc;
if (!auth_data)
return EINVAL;
if ((rc = mu_sql_getpass (auth_data->name, &sql_pass)))
return rc;
switch (mu_sql_module_config.password_encryption)
{
case mu_sql_password_hash:
crypt_pass = crypt (pass, sql_pass);
if (!crypt_pass)
rc = 1;
else
rc = strcmp (sql_pass, crypt_pass);
break;
case mu_sql_password_scrambled:
/* FIXME: Should this call be implementation-independent? I mean,
should we have mu_sql_check_scrambled() that will match the
password depending on the exact type of the underlying database,
just as the rest of mu_sql_.* functions do */
#ifdef HAVE_MYSQL
rc = mu_check_mysql_scrambled_password (sql_pass, pass);
#else
rc = 1;
#endif
break;
case mu_sql_password_plaintext:
rc = strcmp (sql_pass, pass);
break;
}
free (sql_pass);
return rc == 0 ? 0 : MU_ERR_AUTH_FAILURE;
}
#else
# define mu_sql_authenticate mu_auth_nosupport
# define mu_auth_sql_by_name mu_auth_nosupport
# define mu_auth_sql_by_uid mu_auth_nosupport
# define mu_sql_param NULL
#endif
struct mu_auth_module mu_auth_sql_module = {
.name = "sql",
.cfg = mu_sql_param,
.handler = {
[mu_auth_authenticate] = mu_sql_authenticate,
[mu_auth_getpwnam] = mu_auth_sql_by_name,
[mu_auth_getpwuid] = mu_auth_sql_by_uid
}
};