changeset 3183:16ea551957ed HEAD

Replaced userdb/passdb settings with blocks so it's possible to give multiple ones. Plaintext password mechanisms now support handling multiple passdbs, but others don't yet. Also fixed a few memory leaks.
author Timo Sirainen <tss@iki.fi>
date Mon, 07 Mar 2005 20:55:13 +0200
parents 818c75139ac7
children c59506c04088
files dovecot-example.conf src/auth/auth-master-connection.c src/auth/auth-request.c src/auth/auth-request.h src/auth/auth-worker-client.c src/auth/auth.c src/auth/auth.h src/auth/main.c src/auth/passdb-blocking.c src/auth/passdb.c src/auth/passdb.h src/auth/userdb-blocking.c src/auth/userdb-blocking.h src/auth/userdb.c src/auth/userdb.h src/master/auth-process.c src/master/master-settings.c src/master/master-settings.h
diffstat 18 files changed, 537 insertions(+), 188 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Mon Mar 07 11:42:54 2005 +0200
+++ b/dovecot-example.conf	Mon Mar 07 20:55:13 2005 +0200
@@ -484,12 +484,6 @@
 ## Authentication processes
 ##
 
-# You can have multiple authentication processes. With plaintext authentication
-# the password is checked against each process, the first one which succeeds is
-# used. This is useful if you want to allow both system users (/etc/passwd)
-# and virtual users to login without duplicating the system users into virtual
-# database.
-
 # Executable location
 #auth_executable = /usr/libexec/dovecot/dovecot-auth
 
@@ -545,29 +539,111 @@
   #   plain digest-md5 cram-md5 apop anonymous
   mechanisms = plain
 
-  # Password database specifies only the passwords for users.
+  #
+  # Password database is used to verify user's password (and nothing more).
+  # You can have multiple passdbs and userdbs. This is useful if you want to
+  # allow both system users (/etc/passwd) and virtual users to login without
+  # duplicating the system users into virtual database.
+  #
   # http://wiki.dovecot.org/Authentication
-  #   passwd: /etc/passwd or similiar, using getpwnam()
-  #   shadow: /etc/shadow or similiar, using getspnam()
-  #   pam [<service> | *]: PAM authentication
-  #   checkpassword <path>: checkpassword executable authentication
-  #   passwd-file <path>: passwd-like file with specified location
-  #   vpopmail: vpopmail authentication
-  #   ldap <config path>: LDAP, see doc/dovecot-ldap.conf
-  #   sql <config path>: SQL database, see doc/dovecot-sql.conf
-  passdb = pam
+  #
+
+  # PAM authentication. Preferred nowadays by most systems.
+  # Note that PAM can only be used to verify if user's password is correct,
+  # so it can't be used as userdb. If you don't want to use a separate user
+  # database (passwd usually), you can use static userdb.
+  passdb pam {
+    # Service name or * as parameter. * means the authenticating service name
+    # is used, eg. pop3 or imap.
+    #args = dovecot
+  }
+
+  # /etc/passwd or similar, using getpwnam()
+  # In many systems nowadays this uses Name Service Switch, which is
+  # configured in /etc/nsswitch.conf.
+  #passdb passwd {
+  #}
+
+  # /etc/shadow or similiar, using getspnam(). Deprecated by PAM nowadays.
+  #passdb shadow {
+  #}
 
+  # passwd-like file with specified location
+  #passdb passwd-file {
+    # Path for passwd-file
+    #args = 
+  #}
+
+  # checkpassword executable authentication
+  #passdb checkpassword {
+    # Path for checkpassword binary
+    #args = 
+  #}
+
+  # SQL database
+  #passdb sql {
+    # Path for SQL configuration file, see doc/dovecot-sql.conf for example
+    #args = 
+  #}
+
+  # LDAP database
+  #passdb ldap {
+    # Path for LDAP configuration file, see doc/dovecot-ldap.conf for example
+    #args = 
+  #}
+
+  # vpopmail authentication
+  #passdb vpopmail {
+  #}
+
+  #
   # User database specifies where mails are located and what user/group IDs
   # own them. For single-UID configuration use "static".
+  #
   # http://wiki.dovecot.org/Authentication
   # http://wiki.dovecot.org/VirtualUsers
-  #   passwd: /etc/passwd or similiar, using getpwnam()
-  #   passwd-file <path>: passwd-like file with specified location
-  #   static uid=<uid> gid=<gid> home=<dir template>: static settings
-  #   vpopmail: vpopmail library
-  #   ldap <config path>: LDAP, see doc/dovecot-ldap.conf
-  #   sql <config path>: SQL database, see doc/dovecot-sql.conf
-  userdb = passwd
+  #
+
+  # /etc/passwd or similar, using getpwnam()
+  # In many systems nowadays this uses Name Service Switch, which is
+  # configured in /etc/nsswitch.conf.
+  userdb passwd {
+  }
+
+  # passwd-like file with specified location
+  #userdb passwd-file {
+    # Path for passwd-file
+    #args =
+  #}
+
+  # static settings generated from template
+  #userdb static {
+    # Template for settings. Can return anything a userdb could normally
+    # return, eg.: uid, gid, home, mail, nice
+    #
+    # A few examples:
+    #
+    #  args = uid=500 gid=500 home=/var/mail/%u
+    #  args = uid=500 gid=500 home=/home/%u mail=mbox:%h/mail nice=10
+    #
+    #args =
+  #}
+
+  # SQL database
+  #userdb sql {
+    # Path for SQL configuration file, see doc/dovecot-sql.conf for example
+    #args = 
+  #}
+
+  # LDAP database
+  #userdb ldap {
+    # Path for LDAP configuration file, see doc/dovecot-ldap.conf for example
+    #args = 
+  #}
+
+  # vpopmail
+  #userdb vpopmail {
+  #}
 
   # User to use for the process. This user needs access to only user and
   # password databases, nothing else. Only shadow and pam authentication
