changeset 9273:1d7965092e0e HEAD

Implemented support for listing all users in userdb.
author Timo Sirainen <tss@iki.fi>
date Wed, 13 May 2009 17:51:16 -0400
parents f5b6974cbee7
children 39c2db5f1fcc
files doc/dovecot-ldap-example.conf doc/dovecot-sql-example.conf src/auth/auth-master-connection.c src/auth/auth-worker-client.c src/auth/auth-worker-server.c src/auth/auth-worker-server.h src/auth/db-ldap.c src/auth/db-ldap.h src/auth/db-passwd-file.h src/auth/db-sql.c src/auth/db-sql.h src/auth/main.c src/auth/passdb-blocking.c src/auth/passdb-ldap.c src/auth/passdb.c src/auth/userdb-blocking.c src/auth/userdb-blocking.h src/auth/userdb-checkpassword.c src/auth/userdb-ldap.c src/auth/userdb-nss.c src/auth/userdb-passwd-file.c src/auth/userdb-passwd.c src/auth/userdb-prefetch.c src/auth/userdb-sql.c src/auth/userdb-static.c src/auth/userdb-vpopmail.c src/auth/userdb.c src/auth/userdb.h
diffstat 28 files changed, 1022 insertions(+), 181 deletions(-) [+]
line wrap: on
line diff
--- a/doc/dovecot-ldap-example.conf	Wed May 13 12:40:58 2009 -0400
+++ b/doc/dovecot-ldap-example.conf	Wed May 13 17:51:16 2009 -0400
@@ -129,6 +129,10 @@
 # Filter for password lookups
 #pass_filter = (&(objectClass=posixAccount)(uid=%u))
 
+# Attributes and filter to get a list of all users
+#iterate_attrs = uid=user
+#iterate_filter = (objectClass=posixAccount)
+
 # Default password scheme. "{scheme}" before password overrides this.
 # List of supported schemes is in: http://wiki.dovecot.org/Authentication
 #default_pass_scheme = CRYPT
--- a/doc/dovecot-sql-example.conf	Wed May 13 12:40:58 2009 -0400
+++ b/doc/dovecot-sql-example.conf	Wed May 13 17:51:16 2009 -0400
@@ -128,3 +128,6 @@
 #  SELECT userid AS user, password, \
 #    home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid \
 #  FROM users WHERE userid = '%u'
+
+# Query to get a list of all usernames.
+#iterate_query = SELECT username AS user FROM users
--- a/src/auth/auth-master-connection.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/auth-master-connection.c	Wed May 13 17:51:16 2009 -0400
@@ -4,6 +4,7 @@
 #include "array.h"
 #include "hash.h"
 #include "str.h"
+#include "strescape.h"
 #include "hostpid.h"
 #include "str-sanitize.h"
 #include "ioloop.h"
@@ -12,6 +13,7 @@
 #include "ostream.h"
 #include "master-service.h"
 #include "userdb.h"
+#include "userdb-blocking.h"
 #include "auth-request-handler.h"
 #include "auth-master-interface.h"
 #include "auth-client-connection.h"
@@ -29,6 +31,16 @@
 	struct auth_request *auth_request;
 };
 
+struct master_list_iter_ctx {
+	struct auth_master_connection *conn;
+	struct auth_userdb *userdb;
+	struct userdb_iterate_context *iter;
+	unsigned int id;
+	bool failed;
+};
+
+static void master_input(struct auth_master_connection *conn);
+
 ARRAY_TYPE(auth_master_connections) auth_master_connections;
 
 void auth_master_request_callback(struct auth_stream_reply *reply,
@@ -162,6 +174,112 @@
 	return TRUE;
 }
 
