NOTE: There are two FIXMEs that should be looked at

diff -r eaba4cfeaa44 src/auth/auth-request.c
--- a/src/auth/auth-request.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/auth-request.c	Mon May 31 18:01:49 2010 +0100
@@ -2,7 +2,7 @@
 
 #include "common.h"
 #include "ioloop.h"
-#include "buffer.h"
+#include "array.h"
 #include "hash.h"
 #include "hex-binary.h"
 #include "str.h"
@@ -33,6 +33,7 @@
 	request->state = AUTH_REQUEST_STATE_NEW;
 	request->passdb = auth->passdbs;
 	request->userdb = auth->userdbs;
+	p_array_init(&request->passdb_passwords, request->pool, 4);
 
 	request->refcount = 1;
 	request->last_access = ioloop_time;
@@ -59,6 +60,7 @@
 	auth_request->auth = auth;
 	auth_request->passdb = auth->passdbs;
 	auth_request->userdb = auth->userdbs;
+	p_array_init(&auth_request->passdb_passwords, auth_request->pool, 4);
 
 	return auth_request;
 }
@@ -216,11 +218,44 @@
 	request->mech->auth_continue(request, data, data_size);
 }
 
+void auth_request_reset_passdb_lookup(struct auth_request *request)
+{
+	array_clear(&request->passdb_passwords);
+
+	if (request->extra_fields != NULL)
+		auth_stream_reply_reset(request->extra_fields);
+}
+
+static void auth_request_save_cache_passwords(struct auth_request *request,
+					      string_t *str)
+{
+	struct passdb_module *passdb = request->passdb->passdb;
+	char *const *passwords;
+	unsigned int i, count;
+
+	passwords = array_get(&request->passdb_passwords, &count);
+	for (i = 0; i < count; i++) {
+		if (*passwords[i] != '{') {
+			/* cached passwords must have a known scheme */
+			str_append_c(str, '{');
+			str_append(str, passdb->default_pass_scheme);
+			str_append_c(str, '}');
+		}
+		if (strchr(passwords[i], '\t') != NULL)
+			i_panic("%s: Password contains TAB", request->user);
+		if (strchr(passwords[i], '\n') != NULL)
+			i_panic("%s: Password contains LF", request->user);
+		str_append(str, passwords[i]);
+		str_append_c(str, '\t');
+	}
+}
+
 static void auth_request_save_cache(struct auth_request *request,
 				    enum passdb_result result)
 {
 	struct passdb_module *passdb = request->passdb->passdb;
 	const char *extra_fields;
+	char *password;
 	string_t *str;
 
 	switch (result) {
@@ -236,6 +271,7 @@
 		   return success. */
 		return;
 	case PASSDB_RESULT_INTERNAL_FAILURE:
+	case PASSDB_RESULT_LAST_FAILURE:
 		i_unreached();
 	}
 
@@ -257,36 +293,24 @@
 		return;
 	}
 
-	if (!request->no_password && request->passdb_password == NULL) {
+	if (!request->no_password &&
+	    array_count(&request->passdb_passwords) == 0) {
 		/* passdb didn't provide the correct password */
 		if (result != PASSDB_RESULT_OK ||
 		    request->mech_password == NULL)
 			return;
 
-		/* we can still cache valid password lookups though.
-		   strdup() it so that mech_password doesn't get
-		   cleared too early. */
-		request->passdb_password =
-			p_strconcat(request->pool, "{plain}",
-				    request->mech_password, NULL);
+		/* we're doing plaintext authentication, so we can still cache
+		   valid lookups using the user-given password. strdup() it so
+		   that mech_password doesn't get cleared too early. */
+		password = p_strconcat(request->pool, "{plain}",
+				       request->mech_password, NULL);
+		array_append(&request->passdb_passwords, &password, 1);
 	}
 
 	/* save all except the currently given password in cache */
 	str = t_str_new(256);
-	if (request->passdb_password != NULL) {
-		if (*request->passdb_password != '{') {
-			/* cached passwords must have a known scheme */
-			str_append_c(str, '{');
-			str_append(str, passdb->default_pass_scheme);
-			str_append_c(str, '}');
-		}
-		if (strchr(request->passdb_password, '\t') != NULL)
-			i_panic("%s: Password contains TAB", request->user);
-		if (strchr(request->passdb_password, '\n') != NULL)
-			i_panic("%s: Password contains LF", request->user);
-		str_append(str, request->passdb_password);
-	}
-
+	auth_request_save_cache_passwords(request, str);
 	if (extra_fields != NULL && *extra_fields != '\0') {
 		str_append_c(str, '\t');
 		str_append(str, extra_fields);
@@ -317,7 +341,7 @@
 	request->requested_login_user = NULL;
 
 	request->skip_password_check = TRUE;
-	request->passdb_password = NULL;
+	array_clear(&request->passdb_passwords);
 
 	if (!request->passdb->pass) {
 		/* skip the passdb lookup, we're authenticated now. */
@@ -334,10 +358,12 @@
 auth_request_handle_passdb_callback(enum passdb_result *result,
 				    struct auth_request *request)
 {
-	if (request->passdb_password != NULL) {
-		safe_memset(request->passdb_password, 0,
-			    strlen(request->passdb_password));
-	}
+	char *const *passwords;
+	unsigned int i, count;
+
+	passwords = array_get(&request->passdb_passwords, &count);
+	for (i = 0; i < count; i++)
+		safe_memset(passwords[i], 0, strlen(passwords[i]));
 
 	if (request->passdb->deny && *result != PASSDB_RESULT_USER_UNKNOWN) {
 		/* deny passdb. we can get through this step only if the
@@ -359,7 +385,7 @@
 				/* this wasn't the final passdb lookup,
 				   continue to next passdb */
 				request->passdb = request->passdb->next;
-				request->passdb_password = NULL;
+				array_clear(&request->passdb_passwords);
 				return FALSE;
 			}
 		}
@@ -374,7 +400,7 @@
 		   *result != PASSDB_RESULT_USER_DISABLED) {
 		/* try next passdb. */
                 request->passdb = request->passdb->next;
-		request->passdb_password = NULL;
+		auth_request_reset_passdb_lookup(request);
 
                 if (*result == PASSDB_RESULT_INTERNAL_FAILURE) {
 			/* remember that we have had an internal failure. at
@@ -382,9 +408,6 @@
 			   successfully login. */
 			request->passdb_internal_failure = TRUE;
 		}
-		if (request->extra_fields != NULL)
-			auth_stream_reply_reset(request->extra_fields);
-
 		return FALSE;
 	} else if (request->passdb_internal_failure) {
 		/* last passdb lookup returned internal failure. it may have
@@ -509,17 +532,23 @@
 	}
 }
 
-static void
+static bool
 auth_request_lookup_credentials_finish(enum passdb_result result,
 				       const unsigned char *credentials,
 				       size_t size,
 				       struct auth_request *request)
 {
 	if (!auth_request_handle_passdb_callback(&result, request)) {
+		if (result != PASSDB_RESULT_LAST_FAILURE) {
+			/* see if we can get more credentials */
+			return FALSE;
+		}
+
 		/* try next passdb */
 		auth_request_lookup_credentials(request,
 			request->credentials_scheme,
                 	request->private_callback.lookup_credentials);
+		return TRUE;
 	} else {
 		if (request->auth->verbose_debug_passwords &&
 		    result == PASSDB_RESULT_OK) {
@@ -527,45 +556,91 @@
 				"Credentials: %s",
 				binary_to_hex(credentials, size));
 		}
-		request->private_callback.
+		return request->private_callback.
 			lookup_credentials(result, credentials, size, request);
 	}
 }
 