@@ -579,6 +655,7 @@
 
   # Directory where to chroot the process. Most authentication backends don't
   # work if this is set, and there's no point chrooting if auth_user is root.
+  # Note that valid_chroot_dirs isn't needed to use this setting.
   #chroot = 
 
   # Number of authentication processes to create
@@ -588,31 +665,6 @@
   #ssl_require_client_cert = no
 }
 
-# PAM doesn't provide a way to get uid, gid or home directory. If you don't
-# want to use a separate user database (passwd usually), you can use static
-# userdb.
-
-#auth onlypam {
-#  mechanisms = plain
-#  userdb = static uid=500 gid=500 home=/var/mail/%u
-#  passdb = pam
-#  user = dovecot-auth
-#}
-
-#auth ldap {
-#  mechanisms = plain
-#  userdb = ldap /etc/dovecot-ldap.conf
-#  passdb = ldap /etc/dovecot-ldap.conf
-#  user = dovecot-auth
-#}
-
-#auth virtualfile {
-#  mechanisms = plain digest-md5
-#  userdb = passwd-file /etc/passwd.imap
-#  passdb = passwd-file /etc/passwd.imap
-#  user = dovecot-auth
-#}
-
 # It's possible to export the authentication interface to other programs,
 # for example SMTP server which supports talking to Dovecot. Client socket
 # handles the actual authentication - you give it a username and password
@@ -624,8 +676,10 @@
 # settings given inside the auth section
 #auth default_with_listener {
 #  mechanisms = plain
-#  passdb = passwd
-#  userdb = pam
+#  passdb passwd {
+#  }
+#  userdb pam {
+#  }
 #  socket listen {
 #    master {
 #      path = /var/run/dovecot/auth-master
--- a/src/auth/auth-master-connection.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/auth-master-connection.c	Mon Mar 07 20:55:13 2005 +0200
@@ -228,7 +228,10 @@
 		i_error("close(): %m");
 	conn->fd = -1;
 