+static void master_input_list_finish(struct master_list_iter_ctx *ctx)
+{
+	ctx->conn->io = io_add(ctx->conn->fd, IO_READ, master_input, ctx->conn);
+
+	if (ctx->iter != NULL)
+		(void)userdb_blocking_iter_deinit(&ctx->iter);
+	o_stream_unset_flush_callback(ctx->conn->output);
+	i_free(ctx);
+}
+
+static int master_output_list(struct master_list_iter_ctx *ctx)
+{
+	int ret;
+
+	if ((ret = o_stream_flush(ctx->conn->output)) < 0) {
+		master_input_list_finish(ctx);
+		return 1;
+	}
+	if (ret > 0)
+		userdb_blocking_iter_next(ctx->iter);
+	return 1;
+}
+
+static void master_input_list_callback(const char *user, void *context)
+{
+	struct master_list_iter_ctx *ctx = context;
+	int ret;
+
+	if (user == NULL) {
+		if (userdb_blocking_iter_deinit(&ctx->iter) < 0)
+			ctx->failed = TRUE;
+
+		do {
+			ctx->userdb = ctx->userdb->next;
+		} while (ctx->userdb != NULL &&
+			 ctx->userdb->userdb->iface->iterate_init == NULL);
+		if (ctx->userdb == NULL) {
+			/* iteration is finished */
+			const char *str;
+
+			str = t_strdup_printf("DONE\t%u\t%s\n", ctx->id,
+					      ctx->failed ? "fail" : "");
+			(void)o_stream_send_str(ctx->conn->output, str);
+			master_input_list_finish(ctx);
+			return;
+		}
+
+		/* continue iterating next userdb */
+		userdb_blocking_iter_init(ctx->userdb,
+					  master_input_list_callback, ctx);
+		userdb_blocking_iter_next(ctx->iter);
+		return;
+	}
+
+	T_BEGIN {
+		const char *str;
+
+		str = t_strdup_printf("LIST\t%u\t%s\n", ctx->id,
+				      str_tabescape(user));
+		ret = o_stream_send_str(ctx->conn->output, str);
+	} T_END;
+	if (ret < 0) {
+		/* disconnected, don't bother finishing */
+		master_input_list_finish(ctx);
+		return;
+	}
+	if (o_stream_get_buffer_used_size(ctx->conn->output) == 0)
+		userdb_blocking_iter_next(ctx->iter);
+}
+
+static bool
+master_input_list(struct auth_master_connection *conn, const char *args)
+{
+	struct auth_userdb *userdb = conn->auth->userdbs;
+	struct master_list_iter_ctx *ctx;
+	const char *str;
+	unsigned int id;
+
+	/* <id> */
+	if (*args == '\0') {
+		i_error("BUG: Master sent broken LIST");
+		return FALSE;
+	}
+	id = strtoul(args, NULL, 10);
+
+	while (userdb != NULL && userdb->userdb->iface->iterate_init == NULL)
+		userdb = userdb->next;
+	if (userdb == NULL) {
+		i_error("Trying to iterate users, but userdbs don't suppor it");
+		str = t_strdup_printf("DONE\t%u\tfail", id);
+		(void)o_stream_send_str(conn->output, str);
+		return TRUE;
+	}
+
+	ctx = i_new(struct master_list_iter_ctx, 1);
+	ctx->conn = conn;
+	ctx->userdb = userdb;
+	ctx->id = id;
+
+	io_remove(&conn->io);
+	o_stream_set_flush_callback(conn->output, master_output_list, ctx);
+	ctx->iter = userdb_blocking_iter_init(ctx->userdb,
+					      master_input_list_callback, ctx);
+	return TRUE;
+}
+
 static bool
 auth_master_input_line(struct auth_master_connection *conn, const char *line)
 {
@@ -172,6 +290,8 @@
 		return master_input_request(conn, line + 8);
 	else if (strncmp(line, "USER\t", 5) == 0)
 		return master_input_user(conn, line + 5);
+	else if (strncmp(line, "LIST\t", 5) == 0)
+		return master_input_list(conn, line + 5);
 	else if (strncmp(line, "CPID\t", 5) == 0) {
 		i_error("Authentication client trying to connect to "
 			"master socket");
--- a/src/auth/auth-worker-client.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/auth-worker-client.c	Wed May 13 17:51:16 2009 -0400
@@ -25,6 +25,17 @@
 	struct ostream *output;
 };
 
+struct auth_worker_list_context {
+	struct auth_worker_client *client;
+	struct userdb_module *userdb;
+	struct userdb_iterate_context *iter;
+	unsigned int id;
+	bool sending, sent, done;
+};
+
+static void auth_worker_input(struct auth_worker_client *client);
+static int auth_worker_output(struct auth_worker_client *client);
+
 static void
 auth_worker_client_check_throttle(struct auth_worker_client *client)
 {
@@ -395,6 +406,104 @@
 		lookup(auth_request, lookup_user_callback);
 }
 
+static void list_iter_deinit(struct auth_worker_list_context *ctx)
+{
+	struct auth_worker_client *client = ctx->client;
+	string_t *str;
+
+	str = t_str_new(32);
+	if (ctx->userdb->iface->iterate_deinit(ctx->iter) < 0)
+		str_printfa(str, "%u\tFAIL\n", ctx->id);
+	else
+		str_printfa(str, "%u\tOK\n", ctx->id);
+	auth_worker_send_reply(client, str);
+
+	client->io = io_add(client->fd, IO_READ, auth_worker_input, client);
+	o_stream_set_flush_callback(client->output, auth_worker_output, client);
+	auth_worker_client_unref(&client);
+	i_free(ctx);
+}
+
+static void list_iter_callback(const char *user, void *context)
+{
+	struct auth_worker_list_context *ctx = context;
+	string_t *str;
+
+	if (user == NULL) {
+		if (ctx->sending)
+			ctx->done = TRUE;
+		else
+			list_iter_deinit(ctx);
+		return;
+	}
+
+	T_BEGIN {
+		str = t_str_new(128);
+		str_printfa(str, "%u\t*\t%s\n", ctx->id, user);
+		o_stream_send(ctx->client->output, str_data(str), str_len(str));
+	} T_END;
+
+	if (ctx->sending) {
+		/* avoid recursively looping to this same function */
+		ctx->sent = TRUE;
+		return;
+	}
+
+	do {
+		ctx->sending = TRUE;
+		ctx->sent = FALSE;
+		ctx->userdb->iface->iterate_next(ctx->iter);
+	} while (ctx->sent &&
+		 o_stream_get_buffer_used_size(ctx->client->output) == 0);
+	ctx->sending = FALSE;
+	if (ctx->done)
+		list_iter_deinit(ctx);
+}
+
+static int auth_worker_list_output(struct auth_worker_list_context *ctx)
+{
+	int ret;
+
+	if ((ret = o_stream_flush(ctx->client->output)) < 0) {
+		list_iter_deinit(ctx);
+		return 1;
+	}
+	if (ret > 0)
+		ctx->userdb->iface->iterate_next(ctx->iter);
+	return 1;
+}
+
+static void
+auth_worker_handle_list(struct auth_worker_client *client,
+			unsigned int id, const char *args)
+{
+	struct auth_worker_list_context *ctx;
+	struct auth_userdb *userdb;
+	unsigned int num;
+
+	userdb = client->auth->userdbs;
+	for (num = atoi(args); num > 0; num--) {
+		userdb = userdb->next;
+		if (userdb == NULL) {
+			i_error("BUG: LIST had invalid userdb num");
+			return;
+		}
+	}
+
+	ctx = i_new(struct auth_worker_list_context, 1);
+	ctx->client = client;
+	ctx->id = id;
+	ctx->userdb = userdb->userdb;
+
+	io_remove(&ctx->client->io);
+	o_stream_set_flush_callback(ctx->client->output,
+				    auth_worker_list_output, ctx);
+	client->refcount++;
+	ctx->iter = ctx->userdb->iface->
+		iterate_init(userdb, list_iter_callback, ctx);
+	ctx->userdb->iface->iterate_next(ctx->iter);
+}
+
 static bool
 auth_worker_handle_line(struct auth_worker_client *client, const char *line)
 {
@@ -416,7 +525,10 @@
 		auth_worker_handle_setcred(client, id, line + 8);
 	else if (strncmp(line, "USER\t", 5) == 0)
 		auth_worker_handle_user(client, id, line + 5);
-
+	else if (strncmp(line, "LIST\t", 5) == 0)
+		auth_worker_handle_list(client, id, line + 5);
+	else
+		i_error("BUG: Auth-worker received unknown command: %s", line);
         return TRUE;
 }
 
--- a/src/auth/auth-worker-server.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/auth-worker-server.c	Wed May 13 17:51:16 2009 -0400
@@ -23,8 +23,8 @@
 	unsigned int id;
 	time_t created;
 	const char *data_str;
-	struct auth_request *auth_request;
-        auth_worker_callback_t *callback;
+	auth_worker_callback_t *callback;
+	void *context;
 };
 
 struct auth_worker_connection {
@@ -189,15 +189,12 @@
 	if (conn->request == NULL)
 		idle_count--;
 
-	if (conn->request != NULL) T_BEGIN {
-		struct auth_request *auth_request = conn->request->auth_request;
-
-		auth_request_log_error(auth_request, "worker-server",
-				       "Aborted: %s", reason);
-		conn->request->callback(auth_request, t_strdup_printf(
-				"FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE));
-		auth_request_unref(&conn->request->auth_request);
-	} T_END;
+	if (conn->request != NULL) {
+		i_error("auth worker: Aborted request: %s", reason);
+		conn->request->callback(t_strdup_printf(
+				"FAIL\t%d", PASSDB_RESULT_INTERNAL_FAILURE),
+				conn->request->context);
+	}
 
 	io_remove(&conn->io);
 	i_stream_destroy(&conn->input);
@@ -236,14 +233,18 @@
 				       struct auth_worker_request *request,
 				       const char *line)
 {
-	conn->request = NULL;
-	timeout_remove(&conn->to);
-	conn->to = timeout_add(AUTH_WORKER_MAX_IDLE_SECS * 1000,
-			       auth_worker_idle_timeout, conn);
-	idle_count++;
+	if (strncmp(line, "*\t", 2) == 0) {
+		/* multi-line reply, not finished yet */
+	} else {
+		conn->request = NULL;
+		timeout_remove(&conn->to);
+		conn->to = timeout_add(AUTH_WORKER_MAX_IDLE_SECS * 1000,
+				       auth_worker_idle_timeout, conn);
+		idle_count++;
+	}
 
-	request->callback(request->auth_request, line);
-	auth_request_unref(&request->auth_request);
+	if (!request->callback(line, request->context) && conn->io != NULL)
+		io_remove(&conn->io);
 }
 
 static void worker_input(struct auth_worker_connection *conn)
@@ -301,20 +302,19 @@
 		auth_worker_request_send_next(conn);
 }
 
-void auth_worker_call(struct auth_request *auth_request,
-		      struct auth_stream_reply *data,
-		      auth_worker_callback_t *callback)
+struct auth_worker_connection *
+auth_worker_call(struct auth *auth, pool_t pool,
+		 struct auth_stream_reply *data,
+		 auth_worker_callback_t *callback, void *context)
 {
 	struct auth_worker_connection *conn;
 	struct auth_worker_request *request;
 
-	request = p_new(auth_request->pool, struct auth_worker_request, 1);
+	request = p_new(pool, struct auth_worker_request, 1);
 	request->created = ioloop_time;
-	request->data_str = p_strdup(auth_request->pool,
-				     auth_stream_reply_export(data));
-	request->auth_request = auth_request;
+	request->data_str = p_strdup(pool, auth_stream_reply_export(data));
 	request->callback = callback;
-	auth_request_ref(auth_request);
+	request->context = context;
 
 	if (aqueue_count(worker_request_queue) > 0) {
 		/* requests are already being queued, no chance of
@@ -324,7 +324,7 @@
 		conn = auth_worker_find_free();
 		if (conn == NULL) {
 			/* no free connections, create a new one */
-			conn = auth_worker_create(auth_request->auth);
+			conn = auth_worker_create(auth);
 		}
 	}
 	if (conn != NULL)
@@ -333,31 +333,29 @@
 		/* reached the limit, queue the request */
 		aqueue_append(worker_request_queue, &request);
 	}
+	return conn;
 }
 
-void auth_worker_server_init(struct auth *auth)
+void auth_worker_server_resume_input(struct auth_worker_connection *conn)
 {
-	if (array_is_created(&connections)) {
-		/* already initialized */
-		return;
-	}
+	if (conn->io == NULL)
+		conn->io = io_add(conn->fd, IO_READ, worker_input, conn);
+}
 