-void auth_request_lookup_credentials_callback(enum passdb_result result,
+static bool
+auth_request_lookup_credentials_try_use_cache(struct auth_request *request,
+					      bool use_expired)
+{
+	ARRAY_TYPE(const_string) cache_passwords;
+	const char *const *passwords, *password, *scheme, *cache_key;
+	unsigned int i, count;
+	const unsigned char *credentials;
+	size_t size;
+	enum passdb_result result;
+
+	cache_key = passdb_cache == NULL ? NULL :
+		request->passdb->passdb->cache_key;
+	if (cache_key == NULL)
+		return FALSE;
+
+	if (!passdb_cache_lookup_credentials(request, cache_key,
+					     &cache_passwords,
+					     &result, use_expired))
+		return FALSE;
+
+	if (use_expired) {
+		auth_request_log_info(request, "passdb",
+				      "Fallbacking to expired data from cache");
+	}
+
+	if (result != PASSDB_RESULT_OK) {
+		/* negative cache hit */
+		return auth_request_lookup_credentials_finish(result, NULL, 0,
+							      request);
+	}
+
+	/* successful cache lookup with valid passwords */
+	result = PASSDB_RESULT_LAST_FAILURE;
+	passwords = array_get(&cache_passwords, &count);
+	for (i = 0; i < count; i++) {
+		password = passwords[i];
+		scheme = password_get_scheme(&password);
+		i_assert(scheme != NULL);
+
+		if (!passdb_get_credentials(request, password, scheme,
+					    &credentials, &size)) {
+			result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
+			continue;
+		}
+		if (auth_request_lookup_credentials_finish(PASSDB_RESULT_OK,
+							   credentials,
+							   size, request))
+			return TRUE;
+	}
+	return auth_request_lookup_credentials_finish(result, NULL, 0, request);
+}
+
+bool auth_request_lookup_credentials_callback(enum passdb_result result,
 					      const unsigned char *credentials,
 					      size_t size,
 					      struct auth_request *request)
 {
-	const char *cache_cred, *cache_scheme;
-
 	i_assert(request->state == AUTH_REQUEST_STATE_PASSDB);
 
 	request->state = AUTH_REQUEST_STATE_MECH_CONTINUE;
 
-	if (result != PASSDB_RESULT_INTERNAL_FAILURE)
+	if (result == PASSDB_RESULT_LAST_FAILURE) {
+		/* no more results */
+	} else if (result != PASSDB_RESULT_INTERNAL_FAILURE)
 		auth_request_save_cache(request, result);
 	else {
 		/* lookup failed. if we're looking here only because the
 		   request was expired in cache, fallback to using cached
 		   expired record. */
-		const char *cache_key = request->passdb->passdb->cache_key;
-
-		if (passdb_cache_lookup_credentials(request, cache_key,
-						    &cache_cred, &cache_scheme,
-						    &result, TRUE)) {
-			auth_request_log_info(request, "passdb",
-				"Fallbacking to expired data from cache");
-			passdb_handle_credentials(
-				result, cache_cred, cache_scheme,
-				auth_request_lookup_credentials_finish,
-				request);
-			return;
-		}
+		if (auth_request_lookup_credentials_try_use_cache(request, TRUE))
+			return TRUE;
 	}
 
-	auth_request_lookup_credentials_finish(result, credentials, size,
-					       request);
+	if (!auth_request_lookup_credentials_finish(result, credentials, size,
+						    request)) {
+		request->state = AUTH_REQUEST_STATE_PASSDB;
+		return FALSE;
+	}
+	return TRUE;
 }
 
 void auth_request_lookup_credentials(struct auth_request *request,
@@ -573,35 +648,23 @@
 				     lookup_credentials_callback_t *callback)
 {
 	struct passdb_module *passdb = request->passdb->passdb;
-	const char *cache_key, *cache_cred, *cache_scheme;
-	enum passdb_result result;
 
 	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
 
 	request->credentials_scheme = p_strdup(request->pool, scheme);
 	request->private_callback.lookup_credentials = callback;
 
-	cache_key = passdb_cache == NULL ? NULL : passdb->cache_key;
-	if (cache_key != NULL) {
-		if (passdb_cache_lookup_credentials(request, cache_key,
-						    &cache_cred, &cache_scheme,
-						    &result, FALSE)) {
-			passdb_handle_credentials(
-				result, cache_cred, cache_scheme,
-				auth_request_lookup_credentials_finish,
-				request);
-			return;
-		}
-	}
+	if (auth_request_lookup_credentials_try_use_cache(request, FALSE))
+		return;
 
 	request->state = AUTH_REQUEST_STATE_PASSDB;
-
 	if (passdb->iface.lookup_credentials == NULL) {
 		/* this passdb doesn't support credentials */
 		auth_request_log_debug(request, "password",
 			"passdb doesn't support credential lookups");
-		auth_request_lookup_credentials_callback(
+		bool ret = auth_request_lookup_credentials_callback(
 			PASSDB_RESULT_SCHEME_NOT_AVAILABLE, NULL, 0, request);
+		i_assert(ret);
 	} else if (passdb->blocking) {
 		passdb_blocking_lookup_credentials(request);
 	} else {
@@ -931,25 +994,20 @@
 auth_request_set_password(struct auth_request *request, const char *value,
 			  const char *default_scheme, bool noscheme)
 {
-	if (request->passdb_password != NULL) {
-		auth_request_log_error(request,
-			request->passdb->passdb->iface.name,
-			"Multiple password values not supported");
-		return;
-	}
+	char *password;
 
 	/* if the password starts with '{' it most likely contains
 	   also '}'. check it anyway to make sure, because we
 	   assert-crash later if it doesn't exist. this could happen
 	   if plaintext passwords are used. */
 	if (*value == '{' && !noscheme && strchr(value, '}') != NULL)
-		request->passdb_password = p_strdup(request->pool, value);
+		password = p_strdup(request->pool, value);
 	else {
 		i_assert(default_scheme != NULL);
-		request->passdb_password =
-			p_strdup_printf(request->pool, "{%s}%s",
-					default_scheme, value);
+		password = p_strdup_printf(request->pool, "{%s}%s",
+					   default_scheme, value);
 	}
+	array_append(&request->passdb_passwords, &password, 1);
 }
 
 static void auth_request_set_reply_field(struct auth_request *request,
@@ -1033,11 +1091,15 @@
 		request->no_failure_delay = TRUE;
 	} else if (strcmp(name, "nopassword") == 0) {
 		/* NULL password - anything goes */
-		const char *password = request->passdb_password;
+		char *const *passwords;
+		const char *password;
+		unsigned int count;
 
-		if (password != NULL) {
+		passwords = array_get(&request->passdb_passwords, &count);
+		if (count > 0) {
+			password = passwords[0];
 			(void)password_get_scheme(&password);
-			if (*password != '\0') {
+			if (*password != '\0' || count > 1) {
 				auth_request_log_error(request,
 					request->passdb->passdb->iface.name,
 					"nopassword set but password is "
@@ -1046,7 +1108,7 @@
 			}
 		}
 		request->no_password = TRUE;
-		request->passdb_password = NULL;
+		array_clear(&request->passdb_passwords);
 	} else if (strcmp(name, "allow_nets") == 0) {
 		auth_request_validate_networks(request, value);
 	} else if (strncmp(name, "userdb_", 7) == 0) {
diff -r eaba4cfeaa44 src/auth/auth-request.h
--- a/src/auth/auth-request.h	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/auth-request.h	Mon May 31 18:01:49 2010 +0100
@@ -43,7 +43,8 @@
 	/* realm for the request, may be specified by some auth mechanisms */
 	const char *realm;
 	char *mech_password; /* set if verify_plain() is called */
-	char *passdb_password; /* set after password lookup if successful */
+	/* list of all valid passwords. set after passdb lookup if successful */
+	ARRAY_TYPE(string) passdb_passwords;
         /* extra_fields are returned in authentication reply. Fields prefixed
            with "userdb_" are skipped. If prefetch userdb is used, it uses
            the "userdb_" prefixed fields. */
@@ -125,6 +126,7 @@
 			  const unsigned char *data, size_t data_size);
 void auth_request_continue(struct auth_request *request,
 			   const unsigned char *data, size_t data_size);
+void auth_request_reset_passdb_lookup(struct auth_request *request);
 
 void auth_request_verify_plain(struct auth_request *request,
 			       const char *password,
@@ -179,10 +181,11 @@
 
 void auth_request_verify_plain_callback(enum passdb_result result,
 					struct auth_request *request);
-void auth_request_lookup_credentials_callback(enum passdb_result result,
+bool auth_request_lookup_credentials_callback(enum passdb_result result,
 					      const unsigned char *credentials,
 					      size_t size,
-					      struct auth_request *request);
+					      struct auth_request *request)
+	ATTR_WARN_UNUSED_RESULT;
 void auth_request_set_credentials(struct auth_request *request,
 				  const char *scheme, const char *data,
 				  set_credentials_callback_t *callback);
diff -r eaba4cfeaa44 src/auth/auth-worker-client.c
--- a/src/auth/auth-worker-client.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/auth-worker-client.c	Mon May 31 18:01:49 2010 +0100
@@ -1,6 +1,7 @@
 /* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */
 
 #include "common.h"
+#include "array.h"
 #include "base64.h"
 #include "ioloop.h"
 #include "network.h"
@@ -78,6 +79,8 @@
 {
 	struct auth_worker_client *client = request->context;
 	struct auth_stream_reply *reply;
+	char *const *passwords;
+	unsigned int i, count;
 	string_t *str;
 
 	if (request->passdb_failure && result == PASSDB_RESULT_OK)
@@ -95,9 +98,10 @@
 	}
 	if (result != PASSDB_RESULT_INTERNAL_FAILURE) {
 		auth_stream_reply_add(reply, NULL, request->user);
-		auth_stream_reply_add(reply, NULL,
-				      request->passdb_password == NULL ? "" :
-				      request->passdb_password);
+		passwords = array_get(&request->passdb_passwords, &count);
+		for (i = 0; i < count; i++)
+			  auth_stream_reply_add(reply, NULL, passwords[i]);
+		auth_stream_reply_add(reply, NULL, "");
 		if (request->extra_fields != NULL) {
 			const char *fields =
 				auth_stream_reply_export(request->extra_fields);
@@ -172,7 +176,7 @@
 		verify_plain(auth_request, password, verify_plain_callback);
 }
 
-static void
+static bool
 lookup_credentials_callback(enum passdb_result result,
 			    const unsigned char *credentials, size_t size,
 			    struct auth_request *request)
@@ -218,6 +222,7 @@
 	auth_request_unref(&request);
 	auth_worker_client_check_throttle(client);
 	auth_worker_client_unref(&client);
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/auth.c
--- a/src/auth/auth.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/auth.c	Mon May 31 18:01:49 2010 +0100
@@ -211,15 +211,15 @@
 	const char *const *mechanisms;
 	const char *env;
 
+	/* caching is handled only by the main auth process */
+	if (!worker)
+		passdb_cache_init();
 	for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next)
 		passdb_init(passdb);
 	for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next)
 		passdb_init(passdb);
 	for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next)
 		userdb_init(userdb);
-	/* caching is handled only by the main auth process */
-	if (!worker)
-		passdb_cache_init();
 
 	auth->mech_handshake = str_new(auth->pool, 512);
 
diff -r eaba4cfeaa44 src/auth/db-sql.c
--- a/src/auth/db-sql.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/db-sql.c	Mon May 31 18:01:49 2010 +0100
@@ -22,6 +22,7 @@
 	DEF_STR(user_query),
  	DEF_STR(update_query),
 	DEF_STR(default_pass_scheme),
+	DEF_BOOL(allow_multiple_rows),
 
 	{ 0, NULL, 0 }
 };
@@ -32,7 +33,8 @@
 	MEMBER(password_query) "SELECT username, domain, password FROM users WHERE username = '%n' AND domain = '%d'",
 	MEMBER(user_query) "SELECT home, uid, gid FROM users WHERE username = '%n' AND domain = '%d'",
 	MEMBER(update_query) "UPDATE users SET password = '%w' WHERE username = '%n' AND domain = '%d'",
-	MEMBER(default_pass_scheme) "MD5"
+	MEMBER(default_pass_scheme) "MD5",
+	MEMBER(allow_multiple_rows) FALSE
 };
 
 static struct sql_connection *connections = NULL;
diff -r eaba4cfeaa44 src/auth/db-sql.h
--- a/src/auth/db-sql.h	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/db-sql.h	Mon May 31 18:01:49 2010 +0100
@@ -10,6 +10,8 @@
 	const char *user_query;
 	const char *update_query;
 	const char *default_pass_scheme;
+
+	bool allow_multiple_rows;
 };
 
 struct sql_connection {
diff -r eaba4cfeaa44 src/auth/mech-apop.c
--- a/src/auth/mech-apop.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/mech-apop.c	Mon May 31 18:01:49 2010 +0100
@@ -43,7 +43,7 @@
 	return memcmp(digest, request->response_digest, 16) == 0;
 }
 
-static void
+static bool
 apop_credentials_callback(enum passdb_result result,
 			  const unsigned char *credentials, size_t size,
 			  struct auth_request *auth_request)
@@ -53,10 +53,9 @@
 
 	switch (result) {
 	case PASSDB_RESULT_OK:
-		if (verify_credentials(request, credentials, size))
-			auth_request_success(auth_request, NULL, 0);
-		else
-			auth_request_fail(auth_request);
+		if (!verify_credentials(request, credentials, size))
+			return FALSE;
+		auth_request_success(auth_request, NULL, 0);
 		break;
 	case PASSDB_RESULT_INTERNAL_FAILURE:
 		auth_request_internal_failure(auth_request);
@@ -65,6 +64,7 @@
 		auth_request_fail(auth_request);
 		break;
 	}
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/mech-cram-md5.c
--- a/src/auth/mech-cram-md5.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/mech-cram-md5.c	Mon May 31 18:01:49 2010 +0100
@@ -105,7 +105,7 @@
 	return TRUE;
 }
 
-static void credentials_callback(enum passdb_result result,
+static bool credentials_callback(enum passdb_result result,
 				 const unsigned char *credentials, size_t size,
 				 struct auth_request *auth_request)
 {
@@ -114,10 +114,9 @@
 
 	switch (result) {
 	case PASSDB_RESULT_OK:
-		if (verify_credentials(request, credentials, size))
-			auth_request_success(auth_request, NULL, 0);
-		else
-			auth_request_fail(auth_request);
+		if (!verify_credentials(request, credentials, size))
+			return FALSE;
+		auth_request_success(auth_request, NULL, 0);
 		break;
 	case PASSDB_RESULT_INTERNAL_FAILURE:
 		auth_request_internal_failure(auth_request);
@@ -126,6 +125,7 @@
 		auth_request_fail(auth_request);
 		break;
 	}
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/mech-digest-md5.c
--- a/src/auth/mech-digest-md5.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/mech-digest-md5.c	Mon May 31 18:01:49 2010 +0100
@@ -492,7 +492,7 @@
 	return !failed;
 }
 
-static void credentials_callback(enum passdb_result result,
+static bool credentials_callback(enum passdb_result result,
 				 const unsigned char *credentials, size_t size,
 				 struct auth_request *auth_request)
 {
@@ -501,10 +501,8 @@
 
 	switch (result) {
 	case PASSDB_RESULT_OK:
-		if (!verify_credentials(request, credentials, size)) {
-			auth_request_fail(auth_request);
-			return;
-		}
+		if (!verify_credentials(request, credentials, size))
+			return FALSE;
 
 		request->authenticated = TRUE;
 		auth_request->callback(auth_request,
@@ -519,6 +517,7 @@
 		auth_request_fail(auth_request);
 		break;
 	}
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/mech-ntlm.c
--- a/src/auth/mech-ntlm.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/mech-ntlm.c	Mon May 31 18:01:49 2010 +0100
@@ -57,7 +57,7 @@
 	return memcmp(lm_response, client_response, LM_RESPONSE_SIZE) == 0;
 }
 
-static void
+static bool
 lm_credentials_callback(enum passdb_result result,
 			const unsigned char *credentials, size_t size,
 			struct auth_request *auth_request)
@@ -67,10 +67,9 @@
 
 	switch (result) {
 	case PASSDB_RESULT_OK:
-		if (lm_verify_credentials(request, credentials, size))
-			auth_request_success(auth_request, NULL, 0);
-		else
-			auth_request_fail(auth_request);
+		if (!lm_verify_credentials(request, credentials, size))
+			return FALSE;
+		auth_request_success(auth_request, NULL, 0);
 		break;
 	case PASSDB_RESULT_INTERNAL_FAILURE:
 		auth_request_internal_failure(auth_request);
@@ -79,6 +78,7 @@
 		auth_request_fail(auth_request);
 		break;
 	}
+	return TRUE;
 }
 
 static int
@@ -138,7 +138,7 @@
 	}
 }
 
-static void
+static bool
 ntlm_credentials_callback(enum passdb_result result,
 			  const unsigned char *credentials, size_t size,
 			  struct auth_request *auth_request)
@@ -152,16 +152,14 @@
 		ret = ntlm_verify_credentials(request, credentials, size);
 		if (ret > 0) {
 			auth_request_success(auth_request, NULL, 0);
-			return;
+			return TRUE;
 		}
-		if (ret < 0) {
-			auth_request_fail(auth_request);
-			return;
-		}
+		if (ret < 0)
+			return FALSE;
 		break;
 	case PASSDB_RESULT_INTERNAL_FAILURE:
 		auth_request_internal_failure(auth_request);
-		return;
+		return TRUE;
 	default:
 		break;
 	}
@@ -170,6 +168,7 @@
 	   try with LM credentials */
 	auth_request_lookup_credentials(auth_request, "LANMAN",
 					lm_credentials_callback);
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/mech-otp.c
--- a/src/auth/mech-otp.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/mech-otp.c	Mon May 31 18:01:49 2010 +0100
@@ -55,7 +55,7 @@
 			       answer, strlen(answer));
 }
 