-	o_stream_close(conn->output);
+	i_stream_unref(conn->input);
+	conn->input = NULL;
+
+	o_stream_unref(conn->output);
 	conn->output = NULL;
 
 	if (conn->io != NULL) {
--- a/src/auth/auth-request.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/auth-request.c	Mon Mar 07 20:55:13 2005 +0200
@@ -24,6 +24,8 @@
 
 	request = mech->auth_new();
 	request->state = AUTH_REQUEST_STATE_NEW;
+	request->passdb = auth->passdbs;
+	request->userdb = auth->userdbs;
 
 	request->refcount = 1;
 	request->created = ioloop_time;
@@ -107,7 +109,7 @@
 static void auth_request_save_cache(struct auth_request *request,
 				    enum passdb_result result)
 {
-	struct passdb_module *passdb = request->auth->passdb;
+	struct passdb_module *passdb = request->passdb->passdb;
 	string_t *str;
 
 	if (passdb_cache == NULL)
@@ -163,7 +165,7 @@
         auth_request_save_cache(request, result);
 
 	cache_key = passdb_cache == NULL ? NULL :
-		request->auth->passdb->cache_key;
+		request->passdb->passdb->cache_key;
 	if (result == PASSDB_RESULT_INTERNAL_FAILURE && cache_key != NULL) {
 		/* lookup failed. if we're looking here only because the
 		   request was expired in cache, fallback to using cached
@@ -190,6 +192,20 @@
 			    strlen(request->passdb_password));
 	}
 
+	if (result != PASSDB_RESULT_OK &&
+	    result != PASSDB_RESULT_INTERNAL_FAILURE &&
+	    request->passdb->next != NULL) {
+		/* try next passdb. */
+		if (request->extra_fields != NULL)
+			str_truncate(request->extra_fields, 0);
+
+                request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;
+		request->passdb = request->passdb->next;
+		auth_request_verify_plain(request, request->mech_password,
+			request->private_callback.verify_plain);
+		return;
+	}
+
         safe_memset(request->mech_password, 0, strlen(request->mech_password));
 
 	request->private_callback.verify_plain(result, request);
@@ -199,13 +215,14 @@
 			       const char *password,
 			       verify_plain_callback_t *callback)
 {
-	struct passdb_module *passdb = request->auth->passdb;
+	struct passdb_module *passdb = request->passdb->passdb;
 	enum passdb_result result;
 	const char *cache_key;
 
 	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
 
-	request->mech_password = p_strdup(request->pool, password);
+	if (request->mech_password == NULL)
+		request->mech_password = p_strdup(request->pool, password);
 	request->private_callback.verify_plain = callback;
 
 	cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
@@ -244,7 +261,7 @@
 	}
 
 	cache_key = passdb_cache == NULL ? NULL :
-		request->auth->passdb->cache_key;
+		request->passdb->passdb->cache_key;
 	if (result == PASSDB_RESULT_INTERNAL_FAILURE && cache_key != NULL) {
 		/* lookup failed. if we're looking here only because the
 		   request was expired in cache, fallback to using cached
@@ -269,7 +286,7 @@
 				     enum passdb_credentials credentials,
 				     lookup_credentials_callback_t *callback)
 {
-	struct passdb_module *passdb = request->auth->passdb;
+	struct passdb_module *passdb = request->passdb->passdb;
 	const char *cache_key, *result, *scheme;
 
 	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
@@ -299,15 +316,40 @@
 	}
 }
 
+void auth_request_userdb_callback(const char *result,
+				  struct auth_request *request)
+{
+	if (result == NULL && request->userdb->next != NULL) {
+		/* try next userdb. */
+		if (request->extra_fields != NULL)
+			str_truncate(request->extra_fields, 0);
+
+		request->userdb = request->userdb->next;
+		auth_request_lookup_user(request,
+					 request->private_callback.userdb);
+		return;
+	}
+
+	if (result == NULL && request->client_pid != 0) {
+		/* this was actual login attempt */
+		auth_request_log_error(request, "userdb",
+				       "user not found from userdb");
+	}
+
+        request->private_callback.userdb(result, request);
+}
+
 void auth_request_lookup_user(struct auth_request *request,
 			      userdb_callback_t *callback)
 {
-	struct userdb_module *userdb = request->auth->userdb;
+	struct userdb_module *userdb = request->userdb->userdb;
+
+	request->private_callback.userdb = callback;
 
 	if (userdb->blocking)
-		userdb_blocking_lookup(request, callback);
+		userdb_blocking_lookup(request);
 	else
-		userdb->lookup(request, callback);
+		userdb->lookup(request, auth_request_userdb_callback);
 }
 
 int auth_request_set_username(struct auth_request *request,
--- a/src/auth/auth-request.h	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/auth-request.h	Mon Mar 07 20:55:13 2005 +0200
@@ -28,6 +28,8 @@
 
 	struct mech_module *mech;
 	struct auth *auth;
+        struct auth_passdb *passdb;
+        struct auth_userdb *userdb;
 
 	unsigned int connect_uid;
 	unsigned int client_pid;
@@ -109,5 +111,7 @@
 void auth_request_lookup_credentials_callback(enum passdb_result result,
 					      const char *credentials,
 					      struct auth_request *request);
+void auth_request_userdb_callback(const char *result,
+				  struct auth_request *request);
 
 #endif
--- a/src/auth/auth-worker-client.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/auth-worker-client.c	Mon Mar 07 20:55:13 2005 +0200
@@ -54,6 +54,8 @@
 	auth_request->refcount = 1;
 	auth_request->created = ioloop_time;
 	auth_request->auth = client->auth;
+	auth_request->passdb = client->auth->passdbs;
+	auth_request->userdb = client->auth->userdbs;
 
 	client->refcount++;
 	auth_request->context = client;
@@ -115,6 +117,15 @@
 	/* verify plaintext password */
 	struct auth_request *auth_request;
 	const char *password;
+	unsigned int num;
+
+	num = atoi(t_strcut(args, '\t'));
+	args = strchr(args, '\t');
+	if (args == NULL) {
+		i_error("BUG: Auth worker server sent us invalid PASSV");
+		return;
+	}
+	args++;
 
 	password = t_strcut(args, '\t');
 	args = strchr(args, '\t');
@@ -124,8 +135,16 @@
 	auth_request->mech_password =
 		p_strdup(auth_request->pool, password);
 
-	client->auth->passdb->verify_plain(auth_request, password,
-					   verify_plain_callback);
+	for (; num > 0; num++) {
+		auth_request->passdb = auth_request->passdb->next;
+		if (auth_request->passdb == NULL) {
+			i_error("BUG: PASSV had invalid passdb num");
+			return;
+		}
+	}
+
+	auth_request->passdb->passdb->verify_plain(auth_request, password,
+						   verify_plain_callback);
 }
 
 static void
@@ -162,16 +181,35 @@
 	struct auth_request *auth_request;
 	const char *credentials_str;
         enum passdb_credentials credentials;
+	unsigned int num;
+
+	num = atoi(t_strcut(args, '\t'));
+	args = strchr(args, '\t');
+	if (args == NULL) {
+		i_error("BUG: Auth worker server sent us invalid PASSL");
+		return;
+	}
+	args++;
 
 	credentials_str = t_strcut(args, '\t');
 	args = strchr(args, '\t');
 	if (args != NULL) args++;
 
-        credentials = atoi(credentials_str);
+	credentials = atoi(credentials_str);
 
 	auth_request = worker_auth_request_new(client, id, args);
-	client->auth->passdb->lookup_credentials(auth_request, credentials,
-						 lookup_credentials_callback);
+
+	for (; num > 0; num++) {
+		auth_request->passdb = auth_request->passdb->next;
+		if (auth_request->passdb == NULL) {
+			i_error("BUG: PASSL had invalid passdb num");
+			return;
+		}
+	}
+
+	auth_request->passdb->passdb->
+		lookup_credentials(auth_request, credentials,
+				   lookup_credentials_callback);
 }
 
 static void
@@ -196,9 +234,24 @@
 {
 	/* lookup user */
 	struct auth_request *auth_request;
+	unsigned int num;
+
+	num = atoi(t_strcut(args, '\t'));
+	args = strchr(args, '\t');
+	if (args != NULL) args++;
 
 	auth_request = worker_auth_request_new(client, id, args);
-	client->auth->userdb->lookup(auth_request, lookup_user_callback);
+
+	for (; num > 0; num++) {
+		auth_request->userdb = auth_request->userdb->next;
+		if (auth_request->userdb == NULL) {
+			i_error("BUG: USER had invalid userdb num");
+			return;
+		}
+	}
+
+	auth_request->userdb->userdb->
+		lookup(auth_request, lookup_user_callback);
 }
 
 static int