+void auth_worker_server_init(void)
+{
 	worker_socket_path = "auth-worker";
 
 	i_array_init(&worker_request_array, 128);
 	worker_request_queue = aqueue_init(&worker_request_array.arr);
 
 	i_array_init(&connections, 16);
-	(void)auth_worker_create(auth);
 }
 
 void auth_worker_server_deinit(void)
 {
 	struct auth_worker_connection **connp, *conn;
 
-	if (!array_is_created(&connections))
-		return;
-
 	while (array_count(&connections) > 0) {
 		connp = array_idx_modifiable(&connections, 0);
 		conn = *connp;
--- a/src/auth/auth-worker-server.h	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/auth-worker-server.h	Wed May 13 17:51:16 2009 -0400
@@ -4,14 +4,15 @@
 struct auth_request;
 struct auth_stream_reply;
 
-typedef void auth_worker_callback_t(struct auth_request *request,
-				    const char *reply);
+typedef bool auth_worker_callback_t(const char *reply, void *context);
 
-void auth_worker_call(struct auth_request *auth_request,
-		      struct auth_stream_reply *data,
-		      auth_worker_callback_t *callback);
+struct auth_worker_connection *
+auth_worker_call(struct auth *auth, pool_t pool,
+		 struct auth_stream_reply *data,
+		 auth_worker_callback_t *callback, void *context);
+void auth_worker_server_resume_input(struct auth_worker_connection *conn);
 
-void auth_worker_server_init(struct auth *auth);
+void auth_worker_server_init(void);
 void auth_worker_server_deinit(void);
 
 #endif
--- a/src/auth/db-ldap.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/db-ldap.c	Wed May 13 17:51:16 2009 -0400
@@ -105,6 +105,8 @@
 	DEF_STR(user_filter),
 	DEF_STR(pass_attrs),
 	DEF_STR(pass_filter),
+	DEF_STR(iterate_attrs),
+	DEF_STR(iterate_filter),
 	DEF_STR(default_pass_scheme),
 
 	{ 0, NULL, 0 }
@@ -138,6 +140,8 @@
 	MEMBER(user_filter) "(&(objectClass=posixAccount)(uid=%u))",
 	MEMBER(pass_attrs) "uid=user,userPassword=password",
 	MEMBER(pass_filter) "(&(objectClass=posixAccount)(uid=%u))",
+	MEMBER(iterate_attrs) "uid=user",
+	MEMBER(iterate_filter) "(objectClass=posixAccount)",
 	MEMBER(default_pass_scheme) "crypt"
 };
 
@@ -309,7 +313,7 @@
 			    srequest->filter, srequest->attributes, 0);
 	if (request->msgid == -1) {
 		auth_request_log_error(request->auth_request, "ldap",
-				       "ldap_search() failed (filter %s): %s",
+				       "ldap_search(%s) parsing failed: %s",
 				       srequest->filter, ldap_get_error(conn));
 		if (ldap_handle_error(conn) < 0) {
 			/* broken request, remove it */
@@ -511,11 +515,35 @@
 	}
 }
 
+static struct ldap_request *
+db_ldap_find_request(struct ldap_connection *conn, int msgid,
+		     unsigned int *idx_r)
+{
+	struct ldap_request *const *requests, *request = NULL;
+	unsigned int i, count;
+
+	count = aqueue_count(conn->request_queue);
+	if (count == 0)
+		return NULL;
+
+	requests = array_idx(&conn->request_array, 0);
+	for (i = 0; i < count; i++) {
+		request = requests[aqueue_idx(conn->request_queue, i)];
+		if (request->msgid == msgid) {
+			*idx_r = i;
+			return request;
+		}
+		if (request->msgid == -1)
+			break;
+	}
+	return NULL;
+}
+
 static void
 db_ldap_handle_result(struct ldap_connection *conn, LDAPMessage *res)
 {
-	struct ldap_request *const *requests, *request = NULL;
-	unsigned int i, count;
+	struct ldap_request *request;
+	unsigned int idx;
 	int msgid, ret;
 
 	msgid = ldap_msgid(res);
@@ -524,32 +552,38 @@
 		return;
 	}
 
-	count = aqueue_count(conn->request_queue);
-	requests = count == 0 ? NULL : array_idx(&conn->request_array, 0);
-	for (i = 0; i < count; i++) {
-		request = requests[aqueue_idx(conn->request_queue, i)];
-		if (request->msgid == msgid)
-			break;
-		if (request->msgid == -1) {
-			request = NULL;
-			break;
-		}
-	}
+	request = db_ldap_find_request(conn, msgid, &idx);
 	if (request == NULL) {
 		i_error("LDAP: Reply with unknown msgid %d", msgid);
 		return;
 	}
 
+	i_assert(conn->pending_count > 0);
 	if (request->type == LDAP_REQUEST_TYPE_BIND) {
 		i_assert(conn->conn_state == LDAP_CONN_STATE_BINDING);
 		i_assert(conn->pending_count == 1);
 		conn->conn_state = LDAP_CONN_STATE_BOUND_AUTH;
+	} else {
+		switch (ldap_msgtype(res)) {
+		case LDAP_RES_SEARCH_ENTRY:
+		case LDAP_RES_SEARCH_RESULT:
+			break;
+		case LDAP_RES_SEARCH_REFERENCE:
+			/* we're going to ignore this */
+			return;
+		default:
+			i_error("LDAP: Reply with unexpected type %d",
+				ldap_msgtype(res));
+			return;
+		}
 	}
-	i_assert(conn->pending_count > 0);
-	conn->pending_count--;
-	aqueue_delete(conn->request_queue, i);
-
-	ret = ldap_result2error(conn->ld, res, 0);
+	if (ldap_msgtype(res) == LDAP_RES_SEARCH_ENTRY)
+		ret = LDAP_SUCCESS;
+	else {
+		conn->pending_count--;
+		aqueue_delete(conn->request_queue, idx);
+		ret = ldap_result2error(conn->ld, res, 0);
+	}
 	if (ret != LDAP_SUCCESS && request->type == LDAP_REQUEST_TYPE_SEARCH) {
 		/* handle search failures here */
 		struct ldap_request_search *srequest =
@@ -565,9 +599,9 @@
 		request->callback(conn, request, res);
 	} T_END;
 