-static void
+static bool
 skey_credentials_callback(enum passdb_result result,
 			  const unsigned char *credentials, size_t size,
 			  struct auth_request *auth_request)
@@ -71,9 +71,10 @@
 		auth_request_fail(auth_request);
 		break;
 	}
+	return TRUE;
 }
 
-static void
+static bool
 otp_credentials_callback(enum passdb_result result,
 			 const unsigned char *credentials, size_t size,
 			 struct auth_request *auth_request)
@@ -91,6 +92,7 @@
 						skey_credentials_callback);
 		break;
 	}
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/mech-rpa.c
--- a/src/auth/mech-rpa.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/mech-rpa.c	Mon May 31 18:01:49 2010 +0100
@@ -434,7 +434,7 @@
 	return memcmp(response, request->user_response, sizeof(response)) == 0;
 }
 
-static void
+static bool
 rpa_credentials_callback(enum passdb_result result,
 			 const unsigned char *credentials, size_t size,
 			 struct auth_request *auth_request)
@@ -447,14 +447,13 @@
 	switch (result) {
 	case PASSDB_RESULT_OK:
 		if (!verify_credentials(request, credentials, size))
-			auth_request_fail(auth_request);
-		else {
-			token4 = mech_rpa_build_token4(request, &token4_size);
-			auth_request->callback(auth_request,
-					       AUTH_CLIENT_RESULT_CONTINUE,
-					       token4, token4_size);
-			request->phase = 2;
-		}
+			return FALSE;
+
+		token4 = mech_rpa_build_token4(request, &token4_size);
+		auth_request->callback(auth_request,
+				       AUTH_CLIENT_RESULT_CONTINUE,
+				       token4, token4_size);
+		request->phase = 2;
 		break;
 	case PASSDB_RESULT_INTERNAL_FAILURE:
 		auth_request_internal_failure(auth_request);
@@ -463,6 +462,7 @@
 		auth_request_fail(auth_request);
 		break;
 	}
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/mech-skey.c
--- a/src/auth/mech-skey.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/mech-skey.c	Mon May 31 18:01:49 2010 +0100
@@ -61,7 +61,7 @@
 			       answer, strlen(answer));
 }
 