--- a/src/auth/auth.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/auth.c	Mon Mar 07 20:55:13 2005 +0200
@@ -7,6 +7,7 @@
 #include "mech.h"
 #include "userdb.h"
 #include "passdb.h"
+#include "passdb-cache.h"
 #include "auth.h"
 #include "auth-request-handler.h"
 
@@ -15,23 +16,46 @@
 
 struct auth *auth_preinit(void)
 {
-        struct auth *auth;
-	const char *env;
+	struct auth *auth;
+	const char *driver, *args;
+	pool_t pool;
+	unsigned int i;
 
-	auth = i_new(struct auth, 1);
+	pool = pool_alloconly_create("auth", 1024);
+	auth = p_new(pool, struct auth, 1);
+	auth->pool = pool;
 
 	auth->verbose = getenv("VERBOSE") != NULL;
 	auth->verbose_debug = getenv("VERBOSE_DEBUG") != NULL;
 
-	env = getenv("PASSDB");
-	if (env == NULL)
-		i_fatal("PASSDB environment is unset");
-	passdb_preinit(auth, env);
+	t_push();
+	for (i = 1; ; i++) {
+		driver = getenv(t_strdup_printf("PASSDB_%u_DRIVER", i));
+		if (driver == NULL)
+			break;
+
+                args = getenv(t_strdup_printf("PASSDB_%u_ARGS", i));
+		passdb_preinit(auth, driver, args);
+
+	}
+	t_pop();
 
-	env = getenv("USERDB");
-	if (env == NULL)
-		i_fatal("USERDB environment is unset");
-	userdb_preinit(auth, env);
+	t_push();
+	for (i = 1; ; i++) {
+		driver = getenv(t_strdup_printf("USERDB_%u_DRIVER", i));
+		if (driver == NULL)
+			break;
+
+                args = getenv(t_strdup_printf("USERDB_%u_ARGS", i));
+		userdb_preinit(auth, driver, args);
+
+	}
+	t_pop();
+
+	if (auth->passdbs == NULL)
+		i_fatal("No password databases set");
+	if (auth->userdbs == NULL)
+		i_fatal("No user databases set");
 	return auth;
 }
 
@@ -51,7 +75,7 @@
 {
 	struct mech_module_list *list;
 
-	list = i_new(struct mech_module_list, 1);
+	list = p_new(auth->pool, struct mech_module_list, 1);
 	list->module = *mech;
 
 	str_printfa(auth->mech_handshake, "MECH\t%s", mech->mech_name);
@@ -75,35 +99,62 @@
 	auth->mech_modules = list;
 }
 
+static int auth_passdb_list_have_plain(struct auth *auth)
+{
+	struct auth_passdb *passdb;
+
+	for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) {
+		if (passdb->passdb->verify_plain != NULL)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static int auth_passdb_list_have_credentials(struct auth *auth)
+{
+	struct auth_passdb *passdb;
+
+	for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next) {
+		if (passdb->passdb->lookup_credentials != NULL)
+			return TRUE;
+	}
+	return FALSE;
+}
+
 static void auth_mech_list_verify_passdb(struct auth *auth)
 {
 	struct mech_module_list *list;
 
 	for (list = auth->mech_modules; list != NULL; list = list->next) {
 		if (list->module.passdb_need_plain &&
-		    auth->passdb->verify_plain == NULL)
+		    !auth_passdb_list_have_plain(auth))
 			break;
 		if (list->module.passdb_need_credentials &&
-		    auth->passdb->lookup_credentials == NULL)
+                    !auth_passdb_list_have_credentials(auth))
 			break;
 	}
 
 	if (list != NULL) {
-		i_fatal("Passdb %s doesn't support %s method",
-			auth->passdb->name, list->module.mech_name);
+		i_fatal("%s mechanism can't be supported with given passdbs",
+			list->module.mech_name);
 	}
 }
 
 void auth_init(struct auth *auth)
 {
+	struct auth_passdb *passdb;
+	struct auth_userdb *userdb;
 	struct mech_module *mech;
 	const char *const *mechanisms;
 	const char *env;
 
-	passdb_init(auth);
-	userdb_init(auth);
+	for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next)
+		passdb_init(passdb);
+	for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next)
+		userdb_init(userdb);
+	passdb_cache_init();
 
-	auth->mech_handshake = str_new(default_pool, 512);
+	auth->mech_handshake = str_new(auth->pool, 512);
 
 	auth->anonymous_username = getenv("ANONYMOUS_USERNAME");
 	if (auth->anonymous_username != NULL &&
@@ -170,8 +221,14 @@
 
 void auth_deinit(struct auth *auth)
 {
-	userdb_deinit(auth);
-	passdb_deinit(auth);
+	struct auth_passdb *passdb;
+	struct auth_userdb *userdb;
 
-	str_free(auth->mech_handshake);
+	passdb_cache_deinit();
+	for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next)
+		passdb_deinit(passdb);
+	for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next)
+		userdb_deinit(userdb);
+
+	pool_unref(auth->pool);
 }
--- a/src/auth/auth.h	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/auth.h	Mon Mar 07 20:55:13 2005 +0200
@@ -1,19 +1,38 @@
 #ifndef __AUTH_H
 #define __AUTH_H
 