-	if (i > 0) {
+	if (idx > 0) {
 		/* see if there are timed out requests */
-		db_ldap_abort_requests(conn, i,
+		db_ldap_abort_requests(conn, idx,
 				       DB_LDAP_REQUEST_LOST_TIMEOUT_SECS,
 				       TRUE, "Request lost");
 	}
@@ -579,16 +613,16 @@
 	LDAPMessage *res;
 	int ret;
 
-	for (;;) {
+	do {
 		if (conn->ld == NULL)
 			return;
 
 		memset(&timeout, 0, sizeof(timeout));
-		ret = ldap_result(conn->ld, LDAP_RES_ANY, 1, &timeout, &res);
+		ret = ldap_result(conn->ld, LDAP_RES_ANY, 0, &timeout, &res);
 #ifdef OPENLDAP_ASYNC_WORKAROUND
 		if (ret == 0) {
 			/* try again, there may be another in buffer */
-			ret = ldap_result(conn->ld, LDAP_RES_ANY, 1,
+			ret = ldap_result(conn->ld, LDAP_RES_ANY, 0,
 					  &timeout, &res);
 		}
 #endif
@@ -597,10 +631,13 @@
 
 		db_ldap_handle_result(conn, res);
 		ldap_msgfree(res);
-	}
+	} while (conn->io != NULL);
 	conn->last_reply_stamp = ioloop_time;
 
-	if (ret == 0) {
+	if (ret > 0) {
+		/* input disabled, continue once it's enabled */
+		i_assert(conn->io == NULL);
+	} else if (ret == 0) {
 		/* send more requests */
 		while (db_ldap_request_queue_next(conn))
 			;
@@ -858,6 +895,19 @@
 	return 0;
 }
 
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable)
+{
+	if (!enable) {
+		if (conn->io != NULL)
+			io_remove(&conn->io);
+	} else {
+		if (conn->io == NULL && conn->fd != -1) {
+			conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
+			ldap_input(conn);
+		}
+	}
+}
+
 static void db_ldap_disconnect_timeout(struct ldap_connection *conn)
 {
 	db_ldap_abort_requests(conn, -1U,
@@ -967,7 +1017,7 @@
 struct var_expand_table *
 db_ldap_value_get_var_expand_table(struct auth_request *auth_request)
 {
-	const struct var_expand_table *auth_table;
+	const struct var_expand_table *auth_table = NULL;
 	struct var_expand_table *table;
 	unsigned int count;
 
--- a/src/auth/db-ldap.h	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/db-ldap.h	Wed May 13 17:51:16 2009 -0400
@@ -61,6 +61,8 @@
 	const char *user_filter;
 	const char *pass_attrs;
 	const char *pass_filter;
+	const char *iterate_attrs;
+	const char *iterate_filter;
 
 	const char *default_pass_scheme;
 
@@ -85,6 +87,12 @@
 
 	db_search_callback_t *callback;
 	struct auth_request *auth_request;
+
+	/* If expect_one_reply=TRUE, this contains the first LDAP entry.
+	   If another one comes, we'll return an error. */
+	LDAPMessage *first_entry;
+
+	unsigned int expect_one_reply:1;
 };
 
 struct ldap_request_search {
@@ -139,8 +147,8 @@
 	/* Timestamp when we last received a reply */
 	time_t last_reply_stamp;
 
-	char **pass_attr_names, **user_attr_names;
-	struct hash_table *pass_attr_map, *user_attr_map;
+	char **pass_attr_names, **user_attr_names, **iterate_attr_names;
+	struct hash_table *pass_attr_map, *user_attr_map, *iterate_attr_map;
 };
 
 /* Send/queue request */
@@ -156,6 +164,8 @@
 
 int db_ldap_connect(struct ldap_connection *conn);
 
+void db_ldap_enable_input(struct ldap_connection *conn, bool enable);
+
 struct var_expand_table *
 db_ldap_value_get_var_expand_table(struct auth_request *auth_request);
 
--- a/src/auth/db-passwd-file.h	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/db-passwd-file.h	Wed May 13 17:51:16 2009 -0400
@@ -16,6 +16,7 @@
 struct passwd_file {
         struct db_passwd_file *db;
 	pool_t pool;
+	int refcount;
 
 	char *path;
 	time_t stamp;
--- a/src/auth/db-sql.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/db-sql.c	Wed May 13 17:51:16 2009 -0400
@@ -21,6 +21,7 @@
 	DEF_STR(password_query),
 	DEF_STR(user_query),
  	DEF_STR(update_query),
+ 	DEF_STR(iterate_query),
 	DEF_STR(default_pass_scheme),
 
 	{ 0, NULL, 0 }
@@ -32,6 +33,7 @@
 	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(iterate_query) "SELECT username, domain FROM users",
 	MEMBER(default_pass_scheme) "MD5"
 };
 
--- a/src/auth/db-sql.h	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/db-sql.h	Wed May 13 17:51:16 2009 -0400
@@ -9,6 +9,7 @@
 	const char *password_query;
 	const char *user_query;
 	const char *update_query;
+	const char *iterate_query;
 	const char *default_pass_scheme;
 };
 
--- a/src/auth/main.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/main.c	Wed May 13 17:51:16 2009 -0400
@@ -76,6 +76,7 @@
 	child_wait_init();
 	mech_init(auth->set);
 	password_schemes_init();
+	auth_worker_server_init();
 	auth_init(auth);
 	auth_request_handler_init();
 	auth_master_connections_init();
--- a/src/auth/passdb-blocking.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/passdb-blocking.c	Wed May 13 17:51:16 2009 -0400
@@ -59,13 +59,16 @@
 	return PASSDB_RESULT_INTERNAL_FAILURE;
 }
 
-static void
-verify_plain_callback(struct auth_request *request, const char *reply)
+static bool
+verify_plain_callback(const char *reply, void *context)
 {
+	struct auth_request *request = context;
 	enum passdb_result result;
 
 	result = auth_worker_reply_parse(request, reply);
 	auth_request_verify_plain_callback(result, request);
+	auth_request_unref(&request);
+	return TRUE;
 }
 
 void passdb_blocking_verify_plain(struct auth_request *request)
@@ -81,12 +84,14 @@
 	auth_stream_reply_add(reply, NULL, request->mech_password);
 	auth_request_export(request, reply);
 
-	auth_worker_call(request, reply, verify_plain_callback);
+	auth_request_ref(request);
+	auth_worker_call(request->auth, request->pool, reply,
+			 verify_plain_callback, request);
 }
 
-static void
-lookup_credentials_callback(struct auth_request *request, const char *reply)
+static bool lookup_credentials_callback(const char *reply, void *context)
 {
+	struct auth_request *request = context;
 	enum passdb_result result;
 	const char *password = NULL, *scheme = NULL;
 
@@ -105,6 +110,8 @@
 	passdb_handle_credentials(result, password, scheme,
 				  auth_request_lookup_credentials_callback,
 				  request);
+	auth_request_unref(&request);
+	return TRUE;
 }
 
 void passdb_blocking_lookup_credentials(struct auth_request *request)
@@ -120,16 +127,21 @@
 	auth_stream_reply_add(reply, NULL, request->credentials_scheme);
 	auth_request_export(request, reply);
 
-	auth_worker_call(request, reply, lookup_credentials_callback);
+	auth_request_ref(request);
+	auth_worker_call(request->auth, request->pool, reply,
+			 lookup_credentials_callback, request);
 }
 
-static void
-set_credentials_callback(struct auth_request *request, const char *reply)
+static bool
+set_credentials_callback(const char *reply, void *context)
 {
+	struct auth_request *request = context;
 	bool success;
 
 	success = strcmp(reply, "OK") == 0 || strncmp(reply, "OK\t", 3) == 0;
 	request->private_callback.set_credentials(success, request);
+	auth_request_unref(&request);
+	return TRUE;
 }
 
 void passdb_blocking_set_credentials(struct auth_request *request,
@@ -143,5 +155,7 @@
 	auth_stream_reply_add(reply, NULL, new_credentials);
 	auth_request_export(request, reply);
 
-	auth_worker_call(request, reply, set_credentials_callback);
+	auth_request_ref(request);
+	auth_worker_call(request->auth, request->pool, reply,
+			 set_credentials_callback, request);
 }
--- a/src/auth/passdb-ldap.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/passdb-ldap.c	Wed May 13 17:51:16 2009 -0400
@@ -33,46 +33,10 @@
 		verify_plain_callback_t *verify_plain;
                 lookup_credentials_callback_t *lookup_credentials;
 	} callback;
+
+	unsigned int entries;
 };
 
-static LDAPMessage *
-handle_request_get_entry(struct ldap_connection *conn,
-			 struct auth_request *auth_request,
-			 struct passdb_ldap_request *request, LDAPMessage *res)
-{
-	enum passdb_result passdb_result;
-	LDAPMessage *entry;
-
-	passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
-
-	if (res != NULL) {
-		/* LDAP search was successful */
-		entry = ldap_first_entry(conn->ld, res);
-		if (entry == NULL) {
-			passdb_result = PASSDB_RESULT_USER_UNKNOWN;
-			auth_request_log_info(auth_request, "ldap",
-					      "unknown user");
-		} else {
-			if (ldap_next_entry(conn->ld, entry) == NULL) {
-				/* success */
-				return entry;
-			}
-
-			auth_request_log_error(auth_request, "ldap",
-				"Multiple replies found for user");
-		}
-	}
-
-	if (auth_request->credentials_scheme != NULL) {
-		request->callback.lookup_credentials(passdb_result, NULL, 0,
-						     auth_request);
-	} else {
-		request->callback.verify_plain(passdb_result, auth_request);
-	}
-	auth_request_unref(&auth_request);
-	return NULL;
-}
-
 static void
 ldap_query_save_result(struct ldap_connection *conn,
 		       LDAPMessage *entry, struct auth_request *auth_request)
@@ -89,29 +53,24 @@
 }
 
 static void