-static void
+static bool
 otp_credentials_callback(enum passdb_result result,
 			 const unsigned char *credentials, size_t size,
 			 struct auth_request *auth_request)
@@ -77,9 +77,10 @@
 		auth_request_fail(auth_request);
 		break;
 	}
+	return TRUE;
 }
 
-static void
+static bool
 skey_credentials_callback(enum passdb_result result,
 			  const unsigned char *credentials, size_t size,
 			  struct auth_request *auth_request)
@@ -97,6 +98,7 @@
 						otp_credentials_callback);
 		break;
 	}
+	return TRUE;
 }
 
 static void
diff -r eaba4cfeaa44 src/auth/passdb-blocking.c
--- a/src/auth/passdb-blocking.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb-blocking.c	Mon May 31 18:01:49 2010 +0100
@@ -1,6 +1,7 @@
 /* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */
 
 #include "common.h"
+#include "array.h"
 #include "str.h"
 #include "auth-worker-server.h"
 #include "password-scheme.h"
@@ -13,8 +14,12 @@
 auth_worker_reply_parse_args(struct auth_request *request,
 			     const char *const *args)
 {
-	if (**args != '\0')
-		request->passdb_password = p_strdup(request->pool, *args);
+	char *password;
+
+	for (; **args != '\0'; args++) {
+		password = p_strdup(request->pool, *args);
+		array_append(&request->passdb_passwords, &password, 1);
+	}
 	args++;
 
 	if (*args != NULL) {
@@ -33,14 +38,14 @@
 	args = t_strsplit(reply, "\t");
 
 	if (strcmp(*args, "OK") == 0 && args[1] != NULL && args[2] != NULL) {
-		/* OK \t user \t password [\t extra] */
+		/* OK \t user <\t password>* [\t extra] */
 		auth_request_set_field(request, "user", args[1], NULL);
 		auth_worker_reply_parse_args(request, args + 2);
 		return PASSDB_RESULT_OK;
 	}
 
 	if (strcmp(*args, "FAIL") == 0 && args[1] != NULL) {
-		/* FAIL \t result [\t user \t password [\t extra]] */
+		/* FAIL \t result [\t user <\t password>* [\t extra]] */
 		ret = atoi(args[1]);
 		if (ret == PASSDB_RESULT_OK) {
 			/* shouldn't happen */
@@ -88,23 +93,34 @@
 lookup_credentials_callback(struct auth_request *request, const char *reply)
 {
 	enum passdb_result result;
+	char *const *passwords;
+	unsigned int i, count;
 	const char *password = NULL, *scheme = NULL;
 
 	result = auth_worker_reply_parse(request, reply);
-	if (result == PASSDB_RESULT_OK && request->passdb_password != NULL) {
-		password = request->passdb_password;
+
+	passwords = array_get(&request->passdb_passwords, &count);
+	if (result != PASSDB_RESULT_OK)
+		count = 0;
+
+	for (i = 0; i < count; i++) {
+		password = passwords[i];
 		scheme = password_get_scheme(&password);
 		if (scheme == NULL) {
 			auth_request_log_error(request, "blocking",
 				"Received reply from worker without "
 				"password scheme");
 			result = PASSDB_RESULT_INTERNAL_FAILURE;
+			continue;
 		}
+		if (passdb_handle_credentials(PASSDB_RESULT_OK, password, scheme,
+				auth_request_lookup_credentials_callback,
+				request))
+			return;
 	}
-
-	passdb_handle_credentials(result, password, scheme,
-				  auth_request_lookup_credentials_callback,
-				  request);
+	passdb_handle_credentials_last(result, password, scheme,
+				       auth_request_lookup_credentials_callback,
+				       request);
 }
 
 void passdb_blocking_lookup_credentials(struct auth_request *request)
diff -r eaba4cfeaa44 src/auth/passdb-cache.c
--- a/src/auth/passdb-cache.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb-cache.c	Mon May 31 18:01:49 2010 +0100
@@ -1,6 +1,7 @@
 /* Copyright (c) 2004-2010 Dovecot authors, see the included COPYING file */
 
 #include "common.h"
+#include "array.h"
 #include "password-scheme.h"
 #include "passdb.h"
 #include "passdb-cache.h"
@@ -35,7 +36,7 @@
 	if (passdb_cache == NULL || key == NULL)
 		return FALSE;
 
-	/* value = password \t ... */
+	/* value = <password \t>* ... */
 	value = auth_cache_lookup(passdb_cache, request, key, &node,
 				  &expired, &neg_expired);
 	if (value == NULL || (expired && !use_expired)) {
@@ -54,26 +55,31 @@
 
 	list = t_strsplit(value, "\t");
 
-	cached_pw = list[0];
-	if (*cached_pw == '\0') {
+	if (*list[0] == '\0') {
 		/* NULL password */
 		auth_request_log_info(request, "cache", "NULL password access");
 		ret = 1;
 	} else {
-		scheme = password_get_scheme(&cached_pw);
-		i_assert(scheme != NULL);
+		/* try all cached passwords */
+		do {
+			cached_pw = list[0];
+			list++;
 
-		ret = auth_request_password_verify(request, password, cached_pw,
-						   scheme, "cache");
+			scheme = password_get_scheme(&cached_pw);
+			i_assert(scheme != NULL);
+			ret = auth_request_password_verify(request, password,
+							   cached_pw, scheme,
+							   "cache");
+		} while (ret <= 0 && *list[0] != '\0');
+	}
 
-		if (ret == 0 && (node->last_success || neg_expired)) {
-			/* a) the last authentication was successful. assume
-			   that the password was changed and cache is expired.
-			   b) negative TTL reached, use it for password
-			   mismatches too. */
-			node->last_success = FALSE;
-			return FALSE;
-		}
+	if (ret == 0 && (node->last_success || neg_expired)) {
+		/* a) the last authentication was successful. assume
+		   that the password was changed and cache is expired.
+		   b) negative TTL reached, use it for password
+		   mismatches too. */
+		node->last_success = FALSE;
+		return FALSE;
 	}
 	node->last_success = ret > 0;
 
@@ -87,13 +93,14 @@
 }
 
 bool passdb_cache_lookup_credentials(struct auth_request *request,
-				     const char *key, const char **password_r,
-				     const char **scheme_r,
+				     const char *key,
+				     ARRAY_TYPE(const_string) *passwords_r,
 				     enum passdb_result *result_r,
 				     bool use_expired)
 {
 	const char *value, *const *list;
 	struct auth_cache_node *node;
+	unsigned int i;
 	bool expired, neg_expired;
 
 	if (passdb_cache == NULL)
@@ -110,19 +117,25 @@
 
 	if (*value == '\0') {
 		/* negative cache entry */
+		t_array_init(passwords_r, 1);
 		*result_r = PASSDB_RESULT_USER_UNKNOWN;
-		*password_r = NULL;
-		*scheme_r = NULL;
 		return TRUE;
 	}
 
 	list = t_strsplit(value, "\t");
-	auth_request_set_fields(request, list + 1, NULL);
+
+	/* list begins with passwords and continues until an empty field */
+	for (i = 0; *list[i] != '\0'; i++) ;
+
+	t_array_init(passwords_r, i + 1);
+	for (i = 0; *list[i] != '\0'; i++)
+		array_append(passwords_r, &list[i], 1);
+
+	auth_request_set_fields(request, list + i + 1, NULL);
 
 	*result_r = PASSDB_RESULT_OK;
-	*password_r = *list[0] == '\0' ? NULL : list[0];
-	*scheme_r = password_get_scheme(password_r);
-	i_assert(*scheme_r != NULL || *password_r == NULL);
+	/*FIXME: if (i == 0)
+ 		*result_r = PASSDB_RESULT_SCHEME_NOT_AVAILABLE; */
 	return TRUE;
 }
 
diff -r eaba4cfeaa44 src/auth/passdb-cache.h
--- a/src/auth/passdb-cache.h	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb-cache.h	Mon May 31 18:01:49 2010 +0100
@@ -9,8 +9,8 @@
 			       const char *password,
 			       enum passdb_result *result_r, int use_expired);
 bool passdb_cache_lookup_credentials(struct auth_request *request,
-				     const char *key, const char **password_r,
-				     const char **scheme_r,
+				     const char *key,
+				     ARRAY_TYPE(const_string) *passwords_r,
 				     enum passdb_result *result_r,
 				     bool use_expired);
 
diff -r eaba4cfeaa44 src/auth/passdb-ldap.c
--- a/src/auth/passdb-ldap.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb-ldap.c	Mon May 31 18:01:49 2010 +0100
@@ -5,6 +5,7 @@
 
 #if defined(PASSDB_LDAP) && (defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD))
 
+#include "array.h"
 #include "ioloop.h"
 #include "hash.h"
 #include "str.h"
@@ -88,6 +89,46 @@
 	}
 }
 
+static bool
+ldap_check_password(struct passdb_ldap_request *ldap_request,
+		    const char *password)
+{
+	struct auth_request *auth_request =
+		ldap_request->request.ldap.auth_request;
+	const char *scheme;
+
+	/* passdb_passwords may be cleared on the way,
+	   so we'll need to strdup. */
+	password = t_strdup(password);
+
+	scheme = password_get_scheme(&password);
+	/* auth_request_set_field() sets scheme */
+	i_assert(password == NULL || scheme != NULL);
+
+	if (auth_request->credentials_scheme != NULL) {
+		lookup_credentials_callback_t *callback =
+			ldap_request->callback.lookup_credentials;
+
+		if (passdb_handle_credentials(PASSDB_RESULT_OK, password,
+					      scheme, callback, auth_request))
+			return TRUE;
+	} else if (password != NULL) {
+		if (auth_request_password_verify(auth_request,
+						 auth_request->mech_password,
+						 password, scheme, "ldap") > 0)
+			return TRUE;
+	} else {
+		if (auth_request->no_password) {
+			/* all passwords are valid */
+			return TRUE;
+		}
+
+		auth_request_log_info(auth_request, "ldap",
+			"Empty password returned without nopassword");
+	}
+	return FALSE;
+}
+
 static void
 ldap_lookup_pass_callback(struct ldap_connection *conn,
 			  struct ldap_request *request, LDAPMessage *res)
@@ -97,8 +138,8 @@
         struct auth_request *auth_request = request->auth_request;
 	enum passdb_result passdb_result;
 	LDAPMessage *entry;
-	const char *password, *scheme;
-	int ret;
+	char *const *passwords;
+	unsigned int i, count;
 
 	entry = handle_request_get_entry(conn, auth_request, ldap_request, res);
 	if (entry == NULL)
@@ -106,41 +147,30 @@
 
 	/* got first LDAP entry */
 	passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
-	password = NULL;
 
 	ldap_query_save_result(conn, entry, auth_request);
+	passwords = array_get(&auth_request->passdb_passwords, &count);
 	if (ldap_next_entry(conn->ld, entry) != NULL) {
 		auth_request_log_error(auth_request, "ldap",
 			"pass_filter matched multiple objects, aborting");
-	} else if (auth_request->passdb_password == NULL &&
-		   !auth_request->no_password) {
+	} else if (count == 0 && !auth_request->no_password) {
 		auth_request_log_info(auth_request, "ldap",
 			"No password returned (and no nopassword)");
 		passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
 	} else {
-		/* passdb_password may change on the way,
-		   so we'll need to strdup. */
-		password = t_strdup(auth_request->passdb_password);
 		passdb_result = PASSDB_RESULT_OK;
+		for (i = 0; i < count; i++) {
+			if (ldap_check_password(ldap_request, passwords[i]))
+				return;
+		}
+		passdb_result = PASSDB_RESULT_LAST_FAILURE;
 	}
 
-	scheme = password_get_scheme(&password);
-	/* auth_request_set_field() sets scheme */
-	i_assert(password == NULL || scheme != NULL);
-
 	if (auth_request->credentials_scheme != NULL) {
-		passdb_handle_credentials(passdb_result, password, scheme,
+		passdb_handle_credentials_last(passdb_result, NULL, NULL,
 			ldap_request->callback.lookup_credentials,
 			auth_request);
 	} else {
-		if (password != NULL) {
-			ret = auth_request_password_verify(auth_request,
-					auth_request->mech_password,
-					password, scheme, "ldap");
-			passdb_result = ret > 0 ? PASSDB_RESULT_OK :
-				PASSDB_RESULT_PASSWORD_MISMATCH;
-		}
-
 		ldap_request->callback.verify_plain(passdb_result,
 						    auth_request);
 	}
diff -r eaba4cfeaa44 src/auth/passdb-passwd-file.c
--- a/src/auth/passdb-passwd-file.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb-passwd-file.c	Mon May 31 18:01:49 2010 +0100
@@ -102,9 +102,8 @@
 	}
 
 	passwd_file_save_results(request, pu, &crypted_pass, &scheme);
-
-	passdb_handle_credentials(PASSDB_RESULT_OK, crypted_pass, scheme,
-				  callback, request);
+	passdb_handle_credentials_last(PASSDB_RESULT_OK, crypted_pass, scheme,
+				       callback, request);
 }
 
 static struct passdb_module *
diff -r eaba4cfeaa44 src/auth/passdb-sql.c
--- a/src/auth/passdb-sql.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb-sql.c	Mon May 31 18:01:49 2010 +0100
@@ -5,12 +5,14 @@
 
 #ifdef PASSDB_SQL
 
+#include "array.h"
 #include "str.h"
 #include "strescape.h"
 #include "var-expand.h"
 #include "safe-memset.h"
 #include "password-scheme.h"
 #include "auth-cache.h"
+#include "passdb-cache.h"
 #include "db-sql.h"
 
 #include <stdlib.h>
@@ -52,79 +54,145 @@
 	}
 }
 