+struct auth_passdb {
+	struct auth *auth;
+	struct auth_passdb *next;
+
+	unsigned int num;
+	const char *args;
+	struct passdb_module *passdb;
+#ifdef HAVE_MODULES
+	struct auth_module *module;
+#endif
+};
+
+struct auth_userdb {
+	struct auth *auth;
+	struct auth_userdb *next;
+
+	unsigned int num;
+	const char *args;
+	struct userdb_module *userdb;
+#ifdef HAVE_MODULES
+	struct auth_module *module;
+#endif
+};
+
 struct auth {
+	pool_t pool;
+
 	struct mech_module_list *mech_modules;
 	buffer_t *mech_handshake;
 
-	struct passdb_module *passdb;
-	struct userdb_module *userdb;
-
-#ifdef HAVE_MODULES
-	struct auth_module *passdb_module;
-	struct auth_module *userdb_module;
-#endif
-
-	char *passdb_args, *userdb_args;
+	struct auth_passdb *passdbs;
+	struct auth_userdb *userdbs;
 
 	const char *const *auth_realms;
 	const char *default_realm;
--- a/src/auth/main.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/main.c	Mon Mar 07 20:55:13 2005 +0200
@@ -271,6 +271,7 @@
 		for (i = 0; i < size; i++)
 			auth_master_connection_destroy(master[i]);
 	}
+	buffer_free(masters_buf);
 
 	auth_request_handler_deinit();
 	auth_deinit(auth);
--- a/src/auth/passdb-blocking.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/passdb-blocking.c	Mon Mar 07 20:55:13 2005 +0200
@@ -12,19 +12,35 @@
 static enum passdb_result
 check_failure(struct auth_request *request, const char **reply)
 {
+	enum passdb_result ret;
+	const char *p;
+
 	/* OK / FAIL */
 	if (strncmp(*reply, "OK\t", 3) == 0) {
 		*reply += 3;
 		return PASSDB_RESULT_OK;
 	}
 
-	/* FAIL \t result */
-	if (strncmp(*reply, "FAIL\t", 5) != 0) {
+	/* FAIL \t result \t password */
+	if (strncmp(*reply, "FAIL\t", 5) == 0) {
+		*reply += 5;
+		ret = atoi(t_strcut(*reply, '\t'));
+
+		p = strchr(*reply, '\t');
+		if (p == NULL)
+			*reply += strlen(*reply);
+		else
+			*reply = p + 1;
+		if (ret != PASSDB_RESULT_OK)
+			return ret;
+
+		auth_request_log_error(request, "blocking",
+			"Received invalid FAIL result from worker: %d", ret);
+		return PASSDB_RESULT_INTERNAL_FAILURE;
+	} else {
 		auth_request_log_error(request, "blocking",
 			"Received unknown reply from worker: %s", *reply);
 		return PASSDB_RESULT_INTERNAL_FAILURE;
-	} else {
-		return atoi(*reply + 5);
 	}
 }
 
@@ -90,7 +106,7 @@
 	i_assert(request->extra_fields == NULL);
 
 	str = t_str_new(64);
-	str_append(str, "PASSV\t");
+	str_printfa(str, "PASSV\t%u\t", request->passdb->num);
 	str_append(str, request->mech_password);
 	str_append_c(str, '\t');
 	auth_request_export(request, str);
@@ -123,7 +139,8 @@
 	i_assert(request->extra_fields == NULL);
 
 	str = t_str_new(64);
-	str_printfa(str, "PASSL\t%d\t", request->credentials);
+	str_printfa(str, "PASSL\t%u\t%d\t",
+		    request->passdb->num, request->credentials);
 	auth_request_export(request, str);
 
 	auth_worker_call(request, str_c(str), lookup_credentials_callback);
--- a/src/auth/passdb.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/passdb.c	Mon Mar 07 20:55:13 2005 +0200
@@ -5,7 +5,6 @@
 #include "password-scheme.h"
 #include "auth-worker-server.h"
 #include "passdb.h"
-#include "passdb-cache.h"
 
 #include <stdlib.h>
 
@@ -117,67 +116,65 @@
 	callback(PASSDB_RESULT_OK, password, auth_request);
 }
 
