# HG changeset patch # User Timo Sirainen # Date 1084153628 -10800 # Node ID cc64f8bb47163a224f76c9dce2678f240d9285e1 # Parent 77f3111f79766ab4d0ea8e0156b3030b1673f370 MySQL authentication patch by Matther Reimer diff -r 77f3111f7976 -r cc64f8bb4716 AUTHORS --- a/AUTHORS Mon May 10 04:28:19 2004 +0300 +++ b/AUTHORS Mon May 10 04:47:08 2004 +0300 @@ -6,6 +6,8 @@ Alex Howansky (src/auth/*-pgsql.c) +Matthew Reimer (src/auth/*-mysql.c) + This product includes software developed by Computing Services at Carnegie Mellon University (http://www.cmu.edu/computing/). (src/lib/base64.c, src/lib/mkgmtime.c) diff -r 77f3111f7976 -r cc64f8bb4716 configure.in --- a/configure.in Mon May 10 04:28:19 2004 +0300 +++ b/configure.in Mon May 10 04:47:08 2004 +0300 @@ -134,6 +134,15 @@ fi, want_pgsql=no) +AC_ARG_WITH(mysql, +[ --with-mysql Build with MySQL support], + if test x$withval = xno; then + want_mysql=no + else + want_mysql=yes + fi, + want_mysql=no) + AC_ARG_WITH(cyrus-sasl2, [ --with-cyrus-sasl2 Build with Cyrus SASL 2 library support], if test x$withval = xno; then @@ -1016,6 +1025,25 @@ LIBS=$old_LIBS fi +if test $want_mysql = yes; then + AC_CHECK_LIB(mysqlclient, mysql_init, [ + AC_CHECK_HEADER(mysql.h,, [ + AC_CHECK_HEADER(mysql/mysql.h, [ + AUTH_CFLAGS="$AUTH_CFLAGS -DHAVE_MYSQL_MYSQL_H" + ], want_mysql = no) + ]) + ], want_mysql = no) + + if test $want_mysql = yes; then + AUTH_LIBS="$AUTH_LIBS -lmysqlclient" + + AC_DEFINE(USERDB_MYSQL,, Build with MySQL support) + AC_DEFINE(PASSDB_MYSQL,, Build with MySQL support) + userdb="$userdb mysql" + passdb="$passdb mysql" + fi +fi + if test $want_vpopmail = yes; then vpopmail_home="`echo ~vpopmail`" vpop_libdeps="$vpopmail_home/etc/lib_deps" diff -r 77f3111f7976 -r cc64f8bb4716 doc/Makefile.am --- a/doc/Makefile.am Mon May 10 04:28:19 2004 +0300 +++ b/doc/Makefile.am Mon May 10 04:47:08 2004 +0300 @@ -14,5 +14,6 @@ mkcert.sh \ dovecot-openssl.cnf \ dovecot-ldap.conf \ + dovecot-mysql.conf \ dovecot-pgsql.conf \ $(doc_DATA) diff -r 77f3111f7976 -r cc64f8bb4716 doc/dovecot-mysql.conf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/dovecot-mysql.conf Mon May 10 04:47:08 2004 +0300 @@ -0,0 +1,70 @@ +# For the mysql passdb module, you'll need a database with a table that +# contains fields for at least the userid and password. If you want to +# use the user@domain syntax, you might want to have a separate domain +# field as well. +# +# If your users all have the same uig/gid, and have predictable home +# directories, you can use the static userdb module to generate the home +# dir based on the userid and domain. In this case, you won't need fields +# for home, uid, or gid in the database. +# +# If you prefer to use the mysql userdb module, you'll want to add fields +# for home, uid, and gid. Here is an example table: +# +# CREATE TABLE users ( +# userid VARCHAR(128) NOT NULL, +# password VARCHAR(64) NOT NULL, +# home VARCHAR(256) NOT NULL, +# uid INTEGER NOT NULL, +# gid INTEGER NOT NULL, +# active CHAR(1) DEFAULT 'Y' NOT NULL +# ); + +db_host = localhost +db_port = 3306 +#db_unix_socket = /var/tmp/mysql.sock +db = users +db_user = dovecot-db +db_passwd = opensesame +db_client_flags = 0 + +# Default password scheme. +# +# Currently supported schemes include PLAIN, PLAIN-MD5, DIGEST-MD5, and CRYPT. +# +#default_pass_scheme = PLAIN-MD5 + +# Query to retrieve the password. +# +# The query should return one row, one column. If more than one row or column +# is returned, authentication will automatically fail. +# +# Available substitutions: +# %u = entire userid +# %n = user part of user@domain +# %d = domain part of user@domain +# +# Example: +# password_query = SELECT password FROM users WHERE userid = '%n' AND domain = '%d' +# password_query = SELECT password FROM users WHERE userid = '%u' AND active = 'Y' +# +#password_query = SELECT password FROM users WHERE userid = '%u' + +# Query to retrieve the user information. +# +# The query must return only one row. The columns to return are: +# home - Home directory +# mail - MAIL environment +# system_user - System user name (for initgroups()) +# uid - System UID +# gid - System GID +# +# Either home or mail is required. uid and gid are required. If more than one +# row is returned or there's missing fields, login will automatically fail. +# +# Examples +# user_query = SELECT home, uid, gid FROM users WHERE userid = '%n' AND domain = '%d' +# user_query = SELECT dir AS home, user AS uid, group AS gid FROM users where userid = '%u' +# user_query = SELECT home, 501 AS uid, 501 AS gid FROM users WHERE userid = '%u' +# +#user_query = SELECT home, uid, gid FROM users WHERE userid = '%u' diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/Makefile.am --- a/src/auth/Makefile.am Mon May 10 04:28:19 2004 +0300 +++ b/src/auth/Makefile.am Mon May 10 04:47:08 2004 +0300 @@ -20,6 +20,7 @@ auth-master-connection.c \ auth-module.c \ db-ldap.c \ + db-mysql.c \ db-pgsql.c \ db-passwd-file.c \ main.c \ @@ -38,6 +39,7 @@ passdb-pam.c \ passdb-shadow.c \ passdb-vpopmail.c \ + passdb-mysql.c \ passdb-pgsql.c \ password-scheme.c \ password-scheme-md5crypt.c \ @@ -48,6 +50,7 @@ userdb-passwd-file.c \ userdb-static.c \ userdb-vpopmail.c \ + userdb-mysql.c \ userdb-pgsql.c noinst_HEADERS = \ @@ -58,6 +61,7 @@ auth-mech-desc.h \ auth-module.h \ db-ldap.h \ + db-mysql.h \ db-pgsql.h \ db-passwd-file.h \ common.h \ diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/db-mysql.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/db-mysql.c Mon May 10 04:47:08 2004 +0300 @@ -0,0 +1,182 @@ +/* Copyright (C) 2003 Alex Howansky, Timo Sirainen */ + +#include "config.h" +#undef HAVE_CONFIG_H + +#if defined(PASSDB_MYSQL) || defined(USERDB_MYSQL) + +#include "common.h" +#include "network.h" +#include "str.h" +#include "settings.h" +#include "db-mysql.h" + +#include +#include +#include + +#define DEF(type, name) { type, #name, offsetof(struct mysql_settings, name) } + +static struct setting_def setting_defs[] = { + DEF(SET_STR, db_host), + DEF(SET_STR, db_port), + DEF(SET_STR, db_unix_socket), + DEF(SET_STR, db), + DEF(SET_STR, db_user), + DEF(SET_STR, db_passwd), + DEF(SET_STR, db_client_flags), + DEF(SET_STR, password_query), + DEF(SET_STR, user_query), + DEF(SET_STR, default_pass_scheme) +}; + +struct mysql_settings default_mysql_settings = { + MEMBER(db_host) "localhost", + MEMBER(db_port) "0", + MEMBER(db_unix_socket) "/var/tmp/mysql.sock", + MEMBER(db) "email_accounts", + MEMBER(db_user) "dovecot", + MEMBER(db_passwd) "changeme", + MEMBER(db_client_flags) "0", + MEMBER(password_query) "SELECT password FROM users WHERE userid = '%u'", + MEMBER(user_query) "SELECT home, uid, gid FROM users WHERE userid = '%u'", + MEMBER(default_pass_scheme) "PLAIN-MD5" +}; + +static struct mysql_connection *mysql_connections = NULL; + +static int mysql_conn_open(struct mysql_connection *conn); +static void mysql_conn_close(struct mysql_connection *conn); + +void db_mysql_query(struct mysql_connection *conn, const char *query, + struct mysql_request *request) +{ + MYSQL_RES *res; + int failed; + + if (!conn->connected) { + if (!mysql_conn_open(conn)) { + request->callback(conn, request, NULL); + return; + } + } + + if (verbose_debug) + i_info("MYSQL: Performing query: %s", query); + + if (mysql_query(conn->mysql, query)) + i_info("MYSQL: Error executing query \"%s\": %s", query, + mysql_error(conn->mysql)); + + if ((res = mysql_store_result(conn->mysql))) + failed = FALSE; + else { + i_info("MYSQL: Error retrieving results: %s", + mysql_error(conn->mysql)); + failed = TRUE; + } + + request->callback(conn, request, failed ? NULL : res); + mysql_free_result(res); + i_free(request); +} + +static int mysql_conn_open(struct mysql_connection *conn) +{ + if (conn->connected) + return TRUE; + + if (conn->mysql == NULL) { + conn->mysql = mysql_init(NULL); + if (conn->mysql == NULL) { + i_error("MYSQL: mysql_init failed"); + return FALSE; + } + + if (!mysql_real_connect(conn->mysql, conn->set.db_host, + conn->set.db_user, conn->set.db_passwd, + conn->set.db, + atoi(conn->set.db_port), + conn->set.db_unix_socket, + strtoul(conn->set.db_client_flags, + NULL, 10))) { + i_error("MYSQL: Can't connect to database %s: %s", + conn->set.db, mysql_error(conn->mysql)); + return FALSE; + } + } + + conn->connected = TRUE; + return TRUE; +} + +static void mysql_conn_close(struct mysql_connection *conn) +{ + conn->connected = FALSE; + + if (conn->mysql != NULL) { + mysql_close(conn->mysql); + conn->mysql = NULL; + } +} + +static struct mysql_connection *mysql_conn_find(const char *config_path) +{ + struct mysql_connection *conn; + + for (conn = mysql_connections; conn != NULL; conn = conn->next) { + if (strcmp(conn->config_path, config_path) == 0) + return conn; + } + + return NULL; +} + +static const char *parse_setting(const char *key, const char *value, + void *context) +{ + struct mysql_connection *conn = context; + + return parse_setting_from_defs(conn->pool, setting_defs, + &conn->set, key, value); +} + +struct mysql_connection *db_mysql_init(const char *config_path) +{ + struct mysql_connection *conn; + pool_t pool; + + conn = mysql_conn_find(config_path); + if (conn != NULL) { + conn->refcount++; + return conn; + } + + pool = pool_alloconly_create("mysql_connection", 1024); + conn = p_new(pool, struct mysql_connection, 1); + conn->pool = pool; + + conn->refcount = 1; + + conn->config_path = p_strdup(pool, config_path); + conn->set = default_mysql_settings; + if (!settings_read(config_path, NULL, parse_setting, NULL, conn)) + exit(FATAL_DEFAULT); + + (void)mysql_conn_open(conn); + + conn->next = mysql_connections; + mysql_connections = conn; + return conn; +} + +void db_mysql_unref(struct mysql_connection *conn) +{ + if (--conn->refcount > 0) + return; + + mysql_conn_close(conn); + pool_unref(conn->pool); +} + +#endif diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/db-mysql.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/db-mysql.h Mon May 10 04:47:08 2004 +0300 @@ -0,0 +1,55 @@ +#ifndef __DB_MYSQL_H +#define __DB_MYSQL_H + +#ifdef HAVE_MYSQL_MYSQL_H +# include +#else +# include +#endif + +struct mysql_connection; +struct mysql_request; + +typedef void mysql_query_callback_t(struct mysql_connection *conn, + struct mysql_request *request, + MYSQL_RES *res); + +struct mysql_settings { + const char *db_host; + const char *db_port; + const char *db_unix_socket; + const char *db; + const char *db_user; + const char *db_passwd; + const char *db_client_flags; + const char *password_query; + const char *user_query; + const char *default_pass_scheme; +}; + +struct mysql_connection { + struct mysql_connection *next; + + pool_t pool; + int refcount; + + char *config_path; + struct mysql_settings set; + + MYSQL *mysql; + + unsigned int connected:1; +}; + +struct mysql_request { + mysql_query_callback_t *callback; + void *context; +}; + +void db_mysql_query(struct mysql_connection *conn, const char *query, + struct mysql_request *request); + +struct mysql_connection *db_mysql_init(const char *config_path); +void db_mysql_unref(struct mysql_connection *conn); + +#endif diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/passdb-mysql.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/passdb-mysql.c Mon May 10 04:47:08 2004 +0300 @@ -0,0 +1,170 @@ +/* Copyright (C) 2003 Alex Howansky, Timo Sirainen */ + +#include "config.h" +#undef HAVE_CONFIG_H + +#ifdef PASSDB_MYSQL + +#include "common.h" +#include "str.h" +#include "strescape.h" +#include "var-expand.h" +#include "password-scheme.h" +#include "db-mysql.h" +#include "passdb.h" + +#include +#include + +struct passdb_mysql_connection { + struct mysql_connection *conn; +}; + +struct passdb_mysql_request { + struct mysql_request request; + + enum passdb_credentials credentials; + union { + verify_plain_callback_t *verify_plain; + lookup_credentials_callback_t *lookup_credentials; + } callback; + + char password[1]; +}; + +static struct passdb_mysql_connection *passdb_mysql_conn; + +static void mysql_handle_request(struct mysql_connection *conn, + struct mysql_request *request, MYSQL_RES *res) +{ + struct passdb_mysql_request *mysql_request = + (struct passdb_mysql_request *) request; + struct auth_request *auth_request = request->context; + const char *user, *password, *scheme; + int ret = 0; + + user = auth_request->user; + password = NULL; + + if (res != NULL) { + if (mysql_num_rows(res) == 0) { + if (verbose) + i_info("mysql(%s): Unknown user", user); + } else if (mysql_num_rows(res) > 1) { + i_error("mysql(%s): Multiple matches for user", user); + } else if (mysql_num_fields(res) != 1) { + i_error("mysql(%s): Password query returned " + "more than one field", user); + } else { + MYSQL_ROW row; + + row = mysql_fetch_row(res); + if (row) + password = t_strdup(row[0]); + } + } + + scheme = password_get_scheme(&password); + if (scheme == NULL) { + scheme = conn->set.default_pass_scheme; + i_assert(scheme != NULL); + } + + if (mysql_request->credentials != -1) { + passdb_handle_credentials(mysql_request->credentials, + user, password, scheme, + mysql_request->callback.lookup_credentials, + auth_request); + return; + } + + /* verify plain */ + if (password == NULL) { + mysql_request->callback.verify_plain(PASSDB_RESULT_USER_UNKNOWN, + auth_request); + return; + } + + ret = password_verify(mysql_request->password, password, + scheme, user); + if (ret < 0) + i_error("mysql(%s): Unknown password scheme %s", user, scheme); + else if (ret == 0) { + if (verbose) + i_info("mysql(%s): Password mismatch", user); + } + + mysql_request->callback.verify_plain(ret > 0 ? PASSDB_RESULT_OK : + PASSDB_RESULT_PASSWORD_MISMATCH, + auth_request); +} + +static void mysql_lookup_pass(struct auth_request *auth_request, + struct mysql_request *mysql_request) +{ + struct mysql_connection *conn = passdb_mysql_conn->conn; + const char *query; + string_t *str; + + str = t_str_new(512); + var_expand(str, conn->set.password_query, + str_escape(auth_request->user), NULL); + query = str_c(str); + + mysql_request->callback = mysql_handle_request; + mysql_request->context = auth_request; + + db_mysql_query(conn, query, mysql_request); +} + +static void +mysql_verify_plain(struct auth_request *request, const char *password, + verify_plain_callback_t *callback) +{ + struct passdb_mysql_request *mysql_request; + + mysql_request = i_malloc(sizeof(struct passdb_mysql_request) + + strlen(password)); + mysql_request->credentials = -1; + mysql_request->callback.verify_plain = callback; + strcpy(mysql_request->password, password); + + mysql_lookup_pass(request, &mysql_request->request); +} + +static void mysql_lookup_credentials(struct auth_request *request, + enum passdb_credentials credentials, + lookup_credentials_callback_t *callback) +{ + struct passdb_mysql_request *mysql_request; + + mysql_request = i_new(struct passdb_mysql_request, 1); + mysql_request->credentials = credentials; + mysql_request->callback.lookup_credentials = callback; + + mysql_lookup_pass(request, &mysql_request->request); +} + +static void passdb_mysql_init(const char *args) +{ + struct mysql_connection *conn; + + passdb_mysql_conn = i_new(struct passdb_mysql_connection, 1); + passdb_mysql_conn->conn = conn = db_mysql_init(args); +} + +static void passdb_mysql_deinit(void) +{ + db_mysql_unref(passdb_mysql_conn->conn); + i_free(passdb_mysql_conn); +} + +struct passdb_module passdb_mysql = { + passdb_mysql_init, + passdb_mysql_deinit, + + mysql_verify_plain, + mysql_lookup_credentials +}; + +#endif diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/passdb.c --- a/src/auth/passdb.c Mon May 10 04:28:19 2004 +0300 +++ b/src/auth/passdb.c Mon May 10 04:47:08 2004 +0300 @@ -116,6 +116,10 @@ if (strcasecmp(name, "pgsql") == 0) passdb = &passdb_pgsql; #endif +#ifdef PASSDB_MYSQL + if (strcasecmp(name, "mysql") == 0) + passdb = &passdb_mysql; +#endif #ifdef HAVE_MODULES passdb_module = passdb != NULL ? NULL : auth_module_open(name); if (passdb_module != NULL) { diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/passdb.h --- a/src/auth/passdb.h Mon May 10 04:28:19 2004 +0300 +++ b/src/auth/passdb.h Mon May 10 04:47:08 2004 +0300 @@ -60,6 +60,7 @@ extern struct passdb_module passdb_vpopmail; extern struct passdb_module passdb_ldap; extern struct passdb_module passdb_pgsql; +extern struct passdb_module passdb_mysql; void passdb_init(void); void passdb_deinit(void); diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/userdb-mysql.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/userdb-mysql.c Mon May 10 04:47:08 2004 +0300 @@ -0,0 +1,159 @@ +/* Copyright (C) 2003 Alex Howansky, Timo Sirainen */ + +#include "config.h" +#undef HAVE_CONFIG_H + +#ifdef USERDB_MYSQL + +#include "common.h" +#include "str.h" +#include "strescape.h" +#include "var-expand.h" +#include "db-mysql.h" +#include "userdb.h" + +#include +#include + +struct userdb_mysql_connection { + struct mysql_connection *conn; +}; + +struct userdb_mysql_request { + struct mysql_request request; + userdb_callback_t *userdb_callback; + + char username[1]; /* variable width */ +}; + +static struct userdb_mysql_connection *userdb_mysql_conn; + +static int is_result_valid(MYSQL_RES *res) +{ + int i, n_fields, found; + MYSQL_FIELD *fields; + + if (res == NULL) { + i_error("MYSQL: Query failed"); + return FALSE; + } + + if (mysql_num_rows(res) == 0) { + if (verbose) + i_error("MYSQL: Authenticated user not found"); + return FALSE; + } + + n_fields = mysql_num_fields(res); + fields = mysql_fetch_fields(res); + + /* Make sure the 'uid' field exists. */ + for (found = 0, i = 0; i < n_fields; i++) + if (strcmp("uid", fields[i].name) == 0) { + found = 1; + break; + } + + if (!found) { + i_error("MYSQL: User query did not return 'uid' field"); + return FALSE; + } + + /* Make sure the 'gid' field exists. */ + for (found = 0, i = 0; i < n_fields; i++) + if (strcmp("gid", fields[i].name) == 0) { + found = 1; + break; + } + + if (!found) { + i_error("MYSQL: User query did not return 'gid' field"); + return FALSE; + } + + return TRUE; +} + +static const char *my_get_str(MYSQL_RES *res, MYSQL_ROW row, const char *field) +{ + int i, n_fields; + unsigned long *lengths; + MYSQL_FIELD *fields; + + n_fields = mysql_num_fields(res); + lengths = mysql_fetch_lengths(res); + fields = mysql_fetch_fields(res); + for (i = 0; i < n_fields; i++) { + if (strcmp(field, fields[i].name) == 0) + return lengths[i] == 0 ? NULL : t_strdup(row[i]); + } + + return NULL; +} + +static void mysql_handle_request(struct mysql_connection *conn __attr_unused__, + struct mysql_request *request, MYSQL_RES *res) +{ + struct userdb_mysql_request *urequest = + (struct userdb_mysql_request *) request; + struct user_data user; + MYSQL_ROW row; + + if (res != NULL && is_result_valid(res) && + (row = mysql_fetch_row(res))) { + memset(&user, 0, sizeof(user)); + user.virtual_user = urequest->username; + user.system_user = my_get_str(res, row, "system_user"); + user.home = my_get_str(res, row, "home"); + user.mail = my_get_str(res, row, "mail"); + user.uid = atoi(my_get_str(res, row, "uid")); + user.gid = atoi(my_get_str(res, row, "gid")); + urequest->userdb_callback(&user, request->context); + } else { + urequest->userdb_callback(NULL, request->context); + } +} + +static void userdb_mysql_lookup(const char *user, userdb_callback_t *callback, + void *context) +{ + struct mysql_connection *conn = userdb_mysql_conn->conn; + struct userdb_mysql_request *request; + const char *query; + string_t *str; + + str = t_str_new(512); + var_expand(str, conn->set.user_query, str_escape(user), NULL); + query = str_c(str); + + request = i_malloc(sizeof(struct userdb_mysql_request) + strlen(user)); + request->request.callback = mysql_handle_request; + request->request.context = context; + request->userdb_callback = callback; + strcpy(request->username, user); + + db_mysql_query(conn, query, &request->request); +} + +static void userdb_mysql_init(const char *args) +{ + struct mysql_connection *conn; + + userdb_mysql_conn = i_new(struct userdb_mysql_connection, 1); + userdb_mysql_conn->conn = conn = db_mysql_init(args); +} + +static void userdb_mysql_deinit(void) +{ + db_mysql_unref(userdb_mysql_conn->conn); + i_free(userdb_mysql_conn); +} + +struct userdb_module userdb_mysql = { + userdb_mysql_init, + userdb_mysql_deinit, + + userdb_mysql_lookup +}; + +#endif diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/userdb.c --- a/src/auth/userdb.c Mon May 10 04:28:19 2004 +0300 +++ b/src/auth/userdb.c Mon May 10 04:47:08 2004 +0300 @@ -49,6 +49,10 @@ if (strcasecmp(name, "pgsql") == 0) userdb = &userdb_pgsql; #endif +#ifdef USERDB_MYSQL + if (strcasecmp(name, "mysql") == 0) + userdb = &userdb_mysql; +#endif #ifdef HAVE_MODULES userdb_module = userdb != NULL ? NULL : auth_module_open(name); if (userdb_module != NULL) { diff -r 77f3111f7976 -r cc64f8bb4716 src/auth/userdb.h --- a/src/auth/userdb.h Mon May 10 04:28:19 2004 +0300 +++ b/src/auth/userdb.h Mon May 10 04:47:08 2004 +0300 @@ -29,6 +29,7 @@ extern struct userdb_module userdb_vpopmail; extern struct userdb_module userdb_ldap; extern struct userdb_module userdb_pgsql; +extern struct userdb_module userdb_mysql; void userdb_init(void); void userdb_deinit(void);