Here's a patch that implements mysql authentication. I started with the pgsql files and tweaked them to use mysql instead. It works for me, but there might be a couple of memory leaks. I'm welcome to suggestions on how to clean it up so it can be committed. Enjoy! Matt diff -u -r --new-file work/dovecot-0.99.10/doc/dovecot-mysql.conf work.patched/dovecot-0.99.10/doc/dovecot-mysql.conf --- work/dovecot-0.99.10/doc/dovecot-mysql.conf Wed Dec 31 18:00:00 1969 +++ work.patched/dovecot-0.99.10/doc/dovecot-mysql.conf Fri Aug 15 13:43:05 2003 @@ -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 -u -r --new-file work/dovecot-0.99.10/src/auth/Makefile.am work.patched/dovecot-0.99.10/src/auth/Makefile.am --- work/dovecot-0.99.10/src/auth/Makefile.am Sun May 18 07:26:28 2003 +++ work.patched/dovecot-0.99.10/src/auth/Makefile.am Fri Aug 15 16:47:21 2003 @@ -19,6 +19,7 @@ auth-module.c \ db-ldap.c \ db-pgsql.c \ + db-mysql.c \ db-passwd-file.c \ login-connection.c \ main.c \ @@ -38,6 +39,7 @@ passdb-shadow.c \ passdb-vpopmail.c \ passdb-pgsql.c \ + passdb-mysql.c \ password-scheme.c \ userdb.c \ userdb-ldap.c \ @@ -45,7 +47,8 @@ userdb-passwd-file.c \ userdb-static.c \ userdb-vpopmail.c \ - userdb-pgsql.c + userdb-pgsql.c \ + userdb-mysql.c noinst_HEADERS = \ auth-login-interface.h \ @@ -54,6 +57,7 @@ auth-module.h \ db-ldap.h \ db-pgsql.h \ + db-mysql.h \ db-passwd-file.h \ common.h \ login-connection.h \ diff -u -r --new-file work/dovecot-0.99.10/src/auth/db-mysql.c work.patched/dovecot-0.99.10/src/auth/db-mysql.c --- work/dovecot-0.99.10/src/auth/db-mysql.c Wed Dec 31 18:00:00 1969 +++ work.patched/dovecot-0.99.10/src/auth/db-mysql.c Fri Aug 22 17:56:44 2003 @@ -0,0 +1,181 @@ +/* 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 <limits.h> +#include <stddef.h> +#include <stdlib.h> + +#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; + settings_read(config_path, parse_setting, conn); + + (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 -u -r --new-file work/dovecot-0.99.10/src/auth/db-mysql.h work.patched/dovecot-0.99.10/src/auth/db-mysql.h --- work/dovecot-0.99.10/src/auth/db-mysql.h Wed Dec 31 18:00:00 1969 +++ work.patched/dovecot-0.99.10/src/auth/db-mysql.h Fri Aug 15 13:40:56 2003 @@ -0,0 +1,51 @@ +#ifndef __DB_MYSQL_H +#define __DB_MYSQL_H + +#include <mysql.h> + +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 -u -r --new-file work/dovecot-0.99.10/src/auth/master-connection.c work.patched/dovecot-0.99.10/src/auth/master-connection.c --- work/dovecot-0.99.10/src/auth/master-connection.c Mon May 26 10:27:13 2003 +++ work.patched/dovecot-0.99.10/src/auth/master-connection.c Fri Aug 22 17:56:18 2003 @@ -55,7 +55,7 @@ reply.virtual_user_idx = reply_add(buf, user->virtual_user); reply.mail_idx = reply_add(buf, user->mail); - p = strstr(user->home, "/./"); + p = user->home ? strstr(user->home, "/./") : NULL; if (p == NULL) { reply.home_idx = reply_add(buf, user->home); reply.chroot_idx = reply_add(buf, NULL); diff -u -r --new-file work/dovecot-0.99.10/src/auth/passdb-mysql.c work.patched/dovecot-0.99.10/src/auth/passdb-mysql.c --- work/dovecot-0.99.10/src/auth/passdb-mysql.c Wed Dec 31 18:00:00 1969 +++ work.patched/dovecot-0.99.10/src/auth/passdb-mysql.c Fri Aug 15 14:24:44 2003 @@ -0,0 +1,171 @@ +/* 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 <mysql.h> +#include <stdlib.h> +#include <string.h> + +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]); /* XXX binary? */ + } + } + + 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 -u -r --new-file work/dovecot-0.99.10/src/auth/passdb.c work.patched/dovecot-0.99.10/src/auth/passdb.c --- work/dovecot-0.99.10/src/auth/passdb.c Sun May 18 07:26:28 2003 +++ work.patched/dovecot-0.99.10/src/auth/passdb.c Fri Aug 15 11:39:31 2003 @@ -110,6 +110,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 -u -r --new-file work/dovecot-0.99.10/src/auth/passdb.h work.patched/dovecot-0.99.10/src/auth/passdb.h --- work/dovecot-0.99.10/src/auth/passdb.h Thu Mar 20 09:46:33 2003 +++ work.patched/dovecot-0.99.10/src/auth/passdb.h Fri Aug 15 16:45:43 2003 @@ -58,6 +58,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 -u -r --new-file work/dovecot-0.99.10/src/auth/userdb-mysql.c work.patched/dovecot-0.99.10/src/auth/userdb-mysql.c --- work/dovecot-0.99.10/src/auth/userdb-mysql.c Wed Dec 31 18:00:00 1969 +++ work.patched/dovecot-0.99.10/src/auth/userdb-mysql.c Fri Aug 22 17:53:56 2003 @@ -0,0 +1,148 @@ +/* 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 <mysql.h> +#include <stdlib.h> +#include <string.h> + +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) +{ + + 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; + } + + /* XXX */ + if (mysql_num_fields(res) < 3) { + i_error("MYSQL: Not enough fields returned"); + return FALSE; + } +/* XXX + if (PQfnumber(res, "uid") == -1) { + i_error("MYSQL: User query did not return 'uid' field"); + return FALSE; + } + + if (PQfnumber(res, "gid") == -1) { + 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 (const char *) 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")); /* XXX leak */ + user.gid = atoi(my_get_str(res, row, "gid")); /* XXX leak */ + 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 -u -r --new-file work/dovecot-0.99.10/src/auth/userdb.c work.patched/dovecot-0.99.10/src/auth/userdb.c --- work/dovecot-0.99.10/src/auth/userdb.c Sun May 18 07:26:28 2003 +++ work.patched/dovecot-0.99.10/src/auth/userdb.c Fri Aug 22 13:07:21 2003 @@ -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 -u -r --new-file work/dovecot-0.99.10/src/auth/userdb.h work.patched/dovecot-0.99.10/src/auth/userdb.h --- work/dovecot-0.99.10/src/auth/userdb.h Sun May 18 07:26:28 2003 +++ work.patched/dovecot-0.99.10/src/auth/userdb.h Fri Aug 22 13:07:53 2003 @@ -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);