-void passdb_preinit(struct auth *auth, const char *data)
+void passdb_preinit(struct auth *auth, const char *driver, const char *args)
 {
 	struct passdb_module **p;
-	const char *name, *args;
-
-	args = strchr(data, ' ');
-	name = t_strcut(data, ' ');
+        struct auth_passdb *auth_passdb, **dest;
 
 	if (args == NULL) args = "";
-	while (*args == ' ' || *args == '\t')
-		args++;
 
-	auth->passdb_args = i_strdup(args);
+	auth_passdb = p_new(auth->pool, struct auth_passdb, 1);
+	auth_passdb->auth = auth;
+	auth_passdb->args = p_strdup(auth->pool, args);
+
+	for (dest = &auth->passdbs; *dest != NULL; dest = &(*dest)->next)
+		auth_passdb->num++;
+	*dest = auth_passdb;
 
 	for (p = passdbs; *p != NULL; p++) {
-		if (strcmp((*p)->name, name) == 0) {
-			auth->passdb = *p;
+		if (strcmp((*p)->name, driver) == 0) {
+			auth_passdb->passdb = *p;
 			break;
 		}
 	}
 	
 #ifdef HAVE_MODULES
-	auth->passdb_module = auth->passdb != NULL ? NULL :
-		auth_module_open(name);
-	if (auth->passdb_module != NULL) {
-		auth->passdb = auth_module_sym(auth->passdb_module,
-					       t_strconcat("passdb_", name,
-							   NULL));
+	if (auth_passdb->passdb == NULL)
+		auth_passdb->module = auth_module_open(driver);
+	if (auth_passdb->module != NULL) {
+		auth_passdb->passdb =
+			auth_module_sym(auth_passdb->module,
+					t_strconcat("passdb_", driver, NULL));
 	}
 #endif
 
-	if (auth->passdb == NULL)
-		i_fatal("Unknown passdb type '%s'", name);
+	if (auth_passdb->passdb == NULL)
+		i_fatal("Unknown passdb driver '%s'", driver);
 
-	if (auth->passdb->preinit != NULL)
-		auth->passdb->preinit(auth->passdb_args);
+	if (auth_passdb->passdb->preinit != NULL)
+		auth_passdb->passdb->preinit(auth_passdb->args);
 }
 
-void passdb_init(struct auth *auth)
+void passdb_init(struct auth_passdb *passdb)
 {
-	passdb_cache_init();
-	if (auth->passdb->init != NULL)
-		auth->passdb->init(auth->passdb_args);
+	if (passdb->passdb->init != NULL)
+		passdb->passdb->init(passdb->args);
 
-	i_assert(auth->passdb->default_pass_scheme != NULL ||
-		 auth->passdb->cache_key == NULL);
+	i_assert(passdb->passdb->default_pass_scheme != NULL ||
+		 passdb->passdb->cache_key == NULL);
 
-	if (auth->passdb->blocking && !worker) {
+	if (passdb->passdb->blocking && !worker) {
 		/* blocking passdb - we need an auth server */
 		auth_worker_server_init();
 	}
 }
 
-void passdb_deinit(struct auth *auth)
+void passdb_deinit(struct auth_passdb *passdb)
 {
-	if (auth->passdb->deinit != NULL)
-		auth->passdb->deinit();
+	if (passdb->passdb->deinit != NULL)
+		passdb->passdb->deinit();
 #ifdef HAVE_MODULES
-	if (auth->passdb_module != NULL)
-                auth_module_close(auth->passdb_module);
+	if (passdb->module != NULL)
+                auth_module_close(passdb->module);
 #endif
-	passdb_cache_deinit();
-	i_free(auth->passdb_args);
 }
--- a/src/auth/passdb.h	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/passdb.h	Mon Mar 07 20:55:13 2005 +0200
@@ -67,9 +67,9 @@
 			       lookup_credentials_callback_t *callback,
                                struct auth_request *auth_request);
 
-void passdb_preinit(struct auth *auth, const char *data);
-void passdb_init(struct auth *auth);
-void passdb_deinit(struct auth *auth);
+void passdb_preinit(struct auth *auth, const char *driver, const char *args);
+void passdb_init(struct auth_passdb *passdb);
+void passdb_deinit(struct auth_passdb *passdb);
 
 #include "auth-request.h"
 
--- a/src/auth/userdb-blocking.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/userdb-blocking.c	Mon Mar 07 20:55:13 2005 +0200
@@ -13,15 +13,12 @@
         request->private_callback.userdb(reply, request);
 }
 
-void userdb_blocking_lookup(struct auth_request *request,
-			    userdb_callback_t *callback)
+void userdb_blocking_lookup(struct auth_request *request)
 {
 	string_t *str;
 
-	request->private_callback.userdb = callback;
-
 	str = t_str_new(64);
-	str_append(str, "USER\t");
+	str_printfa(str, "USER\t%u\t", request->userdb->num);
 	auth_request_export(request, str);
 
 	auth_worker_call(request, str_c(str), user_callback);
--- a/src/auth/userdb-blocking.h	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/userdb-blocking.h	Mon Mar 07 20:55:13 2005 +0200
@@ -1,7 +1,6 @@
 #ifndef __USERDB_BLOCKING_H
 #define __USERDB_BLOCKING_H
 
-void userdb_blocking_lookup(struct auth_request *request,
-			    userdb_callback_t *callback);
+void userdb_blocking_lookup(struct auth_request *request);
 
 #endif
--- a/src/auth/userdb.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/userdb.c	Mon Mar 07 20:55:13 2005 +0200
@@ -77,56 +77,57 @@
 	return gr->gr_gid;
 }
 
-void userdb_preinit(struct auth *auth, const char *data)
+void userdb_preinit(struct auth *auth, const char *driver, const char *args)
 {
 	struct userdb_module **p;
-	const char *name, *args;
-
-	args = strchr(data, ' ');
-	name = t_strcut(data, ' ');
+        struct auth_userdb *auth_userdb, **dest;
 
 	if (args == NULL) args = "";
-	while (*args == ' ' || *args == '\t')
-		args++;
 
-	auth->userdb_args = i_strdup(args);
+	auth_userdb = p_new(auth->pool, struct auth_userdb, 1);
+	auth_userdb->auth = auth;
+	auth_userdb->args = p_strdup(auth->pool, args);
+
+	for (dest = &auth->userdbs; *dest != NULL; dest = &(*dest)->next)
+		auth_userdb->num++;
+	*dest = auth_userdb;
 
 	for (p = userdbs; *p != NULL; p++) {
-		if (strcmp((*p)->name, name) == 0) {
-			auth->userdb = *p;
+		if (strcmp((*p)->name, driver) == 0) {
+			auth_userdb->userdb = *p;
 			break;
 		}
 	}
+	
 #ifdef HAVE_MODULES
-	auth->userdb_module = auth->userdb != NULL ? NULL :
-		auth_module_open(name);
-	if (auth->userdb_module != NULL) {
-		auth->userdb = auth_module_sym(auth->userdb_module,
-					       t_strconcat("userdb_", name,
-							   NULL));
+	if (auth_userdb->userdb == NULL)
+		auth_userdb->module = auth_module_open(driver);
+	if (auth_userdb->module != NULL) {
+		auth_userdb->userdb =
+			auth_module_sym(auth_userdb->module,
+					t_strconcat("userdb_", driver, NULL));
 	}
 #endif
 
-	if (auth->userdb == NULL)
-		i_fatal("Unknown userdb type '%s'", name);
+	if (auth_userdb->userdb == NULL)
+		i_fatal("Unknown userdb driver '%s'", driver);
 
-	if (auth->userdb->preinit != NULL)
-		auth->userdb->preinit(args);
+	if (auth_userdb->userdb->preinit != NULL)
+		auth_userdb->userdb->preinit(auth_userdb->args);
 }
 
-void userdb_init(struct auth *auth)
+void userdb_init(struct auth_userdb *userdb)
 {
-	if (auth->userdb->init != NULL)
-		auth->userdb->init(auth->userdb_args);
+	if (userdb->userdb->init != NULL)
+		userdb->userdb->init(userdb->args);
 }
 
-void userdb_deinit(struct auth *auth)
+void userdb_deinit(struct auth_userdb *userdb)
 {
-	if (auth->userdb->deinit != NULL)
-		auth->userdb->deinit();
+	if (userdb->userdb->deinit != NULL)
+		userdb->userdb->deinit();
 #ifdef HAVE_MODULES
-	if (auth->userdb_module != NULL)
-                auth_module_close(auth->userdb_module);
+	if (userdb->module != NULL)
+                auth_module_close(userdb->module);
 #endif
-	i_free(auth->userdb_args);
 }
--- a/src/auth/userdb.h	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/auth/userdb.h	Mon Mar 07 20:55:13 2005 +0200
@@ -24,9 +24,9 @@
 uid_t userdb_parse_uid(struct auth_request *request, const char *str);
 gid_t userdb_parse_gid(struct auth_request *request, const char *str);
 
-void userdb_preinit(struct auth *auth, const char *data);
-void userdb_init(struct auth *auth);
-void userdb_deinit(struct auth *auth);
+void userdb_preinit(struct auth *auth, const char *driver, const char *args);
+void userdb_init(struct auth_userdb *passdb);
+void userdb_deinit(struct auth_userdb *passdb);
 
 #include "auth-request.h"
 
--- a/src/master/auth-process.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/master/auth-process.c	Mon Mar 07 20:55:13 2005 +0200
@@ -397,6 +397,8 @@
 static void auth_set_environment(struct auth_settings *set)
 {
 	struct auth_socket_settings *as;
+	struct auth_passdb_settings *ap;
+	struct auth_userdb_settings *au;
 	const char *str;
 	int i;
 
@@ -410,8 +412,6 @@
 	env_put(t_strconcat("MECHANISMS=", set->mechanisms, NULL));
 	env_put(t_strconcat("REALMS=", set->realms, NULL));
 	env_put(t_strconcat("DEFAULT_REALM=", set->default_realm, NULL));
-	env_put(t_strconcat("USERDB=", set->userdb, NULL));
-	env_put(t_strconcat("PASSDB=", set->passdb, NULL));
 	env_put(t_strconcat("USERNAME_CHARS=", set->username_chars, NULL));
 	env_put(t_strconcat("USERNAME_TRANSLATION=",
 			    set->username_translation, NULL));
@@ -420,6 +420,15 @@
 	env_put(t_strdup_printf("CACHE_SIZE=%u", set->cache_size));
 	env_put(t_strdup_printf("CACHE_TTL=%u", set->cache_ttl));
 
+	for (ap = set->passdbs, i = 1; ap != NULL; ap = ap->next, i++) {
+		env_put(t_strdup_printf("PASSDB_%u_DRIVER=%s", i, ap->driver));
+		env_put(t_strdup_printf("PASSDB_%u_ARGS=%s", i, ap->args));
+	}
+	for (au = set->userdbs, i = 1; au != NULL; au = au->next, i++) {
+		env_put(t_strdup_printf("USERDB_%u_DRIVER=%s", i, au->driver));
+		env_put(t_strdup_printf("USERDB_%u_ARGS=%s", i, au->args));
+	}
+
 	for (as = set->sockets, i = 1; as != NULL; as = as->next, i++) {
 		if (strcmp(as->type, "listen") != 0)
 			continue;
--- a/src/master/master-settings.c	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/master/master-settings.c	Mon Mar 07 20:55:13 2005 +0200
@@ -17,6 +17,8 @@
 	SETTINGS_TYPE_SERVER,
 	SETTINGS_TYPE_AUTH,
 	SETTINGS_TYPE_AUTH_SOCKET,
+	SETTINGS_TYPE_AUTH_PASSDB,
+	SETTINGS_TYPE_AUTH_USERDB,
         SETTINGS_TYPE_NAMESPACE,
 	SETTINGS_TYPE_SOCKET
 };
@@ -29,6 +31,8 @@
 	struct auth_settings *auth;
 	struct socket_settings *socket;
 	struct auth_socket_settings *auth_socket;
+	struct auth_passdb_settings *auth_passdb;
+	struct auth_userdb_settings *auth_userdb;
         struct namespace_settings *namespace;
 
 	int level;
@@ -139,8 +143,6 @@
 	DEF(SET_STR, mechanisms),
 	DEF(SET_STR, realms),
 	DEF(SET_STR, default_realm),
-	DEF(SET_STR, userdb),
-	DEF(SET_STR, passdb),
 	DEF(SET_INT, cache_size),
 	DEF(SET_INT, cache_ttl),
 	DEF(SET_STR, executable),
@@ -186,6 +188,28 @@
 
 #undef DEF
 #define DEF(type, name) \
+	{ type, #name, offsetof(struct auth_passdb_settings, name) }
+
+static struct setting_def auth_passdb_setting_defs[] = {
+	DEF(SET_STR, driver),
+	DEF(SET_STR, args),
+
+	{ 0, NULL, 0 }
+};
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct auth_userdb_settings, name) }
+
+static struct setting_def auth_userdb_setting_defs[] = {
+	DEF(SET_STR, driver),
+	DEF(SET_STR, args),
+
+	{ 0, NULL, 0 }
+};
+
+#undef DEF
+#define DEF(type, name) \
 	{ type, #name, offsetof(struct namespace_settings, name) }
 
 static struct setting_def namespace_setting_defs[] = {
@@ -315,8 +339,6 @@
 	MEMBER(mechanisms) "plain",
 	MEMBER(realms) NULL,
 	MEMBER(default_realm) NULL,
-	MEMBER(userdb) "passwd",
-	MEMBER(passdb) "pam",
 	MEMBER(cache_size) 0,
 	MEMBER(cache_ttl) 3600,
 	MEMBER(executable) PKG_LIBEXECDIR"/dovecot-auth",
@@ -337,6 +359,8 @@
 	/* .. */
 	MEMBER(uid) 0,
 	MEMBER(gid) 0,
+	MEMBER(passdbs) NULL,
+	MEMBER(userdbs) NULL,
 	MEMBER(sockets) NULL
 };
 
@@ -646,6 +670,42 @@
 	return auth_settings_new(server, name);
 }
 
+static struct auth_passdb_settings *
+auth_passdb_settings_new(struct auth_settings *auth, const char *type)
+{
+	struct auth_passdb_settings *as, **as_p;
+
+	as = p_new(settings_pool, struct auth_passdb_settings, 1);
+
+	as->parent = auth;
+	as->driver = str_lcase(p_strdup(settings_pool, type));
+
+	as_p = &auth->passdbs;
+	while (*as_p != NULL)
+		as_p = &(*as_p)->next;
+	*as_p = as;
+
+	return as;
+}
+
+static struct auth_userdb_settings *
+auth_userdb_settings_new(struct auth_settings *auth, const char *type)
+{
+	struct auth_userdb_settings *as, **as_p;
+
+	as = p_new(settings_pool, struct auth_userdb_settings, 1);
+
+	as->parent = auth;
+	as->driver = str_lcase(p_strdup(settings_pool, type));
+
+	as_p = &auth->userdbs;
+	while (*as_p != NULL)
+		as_p = &(*as_p)->next;
+	*as_p = as;
+
+	return as;
+}
+
 static struct auth_socket_settings *
 auth_socket_settings_new(struct auth_settings *auth, const char *type)
 {
@@ -775,6 +835,14 @@
 		return parse_setting_from_defs(settings_pool,
 					       auth_socket_setting_defs,
 					       ctx->auth_socket, key, value);
+	case SETTINGS_TYPE_AUTH_PASSDB:
+		return parse_setting_from_defs(settings_pool,
+					       auth_passdb_setting_defs,
+					       ctx->auth_passdb, key, value);
+	case SETTINGS_TYPE_AUTH_USERDB:
+		return parse_setting_from_defs(settings_pool,
+					       auth_userdb_setting_defs,
+					       ctx->auth_userdb, key, value);
 	case SETTINGS_TYPE_NAMESPACE:
 		return parse_setting_from_defs(settings_pool,
 					       namespace_setting_defs,
@@ -906,6 +974,18 @@
 		return ctx->auth_socket != NULL;
 	}
 
+	if (ctx->type == SETTINGS_TYPE_AUTH && strcmp(type, "passdb") == 0) {
+		ctx->type = SETTINGS_TYPE_AUTH_PASSDB;
+		ctx->auth_passdb = auth_passdb_settings_new(ctx->auth, name);
+		return TRUE;
+	}
+
+	if (ctx->type == SETTINGS_TYPE_AUTH && strcmp(type, "userdb") == 0) {
+		ctx->type = SETTINGS_TYPE_AUTH_USERDB;
+		ctx->auth_userdb = auth_userdb_settings_new(ctx->auth, name);
+		return TRUE;
+	}
+
 	if (ctx->type == SETTINGS_TYPE_AUTH_SOCKET) {
 		ctx->type = SETTINGS_TYPE_SOCKET;
 
--- a/src/master/master-settings.h	Mon Mar 07 11:42:54 2005 +0200
+++ b/src/master/master-settings.h	Mon Mar 07 20:55:13 2005 +0200
@@ -125,6 +125,22 @@
         struct socket_settings client;
 };
 
+struct auth_passdb_settings {
+	struct auth_settings *parent;
+	struct auth_passdb_settings *next;
+
+	const char *driver;
+	const char *args;
+};
+
+struct auth_userdb_settings {
+	struct auth_settings *parent;
+	struct auth_userdb_settings *next;
+
+	const char *driver;
+	const char *args;
+};
+
 struct auth_settings {
 	struct server_settings *parent;
 	struct auth_settings *next;
@@ -133,8 +149,6 @@
 	const char *mechanisms;
 	const char *realms;
 	const char *default_realm;
-	const char *userdb;
-	const char *passdb;
 	unsigned int cache_size;
 	unsigned int cache_ttl;
 	const char *executable;
@@ -154,6 +168,8 @@
 	/* .. */
 	uid_t uid;
 	gid_t gid;
+        struct auth_passdb_settings *passdbs;
+        struct auth_userdb_settings *userdbs;
 	struct auth_socket_settings *sockets;
 };