-static void sql_query_callback(struct sql_result *result,
-			       struct passdb_sql_request *sql_request)
+static int sql_query_next_row(struct sql_result *result,
+			      struct passdb_sql_request *sql_request)
 {
 	struct auth_request *auth_request = sql_request->auth_request;
-	enum passdb_result passdb_result;
-	const char *user, *password, *scheme;
+	struct passdb_module *_module = auth_request->passdb->passdb;
+	struct sql_passdb_module *module = (struct sql_passdb_module *)_module;
 	int ret;
 
-	passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
-	user = auth_request->user;
-	password = NULL;
+	ret = sql_result_next_row(result);
+	if (ret <= 0) {
+		if (ret == 0)
+			return 0;
 
-	ret = sql_result_next_row(result);
-	if (ret < 0) {
 		auth_request_log_error(auth_request, "sql",
 				       "Password query failed: %s",
 				       sql_result_get_error(result));
-	} else if (ret == 0) {
-		auth_request_log_info(auth_request, "sql", "unknown user");
-		passdb_result = PASSDB_RESULT_USER_UNKNOWN;
-	} else {
-		sql_query_save_results(result, sql_request);
+		return -1;
+	}
 
-		/* Note that we really want to check if the password field is
-		   found. Just checking if password is set isn't enough,
-		   because with proxies we might want to return NULL as
-		   password. */
-		if (sql_result_find_field(result, "password") < 0) {
+	sql_query_save_results(result, sql_request);
+
+	/* Note that we really want to check if the password field is
+	   found. Just checking if password is set isn't enough,
+	   because with proxies we might want to return NULL as
+	   password. */
+	if (sql_result_find_field(result, "password") < 0) {
+		auth_request_log_error(auth_request, "sql",
+			"Password query must return a field named 'password'");
+		return -1;
+	}
+
+	if (!module->conn->set.allow_multiple_rows) {
+		if (sql_result_next_row(result) > 0) {
 			auth_request_log_error(auth_request, "sql",
-				"Password query must return a field named "
-				"'password'");
-		} else if (sql_result_next_row(result) > 0) {
-			auth_request_log_error(auth_request, "sql",
-				"Password query returned multiple matches");
-		} else if (auth_request->passdb_password == NULL &&
-			   !auth_request->no_password) {
-			auth_request_log_info(auth_request, "sql",
-				"Empty password returned without nopassword");
-			passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
-		} else {
-			/* passdb_password may change on the way,
-			   so we'll need to strdup. */
-			password = t_strdup(auth_request->passdb_password);
-			passdb_result = PASSDB_RESULT_OK;
+				"Password query returned multiple matches "
+				"and allow_multiple_rows=no");
+			return -1;
 		}
 	}
+	return 1;
+}
+
+static bool
+sql_check_password(struct passdb_sql_request *sql_request, const char *password,
+		   enum passdb_result passdb_result)
+{
+	struct auth_request *auth_request = sql_request->auth_request;
+	const char *scheme;
 
 	scheme = password_get_scheme(&password);
 	/* auth_request_set_field() sets scheme */
 	i_assert(password == NULL || scheme != NULL);
 
 	if (auth_request->credentials_scheme != NULL) {
-		passdb_handle_credentials(passdb_result, password, scheme,
-			sql_request->callback.lookup_credentials,
-			auth_request);
-		auth_request_unref(&auth_request);
-		return;
+		lookup_credentials_callback_t *callback =
+			sql_request->callback.lookup_credentials;
+
+		if (passdb_handle_credentials(passdb_result, password,
+					      scheme, callback, auth_request))
+			return TRUE;
+	} else if (password != NULL) {
+		if (auth_request_password_verify(auth_request,
+						 auth_request->mech_password,
+						 password, scheme, "sql") > 0)
+			return TRUE;
+	} else {
+		if (auth_request->no_password) {
+			/* all passwords are valid */
+			return TRUE;
+		}
+
+		auth_request_log_info(auth_request, "sql",
+			"Empty password returned without nopassword");
+	}
+	return FALSE;
+}
+
+static void sql_query_callback(struct sql_result *result,
+			       struct passdb_sql_request *sql_request)
+{
+	struct auth_request *auth_request = sql_request->auth_request;
+	enum passdb_result passdb_result;
+	const char *user, *password;
+	char *const *passwords;
+	unsigned int count, row;
+	int ret;
+	bool send_last = FALSE;
+
+	passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+
+	user = auth_request->user;
+	for (row = 0;; row++) {
+		ret = sql_query_next_row(result, sql_request);
+		if (ret == 0 && row > 0) {
+			/* no more rows. */
+			send_last = TRUE;
+			passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
+			break;
+		}
+
+		switch (ret) {
+		case 1:
+			/* passdb_passwords may be cleared on the way,
+			   so we'll need to strdup. */
+			passwords = array_get(&auth_request->passdb_passwords,
+					      &count);
+			i_assert(count <= 1);
+			password = count == 0 ? NULL : t_strdup(passwords[0]);
+			passdb_result = PASSDB_RESULT_OK;
+			break;
+		case 0:
+			auth_request_log_info(auth_request, "sql",
+					      "unknown user");
+			passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+			password = NULL;
+			break;
+		default:
+			passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+			password = NULL;
+			break;
+		}
+
+		if (sql_check_password(sql_request, password, passdb_result))
+			break;
+		/* see if there's another row */
+		auth_request_reset_passdb_lookup(auth_request);
 	}
 
-	/* verify plain */
-	if (password == NULL) {
+	if (auth_request->credentials_scheme == NULL)
 		sql_request->callback.verify_plain(passdb_result, auth_request);
-		auth_request_unref(&auth_request);
-		return;
+	else if (send_last) {
+		lookup_credentials_callback_t *callback =
+			sql_request->callback.lookup_credentials;
+
+		if (!passdb_handle_credentials(PASSDB_RESULT_LAST_FAILURE,
+					       NULL, NULL, callback,
+					       auth_request))
+			i_unreached();
 	}
-
-	ret = auth_request_password_verify(auth_request,
-					   auth_request->mech_password,
-					   password, scheme, "sql");
-
-	sql_request->callback.verify_plain(ret > 0 ? PASSDB_RESULT_OK :
-					   PASSDB_RESULT_PASSWORD_MISMATCH,
-					   auth_request);
 	auth_request_unref(&auth_request);
 }
 
@@ -250,6 +318,11 @@
 	flags = sql_get_flags(module->conn->db);
 	module->module.blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0;
 
+	if (module->conn->set.allow_multiple_rows && passdb_cache != NULL) {
+		i_fatal("sql: allow_multiple_rows=yes "
+			"is incompatible with auth cache");
+	}
+
 	if (!module->module.blocking || worker)
                 sql_connect(module->conn->db);
 }
diff -r eaba4cfeaa44 src/auth/passdb-vpopmail.c
--- a/src/auth/passdb-vpopmail.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb-vpopmail.c	Mon May 31 18:01:49 2010 +0100
@@ -102,8 +102,8 @@
 		return;
 	}
 	