-ldap_lookup_pass_callback(struct ldap_connection *conn,
-			  struct ldap_request *request, LDAPMessage *res)
+ldap_lookup_finish(struct auth_request *auth_request,
+		   struct passdb_ldap_request *ldap_request,
+		   LDAPMessage *res)
 {
-	struct passdb_ldap_request *ldap_request =
-		(struct passdb_ldap_request *)request;
-        struct auth_request *auth_request = request->auth_request;
 	enum passdb_result passdb_result;
-	LDAPMessage *entry;
-	const char *password, *scheme;
+	const char *password = NULL, *scheme;
 	int ret;
 
-	entry = handle_request_get_entry(conn, auth_request, ldap_request, res);
-	if (entry == NULL)
-		return;
-
-	/* got first LDAP entry */
-	passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
-	password = NULL;
-
-	ldap_query_save_result(conn, entry, auth_request);
-	if (ldap_next_entry(conn->ld, entry) != NULL) {
+	if (res == NULL) {
+		passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+	} else if (ldap_request->entries == 0) {
+		passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+		auth_request_log_info(auth_request, "ldap",
+				      "unknown user");
+	} else if (ldap_request->entries > 1) {
 		auth_request_log_error(auth_request, "ldap",
 			"pass_filter matched multiple objects, aborting");
+		passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
 	} else if (auth_request->passdb_password == NULL &&
 		   !auth_request->no_password) {
 		auth_request_log_info(auth_request, "ldap",
@@ -144,7 +103,26 @@
 		ldap_request->callback.verify_plain(passdb_result,
 						    auth_request);
 	}
-	auth_request_unref(&auth_request);
+}
+
+static void
+ldap_lookup_pass_callback(struct ldap_connection *conn,
+			  struct ldap_request *request, LDAPMessage *res)
+{
+	struct passdb_ldap_request *ldap_request =
+		(struct passdb_ldap_request *)request;
+        struct auth_request *auth_request = request->auth_request;
+
+	if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) {
+		ldap_lookup_finish(auth_request, ldap_request, res);
+		auth_request_unref(&auth_request);
+		return;
+	}
+
+	if (ldap_request->entries++ == 0) {
+		/* first entry */
+		ldap_query_save_result(conn, res, auth_request);
+	}
 }
 
 static void
@@ -208,6 +186,35 @@
 	db_ldap_request(conn, &brequest->request);
 }
 
+static void
+ldap_bind_lookup_dn_fail(struct auth_request *auth_request,
+			 struct passdb_ldap_request *request,
+			 LDAPMessage *res)
+{
+	enum passdb_result passdb_result;
+
+	if (res == NULL)
+		passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+	else if (request->entries == 0) {
+		passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+		auth_request_log_info(auth_request, "ldap",
+				      "unknown user");
+	} else {
+		i_assert(request->entries > 1);
+		auth_request_log_error(auth_request, "ldap",
+			"pass_filter matched multiple objects, aborting");
+		passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
+	}
+
+	if (auth_request->credentials_scheme != NULL) {
+		request->callback.lookup_credentials(passdb_result, NULL, 0,
+						     auth_request);
+	} else {
+		request->callback.verify_plain(passdb_result, auth_request);
+	}
+	auth_request_unref(&auth_request);
+}
+
 static void ldap_bind_lookup_dn_callback(struct ldap_connection *conn,
 					 struct ldap_request *ldap_request,
 					 LDAPMessage *res)
@@ -219,12 +226,18 @@
 	LDAPMessage *entry;
 	char *dn;
 
-	entry = handle_request_get_entry(conn, auth_request,
-					 passdb_ldap_request, res);
-	if (entry == NULL)
+	if (res != NULL && ldap_msgtype(res) != LDAP_RES_SEARCH_RESULT) {
+		if (passdb_ldap_request->entries++ == 0) {
+			/* first entry */
+			ldap_query_save_result(conn, res, auth_request);
+		}
 		return;
+	}
 
-	ldap_query_save_result(conn, entry, auth_request);
+	if (res == NULL || passdb_ldap_request->entries != 0) {
+		ldap_bind_lookup_dn_fail(auth_request, passdb_ldap_request, res);
+		return;
+	}
 
 	/* convert search request to bind request */
 	brequest = &passdb_ldap_request->request.bind;
--- a/src/auth/passdb.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/passdb.c	Wed May 13 17:51:16 2009 -0400
@@ -202,11 +202,6 @@
 
 	i_assert(passdb->passdb->default_pass_scheme != NULL ||
 		 passdb->passdb->cache_key == NULL);
-
-	if (passdb->passdb->blocking && !worker) {
-		/* blocking passdb - we need an auth server */
-		auth_worker_server_init(passdb->auth);
-	}
 }
 
 void passdb_deinit(struct auth_passdb *passdb)
--- a/src/auth/userdb-blocking.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-blocking.c	Wed May 13 17:51:16 2009 -0400
@@ -8,8 +8,16 @@
 
 #include <stdlib.h>
 
-static void user_callback(struct auth_request *request, const char *reply)
+struct blocking_userdb_iterate_context {
+	struct userdb_iterate_context ctx;
+	pool_t pool;
+	struct auth_worker_connection *conn;
+	bool next;
+};
+
+static bool user_callback(const char *reply, void *context)
 {
+	struct auth_request *request = context;
 	enum userdb_result result;
 
 	if (strncmp(reply, "FAIL\t", 5) == 0)
@@ -26,6 +34,8 @@
 	}
 
         auth_request_userdb_callback(result, request);
+	auth_request_unref(&request);
+	return TRUE;
 }
 
 void userdb_blocking_lookup(struct auth_request *request)
@@ -37,5 +47,69 @@
 	auth_stream_reply_add(reply, NULL, dec2str(request->userdb->num));
 	auth_request_export(request, reply);
 
-	auth_worker_call(request, reply, user_callback);
+	auth_request_ref(request);
+	auth_worker_call(request->auth, request->pool, reply,
+			 user_callback, request);
+}
+
+static bool iter_callback(const char *reply, void *context)
+{
+	struct blocking_userdb_iterate_context *ctx = context;
+
+	if (strncmp(reply, "*\t", 2) == 0) {
+		ctx->next = FALSE;
+		ctx->ctx.callback(reply + 2, ctx->ctx.context);
+		return ctx->next;
+	} else if (strcmp(reply, "OK") == 0) {
+		ctx->ctx.callback(NULL, ctx->ctx.context);
+		return TRUE;
+	} else {
+		ctx->ctx.failed = TRUE;
+		ctx->ctx.callback(NULL, ctx->ctx.context);
+		return TRUE;
+	}
 }
+
+struct userdb_iterate_context *
+userdb_blocking_iter_init(struct auth_userdb *userdb,
+			  userdb_iter_callback_t *callback, void *context)
+{
+	struct blocking_userdb_iterate_context *ctx;
+	struct auth_stream_reply *reply;
+	pool_t pool;
+
+	reply = auth_stream_reply_init(pool_datastack_create());
+	auth_stream_reply_add(reply, "LIST", NULL);
+	auth_stream_reply_add(reply, NULL, dec2str(userdb->num));
+
+	pool = pool_alloconly_create("userdb iter", 512);
+	ctx = p_new(pool, struct blocking_userdb_iterate_context, 1);
+	ctx->ctx.userdb = userdb->userdb;
+	ctx->ctx.callback = callback;
+	ctx->ctx.context = context;
+	ctx->pool = pool;
+
+	ctx->conn = auth_worker_call(userdb->auth, pool, reply,
+				     iter_callback, ctx);
+	return &ctx->ctx;
+}
+
+void userdb_blocking_iter_next(struct userdb_iterate_context *_ctx)
+{
+	struct blocking_userdb_iterate_context *ctx =
+		(struct blocking_userdb_iterate_context *)_ctx;
+
+	ctx->next = TRUE;
+	auth_worker_server_resume_input(ctx->conn);
+}
+
+int userdb_blocking_iter_deinit(struct userdb_iterate_context **_ctx)
+{
+	struct blocking_userdb_iterate_context *ctx =
+		(struct blocking_userdb_iterate_context *)*_ctx;
+	int ret = ctx->ctx.failed ? -1 : 0;
+
+	*_ctx = NULL;
+	pool_unref(&ctx->pool);
+	return ret;
+}
--- a/src/auth/userdb-blocking.h	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-blocking.h	Wed May 13 17:51:16 2009 -0400
@@ -3,4 +3,10 @@
 
 void userdb_blocking_lookup(struct auth_request *request);
 