-	passdb_handle_credentials(PASSDB_RESULT_OK, password, "CLEARTEXT",
-				  callback, request);
+	passdb_handle_credentials_last(PASSDB_RESULT_OK, password, "CLEARTEXT",
+				       callback, request);
 	safe_memset(password, 0, strlen(password));
 }
 
diff -r eaba4cfeaa44 src/auth/passdb.c
--- a/src/auth/passdb.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb.c	Mon May 31 18:01:49 2010 +0100
@@ -128,7 +128,7 @@
 	return TRUE;
 }
 
-void passdb_handle_credentials(enum passdb_result result,
+bool passdb_handle_credentials(enum passdb_result result,
 			       const char *password, const char *scheme,
 			       lookup_credentials_callback_t *callback,
                                struct auth_request *auth_request)
@@ -136,12 +136,11 @@
 	const unsigned char *credentials;
 	size_t size = 0;
 
-	if (result != PASSDB_RESULT_OK) {
-		callback(result, NULL, 0, auth_request);
-		return;
-	}
+	if (result != PASSDB_RESULT_OK)
+		return callback(result, NULL, 0, auth_request);
 
 	if (password == NULL) {
+		/* FIXME: do we get here anymore (with cache)? */
 		auth_request_log_info(auth_request, "password",
 			"Requested %s scheme, but we have a NULL password",
 			auth_request->credentials_scheme);
@@ -151,7 +150,21 @@
 		result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;
 	}
 
-	callback(result, credentials, size, auth_request);
+	return callback(result, credentials, size, auth_request);
+}
+
+void passdb_handle_credentials_last(enum passdb_result result,
+				    const char *password, const char *scheme,
+				    lookup_credentials_callback_t *callback,
+				    struct auth_request *auth_request)
+{
+	if (!passdb_handle_credentials(result, password, scheme,
+				       callback, auth_request)) {
+		if (!passdb_handle_credentials(PASSDB_RESULT_LAST_FAILURE,
+					       NULL, NULL, callback,
+					       auth_request))
+			i_unreached();
+	}
 }
 
 struct auth_passdb *passdb_preinit(struct auth *auth, const char *driver,
diff -r eaba4cfeaa44 src/auth/passdb.h
--- a/src/auth/passdb.h	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/passdb.h	Mon May 31 18:01:49 2010 +0100
@@ -13,6 +13,7 @@
 	PASSDB_RESULT_USER_UNKNOWN = -3,
 	PASSDB_RESULT_USER_DISABLED = -4,
 	PASSDB_RESULT_PASS_EXPIRED = -5,
+	PASSDB_RESULT_LAST_FAILURE = -6,
 
 	PASSDB_RESULT_PASSWORD_MISMATCH = 0,
 	PASSDB_RESULT_OK = 1
@@ -20,7 +21,11 @@
 
 typedef void verify_plain_callback_t(enum passdb_result result,
 				     struct auth_request *request);
-typedef void lookup_credentials_callback_t(enum passdb_result result,
+/* Returns TRUE if successful, FALSE if more credentials are wanted
+   (ie. support for multiple passwords). If FALSE is returned, the caller must
+   call this function again. If there are no more results,
+   result=PASSDB_RESULT_END_OF_LIST */
+typedef bool lookup_credentials_callback_t(enum passdb_result result,
 					   const unsigned char *credentials,
 					   size_t size,
 					   struct auth_request *request);
@@ -75,10 +80,15 @@
 			    const unsigned char **credentials_r,
 			    size_t *size_r);
 
-void passdb_handle_credentials(enum passdb_result result,
+bool passdb_handle_credentials(enum passdb_result result,
 			       const char *password, const char *scheme,
 			       lookup_credentials_callback_t *callback,
-                               struct auth_request *auth_request);
+			       struct auth_request *auth_request)
+	ATTR_WARN_UNUSED_RESULT;
+void passdb_handle_credentials_last(enum passdb_result result,
+				    const char *password, const char *scheme,
+				    lookup_credentials_callback_t *callback,
+				    struct auth_request *auth_request);
 
 struct auth_passdb *passdb_preinit(struct auth *auth, const char *driver,
 				   const char *args, unsigned int id);
diff -r eaba4cfeaa44 src/auth/userdb-sql.c
--- a/src/auth/userdb-sql.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/userdb-sql.c	Mon May 31 18:01:49 2010 +0100
@@ -9,6 +9,7 @@
 #include "strescape.h"
 #include "var-expand.h"
 #include "auth-cache.h"
+#include "passdb-cache.h"
 #include "db-sql.h"
 
 #include <stdlib.h>
@@ -130,6 +131,11 @@
 	flags = sql_get_flags(module->conn->db);
 	_module->blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0;
 
+	if (module->conn->set.allow_multiple_rows && passdb_cache != NULL) {
+		i_fatal("sql: allow_multiple_rows=yes "
+			"is incompatible with auth cache");
+	}
+
 	if (!_module->blocking || worker)
 		sql_connect(module->conn->db);
 }
diff -r eaba4cfeaa44 src/auth/userdb-static.c
--- a/src/auth/userdb-static.c	Fri May 28 16:52:23 2010 +0100
+++ b/src/auth/userdb-static.c	Mon May 31 18:01:49 2010 +0100
@@ -148,7 +148,7 @@
 	callback(USERDB_RESULT_OK, auth_request);
 }
 
-static void
+static bool
 static_credentials_callback(enum passdb_result result,
 			    const unsigned char *credentials ATTR_UNUSED,
 			    size_t size ATTR_UNUSED,
@@ -180,6 +180,7 @@
 	}
 
 	i_free(ctx);
+	return TRUE;
 }
 
 static void static_lookup(struct auth_request *auth_request,