+struct userdb_iterate_context *
+userdb_blocking_iter_init(struct auth_userdb *userdb,
+			  userdb_iter_callback_t *callback, void *context);
+void userdb_blocking_iter_next(struct userdb_iterate_context *ctx);
+int userdb_blocking_iter_deinit(struct userdb_iterate_context **ctx);
+
 #endif
--- a/src/auth/userdb-checkpassword.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-checkpassword.c	Wed May 13 17:51:16 2009 -0400
@@ -261,6 +261,10 @@
 	checkpassword_deinit,
 
 	checkpassword_lookup,
+
+	NULL,
+	NULL,
+	NULL
 };
 #else
 struct userdb_module_interface userdb_checkpassword = {
--- a/src/auth/userdb-ldap.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-ldap.c	Wed May 13 17:51:16 2009 -0400
@@ -22,7 +22,22 @@
 
 struct userdb_ldap_request {
 	struct ldap_request_search request;
-        userdb_callback_t *userdb_callback;
+	userdb_callback_t *userdb_callback;
+	unsigned int entries;
+};
+
+struct userdb_iter_ldap_request {
+	struct ldap_request_search request;
+	struct ldap_userdb_iterate_context *ctx;
+	userdb_callback_t *userdb_callback;
+};
+
+struct ldap_userdb_iterate_context {
+	struct userdb_iterate_context ctx;
+	struct userdb_iter_ldap_request request;
+	struct auth *auth;
+	struct ldap_connection *conn;
+	bool continued, in_callback;
 };
 
 static void
@@ -42,6 +57,30 @@
 	}
 }
 
+static void
+userdb_ldap_lookup_finish(struct auth_request *auth_request,
+			  struct userdb_ldap_request *urequest,
+			  LDAPMessage *res)
+{
+	enum userdb_result result = USERDB_RESULT_INTERNAL_FAILURE;
+
+	if (res == NULL) {
+		result = USERDB_RESULT_INTERNAL_FAILURE;
+	} else if (urequest->entries == 0) {
+		result = USERDB_RESULT_USER_UNKNOWN;
+		auth_request_log_info(auth_request, "ldap",
+				      "unknown user");
+	} else if (urequest->entries > 1) {
+		auth_request_log_error(auth_request, "ldap",
+			"user_filter matched multiple objects, aborting");
+		result = USERDB_RESULT_INTERNAL_FAILURE;
+	} else {
+		result = USERDB_RESULT_OK;
+	}
+
+	urequest->userdb_callback(result, auth_request);
+}
+
 static void userdb_ldap_lookup_callback(struct ldap_connection *conn,
 					struct ldap_request *request,
 					LDAPMessage *res)
@@ -50,28 +89,17 @@
 		(struct userdb_ldap_request *) request;
 	struct auth_request *auth_request =
 		urequest->request.request.auth_request;
-	LDAPMessage *entry;
-	enum userdb_result result = USERDB_RESULT_INTERNAL_FAILURE;
 
-	if (res != NULL) {
-		entry = ldap_first_entry(conn->ld, res);
-		if (entry == NULL) {
-			result = USERDB_RESULT_USER_UNKNOWN;
-			auth_request_log_info(auth_request, "ldap",
-					      "Unknown user");
-		} else {
-			ldap_query_get_result(conn, entry, auth_request);
-			if (ldap_next_entry(conn->ld, entry) == NULL)
-				result = USERDB_RESULT_OK;
-			else {
-				auth_request_log_error(auth_request, "ldap",
-					"Multiple replies found for user");
-			}
-		}
+	if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) {
+		userdb_ldap_lookup_finish(auth_request, urequest, res);
+		auth_request_unref(&auth_request);
+		return;
 	}
 
-	urequest->userdb_callback(result, auth_request);
-	auth_request_unref(&auth_request);
+	if (urequest->entries++ == 0) {
+		/* first entry */
+		ldap_query_get_result(conn, res, auth_request);
+	}
 }
 
 static void userdb_ldap_lookup(struct auth_request *auth_request,
@@ -114,6 +142,97 @@
 	db_ldap_request(conn, &request->request.request);
 }
 
+static void userdb_ldap_iterate_callback(struct ldap_connection *conn,
+					 struct ldap_request *request,
+					 LDAPMessage *res)
+{
+	struct userdb_iter_ldap_request *urequest =
+		(struct userdb_iter_ldap_request *)request;
+	struct ldap_userdb_iterate_context *ctx = urequest->ctx;
+	struct db_ldap_result_iterate_context *ldap_iter;
+	const char *name, *const *values;
+
+	if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) {
+		if (res == NULL)
+			ctx->ctx.failed = TRUE;
+		ctx->ctx.callback(NULL, ctx->ctx.context);
+		return;
+	}
+
+	ctx->in_callback = TRUE;
+	ldap_iter = db_ldap_result_iterate_init(conn, res,
+						request->auth_request,
+						conn->iterate_attr_map);
+	while (db_ldap_result_iterate_next_all(ldap_iter, &name, &values)) {
+		for (; *values != NULL; values++) {
+			ctx->continued = FALSE;
+			ctx->ctx.callback(*values, ctx->ctx.context);
+		}
+	}
+	if (!ctx->continued)
+		db_ldap_enable_input(conn, FALSE);
+	ctx->in_callback = FALSE;
+}
+
+static struct userdb_iterate_context *
+userdb_ldap_iterate_init(struct auth_userdb *userdb,
+			 userdb_iter_callback_t *callback, void *context)
+{
+	struct ldap_userdb_module *module =
+		(struct ldap_userdb_module *)userdb->userdb;
+	struct ldap_connection *conn = module->conn;
+	struct ldap_userdb_iterate_context *ctx;
+	struct userdb_iter_ldap_request *request;
+	const char **attr_names = (const char **)conn->iterate_attr_names;
+
+	ctx = i_new(struct ldap_userdb_iterate_context, 1);
+	ctx->ctx.userdb = userdb->userdb;
+	ctx->ctx.callback = callback;
+	ctx->ctx.context = context;
+	ctx->auth = userdb->auth;
+	ctx->conn = conn;
+	request = &ctx->request;
+	request->ctx = ctx;
+
+	request->request.request.auth_request =
+		auth_request_new_dummy(userdb->auth);
+	request->request.base = conn->set.base;
+	request->request.filter = conn->set.iterate_filter;
+	request->request.attributes = conn->iterate_attr_names;
+
+	if (userdb->auth->set->debug) {
+		i_info("ldap: iterate: base=%s scope=%s filter=%s fields=%s",
+		       conn->set.base, conn->set.scope, request->request.filter,
+		       attr_names == NULL ? "(all)" :
+		       t_strarray_join(attr_names, ","));
+	}
+	request->request.request.callback = userdb_ldap_iterate_callback;
+	db_ldap_request(conn, &request->request.request);
+	return &ctx->ctx;
+}
+
+static void userdb_ldap_iterate_next(struct userdb_iterate_context *_ctx)
+{
+	struct ldap_userdb_iterate_context *ctx =
+		(struct ldap_userdb_iterate_context *)_ctx;
+
+	ctx->continued = TRUE;
+	if (!ctx->in_callback)
+		db_ldap_enable_input(ctx->conn, TRUE);
+}
+
+static int userdb_ldap_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+	struct ldap_userdb_iterate_context *ctx =
+		(struct ldap_userdb_iterate_context *)_ctx;
+	int ret = _ctx->failed ? -1 : 0;
+
+	db_ldap_enable_input(ctx->conn, TRUE);
+	auth_request_unref(&ctx->request.request.request.auth_request);
+	i_free(ctx);
+	return ret;
+}
+
 static struct userdb_module *
 userdb_ldap_preinit(struct auth_userdb *auth_userdb, const char *args)
 {
@@ -125,9 +244,15 @@
 	conn->user_attr_map =
 		hash_table_create(default_pool, conn->pool, 0, str_hash,
 				  (hash_cmp_callback_t *)strcmp);
+	conn->iterate_attr_map =
+		hash_table_create(default_pool, conn->pool, 0, str_hash,
+				  (hash_cmp_callback_t *)strcmp);
 
 	db_ldap_set_attrs(conn, conn->set.user_attrs, &conn->user_attr_names,
 			  conn->user_attr_map, NULL);
+	db_ldap_set_attrs(conn, conn->set.iterate_attrs,
+			  &conn->iterate_attr_names,
+			  conn->iterate_attr_map, NULL);
 	module->module.cache_key =
 		auth_cache_parse_key(auth_userdb->auth->pool,
 				     t_strconcat(conn->set.base,
@@ -159,7 +284,11 @@
 	userdb_ldap_init,
 	userdb_ldap_deinit,
 
-	userdb_ldap_lookup
+	userdb_ldap_lookup,
+
+	userdb_ldap_iterate_init,
+	userdb_ldap_iterate_next,
+	userdb_ldap_iterate_deinit
 };
 #else
 struct userdb_module_interface userdb_ldap = {
--- a/src/auth/userdb-nss.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-nss.c	Wed May 13 17:51:16 2009 -0400
@@ -147,7 +147,11 @@
 	NULL,
 	userdb_nss_deinit,
 
-	userdb_nss_lookup
+	userdb_nss_lookup,
+
+	NULL,
+	NULL,
+	NULL
 };
 #else
 struct userdb_module_interface userdb_nss = {
--- a/src/auth/userdb-passwd-file.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-passwd-file.c	Wed May 13 17:51:16 2009 -0400
@@ -5,13 +5,23 @@
 
 #ifdef USERDB_PASSWD_FILE
 
+#include "istream.h"
 #include "str.h"
 #include "auth-cache.h"
 #include "var-expand.h"
 #include "db-passwd-file.h"
 
+#include <unistd.h>
+#include <fcntl.h>
+
 #define PASSWD_FILE_CACHE_KEY "%u"
 
+struct passwd_file_userdb_iterate_context {
+	struct userdb_iterate_context ctx;
+	struct istream *input;
+	char *path;
+};
+
 struct passwd_file_userdb_module {
         struct userdb_module module;
 
@@ -73,6 +83,66 @@
 	callback(USERDB_RESULT_OK, auth_request);
 }
 
+static struct userdb_iterate_context *
+passwd_file_iterate_init(struct auth_userdb *userdb,
+			 userdb_iter_callback_t *callback, void *context)
+{
+	struct passwd_file_userdb_module *module =
+		(struct passwd_file_userdb_module *)userdb;
+	struct passwd_file_userdb_iterate_context *ctx;
+	int fd;
+
+	ctx = i_new(struct passwd_file_userdb_iterate_context, 1);
+	ctx->ctx.userdb = userdb->userdb;
+	ctx->ctx.callback = callback;
+	ctx->ctx.context = context;
+	ctx->path = i_strdup(module->pwf->default_file->path);
+
+	/* for now we support only a single passwd-file */
+	fd = open(ctx->path, O_RDONLY);
+	if (fd == -1) {
+		i_error("open(%s) failed: %m", ctx->path);
+		ctx->ctx.failed = TRUE;
+	} else {
+		ctx->input = i_stream_create_fd(fd, (size_t)-1, TRUE);
+	}
+	return &ctx->ctx;
+}
+
+static void passwd_file_iterate_next(struct userdb_iterate_context *_ctx)
+{
+	struct passwd_file_userdb_iterate_context *ctx =
+		(struct passwd_file_userdb_iterate_context *)_ctx;
+	const char *line;
+
+	if (ctx->input == NULL)
+		line = NULL;
+	else {
+		line = i_stream_read_next_line(ctx->input);
+		if (line == NULL && ctx->input->stream_errno != 0) {
+			i_error("read(%s) failed: %m", ctx->path);
+			_ctx->failed = TRUE;
+		}
+	}
+	if (line == NULL)
+		_ctx->callback(NULL, _ctx->context);
+	else T_BEGIN {
+		_ctx->callback(t_strcut(line, ':'), _ctx->context);
+	} T_END;
+}
+
+static int passwd_file_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+	struct passwd_file_userdb_iterate_context *ctx =
+		(struct passwd_file_userdb_iterate_context *)_ctx;
+	int ret = _ctx->failed ? -1 : 0;
+
+	i_stream_destroy(&ctx->input);
+	i_free(ctx->path);
+	i_free(ctx);
+	return ret;
+}
+
 static struct userdb_module *
 passwd_file_preinit(struct auth_userdb *auth_userdb, const char *args)
 {
@@ -135,7 +205,11 @@
 	passwd_file_init,
 	passwd_file_deinit,
 
-	passwd_file_lookup
+	passwd_file_lookup,
+
+	passwd_file_iterate_init,
+	passwd_file_iterate_next,
+	passwd_file_iterate_deinit
 };
 #else
 struct userdb_module_interface userdb_passwd_file = {
--- a/src/auth/userdb-passwd.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-passwd.c	Wed May 13 17:51:16 2009 -0400
@@ -5,6 +5,7 @@
 
 #ifdef USERDB_PASSWD
 
+#include "ioloop.h"
 #include "userdb-static.h"
 
 #include <pwd.h>
@@ -16,6 +17,14 @@
 	struct userdb_static_template *tmpl;
 };
 
+struct passwd_userdb_iterate_context {
+	struct userdb_iterate_context ctx;
+	struct passwd_userdb_iterate_context *next_waiting;
+};
+
+static struct passwd_userdb_iterate_context *cur_userdb_iter = NULL;
+static struct timeout *cur_userdb_iter_to = NULL;
+
 static void passwd_lookup(struct auth_request *auth_request,
 			  userdb_callback_t *callback)
 {
@@ -59,6 +68,72 @@
 	callback(USERDB_RESULT_OK, auth_request);
 }
 
+static struct userdb_iterate_context *
+passwd_iterate_init(struct auth_userdb *userdb,
+		    userdb_iter_callback_t *callback, void *context)
+{
+	struct passwd_userdb_iterate_context *ctx;
+
+	ctx = i_new(struct passwd_userdb_iterate_context, 1);
+	ctx->ctx.userdb = userdb->userdb;
+	ctx->ctx.callback = callback;
+	ctx->ctx.context = context;
+	setpwent();
+
+	if (cur_userdb_iter == NULL)
+		cur_userdb_iter = ctx;
+	return &ctx->ctx;
+}
+
+static void passwd_iterate_next(struct userdb_iterate_context *_ctx)
+{
+	struct passwd_userdb_iterate_context *ctx =
+		(struct passwd_userdb_iterate_context *)_ctx;
+	struct passwd *pw;
+
+	if (cur_userdb_iter != NULL && cur_userdb_iter != ctx) {
+		/* we can't support concurrent userdb iteration.
+		   wait until the previous one is done */
+		ctx->next_waiting = cur_userdb_iter->next_waiting;
+		cur_userdb_iter->next_waiting = ctx;
+		return;
+	}
+
+	errno = 0;
+	pw = getpwent();
+	if (pw == NULL) {
+		if (errno != 0) {
+			i_error("getpwent() failed: %m");
+			_ctx->failed = TRUE;
+		}
+		_ctx->callback(NULL, _ctx->context);
+	} else {
+		_ctx->callback(pw->pw_name, _ctx->context);
+	}
+}
+
+static void passwd_iterate_next_timeout(void *context ATTR_UNUSED)
+{
+	timeout_remove(&cur_userdb_iter_to);
+	passwd_iterate_next(&cur_userdb_iter->ctx);
+}
+
+static int passwd_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+	struct passwd_userdb_iterate_context *ctx =
+		(struct passwd_userdb_iterate_context *)_ctx;
+	int ret = _ctx->failed ? -1 : 0;
+
+	cur_userdb_iter = ctx->next_waiting;
+	i_free(ctx);
+
+	if (cur_userdb_iter != NULL) {
+		cur_userdb_iter_to =
+			timeout_add(0, passwd_iterate_next_timeout, NULL);
+	}
+	return ret;
+}
+
 static struct userdb_module *
 passwd_passwd_preinit(struct auth_userdb *auth_userdb, const char *args)
 {
@@ -85,7 +160,11 @@
 	NULL,
 	NULL,
 
-	passwd_lookup
+	passwd_lookup,
+
+	passwd_iterate_init,
+	passwd_iterate_next,
+	passwd_iterate_deinit
 };
 #else
 struct userdb_module_interface userdb_passwd = {
--- a/src/auth/userdb-prefetch.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-prefetch.c	Wed May 13 17:51:16 2009 -0400
@@ -47,7 +47,11 @@
 	NULL,
 	NULL,
 
-	prefetch_lookup
+	prefetch_lookup,
+
+	NULL,
+	NULL,
+	NULL
 };
 #else
 struct userdb_module_interface userdb_prefetch = {
--- a/src/auth/userdb-sql.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-sql.c	Wed May 13 17:51:16 2009 -0400
@@ -25,6 +25,16 @@
 	userdb_callback_t *callback;
 };
 
+struct sql_userdb_iterate_context {
+	struct userdb_iterate_context ctx;
+	struct sql_result *result;
+	unsigned int freed:1;
+	unsigned int call_iter:1;
+};
+
+static void userdb_sql_iterate_next(struct userdb_iterate_context *_ctx);
+static int userdb_sql_iterate_deinit(struct userdb_iterate_context *_ctx);
+
 static void
 sql_query_get_result(struct sql_result *result,
 		     struct auth_request *auth_request)
@@ -106,6 +116,115 @@
 		  sql_query_callback, sql_request);
 }
 
+static void sql_iter_query_callback(struct sql_result *sql_result,
+				    struct sql_userdb_iterate_context *ctx)
+{
+	ctx->result = sql_result;
+	sql_result_ref(sql_result);
+
+	if (ctx->freed)
+		userdb_sql_iterate_deinit(&ctx->ctx);
+	else if (ctx->call_iter)
+		userdb_sql_iterate_next(&ctx->ctx);
+}
+
+static struct userdb_iterate_context *
+userdb_sql_iterate_init(struct auth_userdb *userdb,
+			userdb_iter_callback_t *callback, void *context)
+{
+	struct sql_userdb_module *module =
+		(struct sql_userdb_module *)userdb;
+	struct sql_userdb_iterate_context *ctx;
+
+	ctx = i_new(struct sql_userdb_iterate_context, 1);
+	ctx->ctx.userdb = userdb->userdb;
+	ctx->ctx.callback = callback;
+	ctx->ctx.context = context;
+
+	sql_query(module->conn->db, module->conn->set.iterate_query,
+		  sql_iter_query_callback, ctx);
+	return &ctx->ctx;
+}
+
+static int userdb_sql_iterate_get_user(struct sql_userdb_iterate_context *ctx,
+				       const char **user_r)
+{
+	const char *domain;
+	int idx;
+
+	/* try user first */
+	idx = sql_result_find_field(ctx->result, "user");
+	if (idx == 0) {
+		*user_r = sql_result_get_field_value(ctx->result, idx);
+		return 0;
+	}
+
+	/* username [+ domain]? */
+	idx = sql_result_find_field(ctx->result, "username");
+	if (idx < 0) {
+		/* no user or username, fail */
+		return -1;
+	}
+
+	*user_r = sql_result_get_field_value(ctx->result, idx);
+	if (*user_r == NULL)
+		return 0;
+
+	domain = sql_result_find_field_value(ctx->result, "domain");
+	if (domain != NULL)
+		*user_r = t_strconcat(*user_r, "@", domain, NULL);
+	return 0;
+}
+
+static void userdb_sql_iterate_next(struct userdb_iterate_context *_ctx)
+{
+	struct sql_userdb_iterate_context *ctx =
+		(struct sql_userdb_iterate_context *)_ctx;
+	const char *user;
+	int ret;
+
+	if (ctx->result == NULL) {
+		/* query not finished yet */
+		ctx->call_iter = TRUE;
+		return;
+	}
+
+	ret = sql_result_next_row(ctx->result);
+	if (ret > 0) {
+		if (userdb_sql_iterate_get_user(ctx, &user) < 0)
+			i_error("sql: Iterate query didn't return 'user' field");
+		else if (user == NULL)
+			i_error("sql: Iterate query returned NULL user");
+		else {
+			_ctx->callback(user, _ctx->context);
+			return;
+		}
+		_ctx->failed = TRUE;
+	} else if (ret < 0) {
+		i_error("sql: Iterate query failed: %s",
+			sql_result_get_error(ctx->result));
+		_ctx->failed = TRUE;
+	}
+	_ctx->callback(NULL, _ctx->context);
+}
+
+static int userdb_sql_iterate_deinit(struct userdb_iterate_context *_ctx)
+{
+	struct sql_userdb_iterate_context *ctx =
+		(struct sql_userdb_iterate_context *)_ctx;
+	int ret = _ctx->failed ? -1 : 0;
+
+	if (ctx->result == NULL) {
+		/* sql query hasn't finished yet */
+		ctx->freed = TRUE;
+	} else {
+		if (ctx->result != NULL)
+			sql_result_unref(ctx->result);
+		i_free(ctx);
+	}
+	return ret;
+}
+
 static struct userdb_module *
 userdb_sql_preinit(struct auth_userdb *auth_userdb, const char *args)
 {
@@ -149,7 +268,11 @@
 	userdb_sql_init,
 	userdb_sql_deinit,
 
-	userdb_sql_lookup
+	userdb_sql_lookup,
+
+	userdb_sql_iterate_init,
+	userdb_sql_iterate_next,
+	userdb_sql_iterate_deinit
 };
 #else
 struct userdb_module_interface userdb_sql = {
--- a/src/auth/userdb-static.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-static.c	Wed May 13 17:51:16 2009 -0400
@@ -236,7 +236,11 @@
 	NULL,
 	NULL,
 
-	static_lookup
+	static_lookup,
+
+	NULL,
+	NULL,
+	NULL
 };
 #else
 struct userdb_module_interface userdb_static = {
--- a/src/auth/userdb-vpopmail.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb-vpopmail.c	Wed May 13 17:51:16 2009 -0400
@@ -170,7 +170,11 @@
 	NULL,
 	NULL,
 
-	vpopmail_lookup
+	vpopmail_lookup,
+
+	NULL,
+	NULL,
+	NULL
 };
 #else
 struct userdb_module_interface userdb_vpopmail = {
--- a/src/auth/userdb.c	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb.c	Wed May 13 17:51:16 2009 -0400
@@ -148,11 +148,6 @@
 {
 	if (userdb->userdb->iface->init != NULL)
 		userdb->userdb->iface->init(userdb->userdb, userdb->args);
-
-	if (userdb->userdb->blocking && !worker) {
-		/* blocking userdb - we need an auth server */
-		auth_worker_server_init(userdb->auth);
-	}
 }
 
 void userdb_deinit(struct auth_userdb *userdb)
--- a/src/auth/userdb.h	Wed May 13 12:40:58 2009 -0400
+++ b/src/auth/userdb.h	Wed May 13 17:51:16 2009 -0400
@@ -14,6 +14,8 @@
 
 typedef void userdb_callback_t(enum userdb_result result,
 			       struct auth_request *request);
+/* user=NULL when there are no more users */
+typedef void userdb_iter_callback_t(const char *user, void *context);
 
 struct userdb_module {
 	/* The caching key for this module, or NULL if caching isn't wanted. */
@@ -26,6 +28,13 @@
 	const struct userdb_module_interface *iface;
 };
 
+struct userdb_iterate_context {
+	struct userdb_module *userdb;
+	userdb_iter_callback_t *callback;
+	void *context;
+	bool failed;
+};
+
 struct userdb_module_interface {
 	const char *name;
 
@@ -36,6 +45,13 @@
 
 	void (*lookup)(struct auth_request *auth_request,
 		       userdb_callback_t *callback);
+
+	struct userdb_iterate_context *
+		(*iterate_init)(struct auth_userdb *userdb,
+				userdb_iter_callback_t *callback,
+				void *context);
+	void (*iterate_next)(struct userdb_iterate_context *ctx);
+	int (*iterate_deinit)(struct userdb_iterate_context *ctx);
 };
 
 uid_t userdb_parse_uid(struct auth_request *request, const char *str);