changeset 7050:0dcea80312b0 HEAD

LDAP handling rewrite. Reconnections are handled a lot better now. If connection is down, requests are added to queue and they always stay there at least 4 seconds.
author Timo Sirainen <tss@iki.fi>
date Sat, 29 Dec 2007 04:14:56 +0200
parents bbeee3db9967
children 1d37d2997220
files src/auth/db-ldap.c src/auth/db-ldap.h src/auth/passdb-ldap.c src/auth/userdb-ldap.c
diffstat 4 files changed, 532 insertions(+), 464 deletions(-) [+]
line wrap: on
line diff
--- a/src/auth/db-ldap.c	Sat Dec 29 04:11:59 2007 +0200
+++ b/src/auth/db-ldap.c	Sat Dec 29 04:14:56 2007 +0200
@@ -6,7 +6,9 @@
 
 #include "network.h"
 #include "ioloop.h"
+#include "array.h"
 #include "hash.h"
+#include "queue.h"
 #include "str.h"
 #include "var-expand.h"
 #include "settings.h"
@@ -42,10 +44,6 @@
 #  define LDAP_OPT_SUCCESS LDAP_SUCCESS
 #endif
 
-/* If server disconnects us, don't reconnect if no requests have been sent
-   for this many seconds. */
-#define LDAP_IDLE_RECONNECT_SECS 60
-
 struct db_ldap_result_iterate_context {
 	struct ldap_connection *conn;
 	LDAPMessage *entry;
@@ -63,6 +61,13 @@
 	unsigned int value_idx;
 };
 
+struct db_ldap_sasl_bind_context {
+	const char *authcid;
+	const char *passwd;
+	const char *realm;
+	const char *authzid;
+};
+
 #define DEF_STR(name) DEF_STRUCT_STR(name, ldap_settings)
 #define DEF_INT(name) DEF_STRUCT_INT(name, ldap_settings)
 #define DEF_BOOL(name) DEF_STRUCT_BOOL(name, ldap_settings)
@@ -118,7 +123,7 @@
 static struct ldap_connection *ldap_connections = NULL;
 
 static int db_ldap_bind(struct ldap_connection *conn);
-static void ldap_conn_close(struct ldap_connection *conn, bool flush_requests);
+static void db_ldap_conn_close(struct ldap_connection *conn);
 
 static int deref2str(const char *str)
 {
@@ -165,49 +170,14 @@
 	return ldap_err2string(ldap_get_errno(conn));
 }
 
-void db_ldap_add_delayed_request(struct ldap_connection *conn,
-				 struct ldap_request *request)
+static void ldap_conn_reconnect(struct ldap_connection *conn)
 {
-	request->next = NULL;
-
-	if (conn->delayed_requests_head == NULL)
-		conn->delayed_requests_head = request;
-	else
-		conn->delayed_requests_tail->next = request;
-	conn->delayed_requests_tail = request;
+	db_ldap_conn_close(conn);
+	if (db_ldap_connect(conn) < 0)
+		db_ldap_conn_close(conn);
 }
 
-static void db_ldap_handle_next_delayed_request(struct ldap_connection *conn)
-{
-	struct ldap_request *request;
-
-	if (conn->delayed_requests_head == NULL)
-		return;
-
-	request = conn->delayed_requests_head;
-	conn->delayed_requests_head = request->next;
-	if (conn->delayed_requests_head == NULL)
-		conn->delayed_requests_tail = NULL;
-
-	conn->retrying = TRUE;
-	if (request->filter == NULL)
-		request->callback(conn, request, NULL);
-	else
-		db_ldap_search(conn, request, conn->set.ldap_scope);
-	conn->retrying = FALSE;
-}
-
-static void ldap_conn_reconnect(struct ldap_connection *conn)
-{
-	ldap_conn_close(conn, FALSE);
-
-	if (db_ldap_connect(conn) < 0) {
-		/* failed to reconnect. fail all requests. */
-		ldap_conn_close(conn, TRUE);
-	}
-}
-
-static void ldap_handle_error(struct ldap_connection *conn)
+static int ldap_handle_error(struct ldap_connection *conn)
 {
 	int err = ldap_get_errno(conn);
 
@@ -229,7 +199,7 @@
 	case LDAP_ALIAS_DEREF_PROBLEM:
 	case LDAP_FILTER_ERROR:
 		/* invalid input */
-		break;
+		return -1;
 	case LDAP_SERVER_DOWN:
 	case LDAP_TIMEOUT:
 	case LDAP_UNAVAILABLE:
@@ -242,132 +212,285 @@
 	default:
 		/* connection problems */
 		ldap_conn_reconnect(conn);
-		break;
+		return 0;
 	}
 }
 
-void db_ldap_search(struct ldap_connection *conn, struct ldap_request *request,
-		    int scope)
+static int db_ldap_request_bind(struct ldap_connection *conn,
+				struct ldap_request *request)
+{
+	struct ldap_request_bind *brequest =
+		(struct ldap_request_bind *)request;
+
+	i_assert(request->type == LDAP_REQUEST_TYPE_BIND);
+	i_assert(request->msgid == -1);
+	i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND_AUTH ||
+		 conn->conn_state == LDAP_CONN_STATE_BOUND_DEFAULT);
+	i_assert(conn->pending_count == 0);
+
+	request->msgid = ldap_bind(conn->ld, brequest->dn,
+				   request->auth_request->mech_password,
+				   LDAP_AUTH_SIMPLE);
+	if (request->msgid == -1) {
+		auth_request_log_error(request->auth_request, "ldap",
+				       "ldap_bind(%s) failed: %s",
+				       brequest->dn, ldap_get_error(conn));
+		if (ldap_handle_error(conn) < 0) {
+			/* broken request, remove it */
+			return 0;
+		}
+		return -1;
+	}
+	conn->conn_state = LDAP_CONN_STATE_BINDING;
+	return 1;
+}
+
+static int db_ldap_request_search(struct ldap_connection *conn,
+				  struct ldap_request *request)
+{
+	struct ldap_request_search *srequest =
+		(struct ldap_request_search *)request;
+
+	i_assert(conn->conn_state == LDAP_CONN_STATE_BOUND_DEFAULT);
+	i_assert(request->msgid == -1);
+
+	request->msgid =
+		ldap_search(conn->ld, srequest->base, conn->set.ldap_scope,
+			    srequest->filter, srequest->attributes, 0);
+	if (request->msgid == -1) {
+		auth_request_log_error(request->auth_request, "ldap",
+				       "ldap_search() failed (filter %s): %s",
+				       srequest->filter, ldap_get_error(conn));
+		if (ldap_handle_error(conn) < 0) {
+			/* broken request, remove it */
+			return 0;
+		}
+		return -1;
+	}
+	return 1;
+}
+
+static bool db_ldap_request_queue_next(struct ldap_connection *conn)
 {
-	int try, msgid = -1;
+	struct ldap_request *const *requestp, *request;
+	unsigned int queue_size = queue_count(conn->request_queue);
+	int ret = -1;
+
+	if (conn->pending_count == queue_size) {
+		/* no non-pending requests */
+		return FALSE;
+	}
+	if (queue_size > DB_LDAP_MAX_PENDING_REQUESTS) {
+		/* wait until server has replied to some requests */
+		return FALSE;
+	}
+
+	if (db_ldap_connect(conn) < 0)
+		return FALSE;
+
+	requestp = array_idx(&conn->request_array,
+			     queue_idx(conn->request_queue,
+				       conn->pending_count));
+	request = *requestp;
+
+	if (conn->pending_count > 0 &&
+	    request->type == LDAP_REQUEST_TYPE_BIND) {
+		/* we can't do binds until all existing requests are finished */
+		return FALSE;
+	}
+
+	switch (conn->conn_state) {
+	case LDAP_CONN_STATE_DISCONNECTED:
+	case LDAP_CONN_STATE_BINDING:
+		/* wait until we're in bound state */
+		return FALSE;
+	case LDAP_CONN_STATE_BOUND_AUTH:
+		if (request->type == LDAP_REQUEST_TYPE_BIND)
+			break;
 
-	if (db_ldap_connect(conn) < 0) {
+		/* bind to default dn first */
+		i_assert(conn->pending_count == 0);
+		(void)db_ldap_bind(conn);
+		return FALSE;
+	case LDAP_CONN_STATE_BOUND_DEFAULT:
+		/* we can do anything in this state */
+		break;
+	}
+
+	switch (request->type) {
+	case LDAP_REQUEST_TYPE_BIND:
+		ret = db_ldap_request_bind(conn, request);
+		break;
+	case LDAP_REQUEST_TYPE_SEARCH:
+		ret = db_ldap_request_search(conn, request);
+		break;
+	}
+
+	if (ret > 0) {
+		/* success */
+		i_assert(request->msgid != -1);
+		conn->pending_count++;
+		return TRUE;
+	} else if (ret < 0) {
+		/* disconnected */
+		return FALSE;
+	} else {
+		/* broken request, remove from queue */
+		queue_delete_tail(conn->request_queue);
+		request->callback(conn, request, NULL);
+		return TRUE;
+	}
+}
+
+void db_ldap_request(struct ldap_connection *conn,
+		     struct ldap_request *request)
+{
+	request->msgid = -1;
+	request->create_time = ioloop_time;
+
+	if (conn->request_queue->full &&
+	    queue_count(conn->request_queue) >= DB_LDAP_MAX_QUEUE_SIZE) {
+		/* Queue is full already, fail this request */
+		auth_request_log_error(request->auth_request, "ldap",
+				       "Request queue is full");
 		request->callback(conn, request, NULL);
 		return;
 	}
 
-	for (try = 0; conn->connected && !conn->binding && try < 2; try++) {
-		if (conn->last_auth_bind) {
-			/* switch back to the default dn before doing the
-			   search request. */
-			if (db_ldap_bind(conn) < 0) {
-				request->callback(conn, request, NULL);
-				return;
-			}
-			break;
-		}
+	queue_append(conn->request_queue, &request);
+	(void)db_ldap_request_queue_next(conn);
+}
 
-		msgid = ldap_search(conn->ld, request->base, scope,
-				    request->filter, request->attributes, 0);
-		if (msgid != -1)
-			break;
-
-		i_error("LDAP: ldap_search() failed (filter %s): %s",
-			request->filter, ldap_get_error(conn));
-		ldap_handle_error(conn);
+static int db_ldap_connect_finish(struct ldap_connection *conn, int ret)
+{
+	if (ret == LDAP_SERVER_DOWN) {
+		i_error("LDAP: Can't connect to server: %s",
+			conn->set.uris != NULL ?
+			conn->set.uris : conn->set.hosts);
+		return -1;
+	}
+	if (ret != LDAP_SUCCESS) {
+		i_error("LDAP: binding failed (dn %s): %s",
+			conn->set.dn == NULL ? "(none)" : conn->set.dn,
+			ldap_get_error(conn));
+		return -1;
 	}
 
-	if (msgid != -1)
-		hash_insert(conn->requests, POINTER_CAST(msgid), request);
-	else
-		db_ldap_add_delayed_request(conn, request);
-	conn->last_request_stamp = ioloop_time;
+	conn->conn_state = LDAP_CONN_STATE_BOUND_DEFAULT;
+	while (db_ldap_request_queue_next(conn))
+		;
+	return 0;
+}
+
+static void db_ldap_default_bind_finished(struct ldap_connection *conn,
+					  LDAPMessage *res)
+{
+	int ret;
+
+	i_assert(conn->pending_count == 0);
+	conn->default_bind_msgid = -1;
+
+	ret = ldap_result2error(conn->ld, res, FALSE);
+	if (db_ldap_connect_finish(conn, ret) < 0) {
+		/* lost connection, close it */
+		db_ldap_conn_close(conn);
+	}
 }
 
-static void ldap_conn_retry_requests(struct ldap_connection *conn)
+static void db_ldap_abort_requests(struct ldap_connection *conn,
+				   unsigned int max_count,
+				   unsigned int timeout_secs)
 {
-	struct hash_table *old_requests;
-        struct hash_iterate_context *iter;
-	struct ldap_request *request, **p, *next;
-	void *key, *value;
-	bool have_hash_binds = FALSE;
+	struct ldap_request *const *requestp, *request;
+	time_t diff;
 
-	i_assert(conn->connected);
+	while (queue_count(conn->request_queue) > 0 && max_count > 0) {
+		requestp = array_idx(&conn->request_array,
+				     queue_idx(conn->request_queue, 0));
+		request = *requestp;
 
-	if (hash_count(conn->requests) == 0 &&
-	    conn->delayed_requests_head == NULL)
-		return;
+		diff = ioloop_time - request->create_time;
+		if (diff < (time_t)timeout_secs)
+			break;
+
+		/* timed out, abort */
+		queue_delete_tail(conn->request_queue);
 
-	old_requests = conn->requests;
-	conn->requests = hash_create(default_pool, conn->pool, 0, NULL, NULL);
+		if (request->msgid != -1) {
+			i_assert(conn->pending_count > 0);
+			conn->pending_count--;
+		}
+		request->callback(conn, request, NULL);
+		max_count--;
+	}
+}
+
+static void
+db_ldap_handle_result(struct ldap_connection *conn, LDAPMessage *res)
+{
+	struct ldap_request *const *requests, *request = NULL;
+	unsigned int i, count;
+	int msgid, ret;
 
-	conn->retrying = TRUE;
-	/* first retry all the search requests */
-	iter = hash_iterate_init(old_requests);
-	while (hash_iterate(iter, &key, &value)) {
-		request = value;
+	msgid = ldap_msgid(res);
+	if (msgid == conn->default_bind_msgid) {
+		db_ldap_default_bind_finished(conn, res);
+		return;
+	}
 
-		if (request->filter == NULL) {
-			/* bind request */
-			have_hash_binds = TRUE;
-		} else {
-			i_assert(conn->connected);
-			db_ldap_search(conn, request, conn->set.ldap_scope);
+	count = queue_count(conn->request_queue);
+	requests = count == 0 ? NULL : array_idx(&conn->request_array, 0);
+	for (i = 0; i < count; i++) {
+		request = requests[queue_idx(conn->request_queue, i)];
+		if (request->msgid == msgid)
+			break;
+		if (request->msgid == -1) {
+			request = NULL;
+			break;
 		}
 	}
-	hash_iterate_deinit(&iter);
-
-	/* then delayed search requests */
-	p = &conn->delayed_requests_head;
-	while (*p != NULL) {
-		request = *p;
-
-		if (request->filter != NULL) {
-			*p = request->next;
-
-			i_assert(conn->connected);
-			db_ldap_search(conn, request, conn->set.ldap_scope);
-		} else {
-			p = &(*p)->next;
-		}
+	if (request == NULL) {
+		i_error("LDAP: Reply with unknown msgid %d", msgid);
+		return;
 	}
 
-	if (have_hash_binds && conn->set.auth_bind) {
-		/* next retry all the bind requests. without auth binds the
-		   only bind request can be the initial connection binding,
-		   which we don't care to retry. */
-		iter = hash_iterate_init(old_requests);
-		while (hash_iterate(iter, &key, &value)) {
-			request = value;
-
-			if (request->filter == NULL)
-				request->callback(conn, request, NULL);
-		}
-		hash_iterate_deinit(&iter);
+	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;
 	}
-	if (conn->delayed_requests_head != NULL && conn->set.auth_bind) {
-		request = conn->delayed_requests_head;
-		for (; request != NULL; request = next) {
-			next = request->next;
+	i_assert(conn->pending_count > 0);
+	conn->pending_count--;
+	queue_delete(conn->request_queue, i);
+
+	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 =
+			(struct ldap_request_search *)request;
 
-			i_assert(request->filter == NULL);
-			request->callback(conn, request, NULL);
-		}
-		conn->delayed_requests_head = NULL;
+		auth_request_log_error(request->auth_request, "ldap",
+				       "ldap_search(%s) failed: %s",
+				       srequest->filter, ldap_err2string(ret));
+		res = NULL;
 	}
-	hash_destroy(&old_requests);
+
+	T_FRAME(
+		request->callback(conn, request, res);
+	);
 
-	i_assert(conn->delayed_requests_head == NULL);
-	conn->delayed_requests_tail = NULL;
-	conn->retrying = FALSE;
+	if (i > 0) {
+		/* see if there are timed out requests */
+		db_ldap_abort_requests(conn, i,
+				       DB_LDAP_REQUEST_LOST_TIMEOUT_SECS);
+	}
 }
 
 static void ldap_input(struct ldap_connection *conn)
 {
-        struct ldap_request *request;
 	struct timeval timeout;
 	LDAPMessage *res;
-	int ret, msgid;
+	int ret;
 
 	for (;;) {
 		if (conn->ld == NULL)
@@ -385,36 +508,27 @@
 		if (ret <= 0)
 			break;
 
-		msgid = ldap_msgid(res);
-		request = hash_lookup(conn->requests, POINTER_CAST(msgid));
-		if (request == NULL) {
-			i_error("LDAP: Reply with unknown msgid %d",
-				msgid);
-		} else {
-			hash_remove(conn->requests, POINTER_CAST(msgid));
-			T_FRAME(
-				request->callback(conn, request, res);
-			);
-		}
-
+		db_ldap_handle_result(conn, res);
 		ldap_msgfree(res);
 	}
+	conn->last_reply_stamp = ioloop_time;
 
 	if (ret == 0) {
-		if (!conn->binding)
-			db_ldap_handle_next_delayed_request(conn);
+		/* send more requests */
+		while (db_ldap_request_queue_next(conn))
+			;
 	} else if (ldap_get_errno(conn) != LDAP_SERVER_DOWN) {
 		i_error("LDAP: ldap_result() failed: %s", ldap_get_error(conn));
 		ldap_conn_reconnect(conn);
-	} else if (hash_count(conn->requests) > 0 ||
-		   ioloop_time - conn->last_request_stamp <
-		   				LDAP_IDLE_RECONNECT_SECS) {
+	} else if (queue_count(conn->request_queue) > 0 ||
+		   ioloop_time - conn->last_reply_stamp <
+		   				DB_LDAP_IDLE_RECONNECT_SECS) {
 		i_error("LDAP: Connection lost to LDAP server, reconnecting");
 		ldap_conn_reconnect(conn);
 	} else {
 		/* server probably disconnected an idle connection. don't
 		   reconnect until the next request comes. */
-		ldap_conn_close(conn, TRUE);
+		db_ldap_conn_close(conn);
 	}
 }
 
@@ -423,7 +537,7 @@
 sasl_interact(LDAP *ld ATTR_UNUSED, unsigned flags ATTR_UNUSED,
 	      void *defaults, void *interact)
 {
-	struct ldap_sasl_bind_context *context = defaults;
+	struct db_ldap_sasl_bind_context *context = defaults;
 	sasl_interact_t *in;
 	const char *str;
 
@@ -455,82 +569,27 @@
 }
 #endif
 
-static int db_ldap_connect_finish(struct ldap_connection *conn, int ret)
-{
-	if (ret == LDAP_SERVER_DOWN) {
-		i_error("LDAP: Can't connect to server: %s",
-			conn->set.uris != NULL ?
-			conn->set.uris : conn->set.hosts);
-		return -1;
-	}
-	if (ret != LDAP_SUCCESS) {
-		i_error("LDAP: binding failed (dn %s): %s",
-			conn->set.dn == NULL ? "(none)" : conn->set.dn,
-			ldap_get_error(conn));
-		return -1;
-	}
-
-	if (!conn->connected) {
-		conn->connected = TRUE;
-
-		/* in case there are requests waiting, retry them */
-		ldap_conn_retry_requests(conn);
-	}
-	return 0;
-}
-
-static void db_ldap_bind_callback(struct ldap_connection *conn,
-				  struct ldap_request *ldap_request,
-				  LDAPMessage *res)
-{
-	int ret;
-
-	conn->binding = FALSE;
-	conn->connecting = FALSE;
-	i_free(ldap_request);
-
-	if (res == NULL) {
-		/* aborted */
-		return;
-	}
-
-	ret = ldap_result2error(conn->ld, res, FALSE);
-	if (db_ldap_connect_finish(conn, ret) < 0) {
-		/* lost connection, close it */
-		ldap_conn_close(conn, TRUE);
-	}
-}
-
 static int db_ldap_bind(struct ldap_connection *conn)
 {
-	struct ldap_request *ldap_request;
 	int msgid;
 
-	i_assert(!conn->binding);
-
-	ldap_request = i_new(struct ldap_request, 1);
-	ldap_request->callback = db_ldap_bind_callback;
-	ldap_request->context = conn;
+	i_assert(conn->conn_state != LDAP_CONN_STATE_BINDING);
+	i_assert(conn->default_bind_msgid == -1);
+	i_assert(conn->pending_count == 0);
 
 	msgid = ldap_bind(conn->ld, conn->set.dn, conn->set.dnpass,
 			  LDAP_AUTH_SIMPLE);
 	if (msgid == -1) {
+		i_assert(ldap_get_errno(conn) != LDAP_SUCCESS);
 		if (db_ldap_connect_finish(conn, ldap_get_errno(conn)) < 0) {
 			/* lost connection, close it */
-			ldap_conn_close(conn, TRUE);
+			db_ldap_conn_close(conn);
 		}
-		i_free(ldap_request);
 		return -1;
 	}
 
-	conn->connecting = TRUE;
-	conn->binding = TRUE;
-	hash_insert(conn->requests, POINTER_CAST(msgid), ldap_request);
-
-	/* we're binding back to the original DN, not doing an
-	   authentication bind */
-	conn->last_auth_bind = FALSE;
-	conn->last_request_stamp = ioloop_time;
+	conn->conn_state = LDAP_CONN_STATE_BINDING;
+	conn->default_bind_msgid = msgid;
 	return 0;
 }
 
@@ -558,11 +617,10 @@
 	unsigned int ldap_version;
 	int ret;
 
-	if (conn->connected || conn->connecting)
+	if (conn->conn_state != LDAP_CONN_STATE_DISCONNECTED)
 		return 0;
-	i_assert(!conn->binding);
 
-	conn->last_request_stamp = ioloop_time;
+	i_assert(conn->pending_count == 0);
 	if (conn->ld == NULL) {
 		if (conn->set.uris != NULL) {
 #ifdef LDAP_HAVE_INITIALIZE
@@ -615,7 +673,7 @@
 
 	if (conn->set.sasl_bind) {
 #ifdef HAVE_LDAP_SASL
-		struct ldap_sasl_bind_context context;
+		struct db_ldap_sasl_bind_context context;
 
 		memset(&context, 0, sizeof(context));
 		context.authcid = conn->set.dn;
@@ -631,49 +689,50 @@
 						   sasl_interact, &context);
 		if (db_ldap_connect_finish(conn, ret) < 0)
 			return -1;
-		db_ldap_get_fd(conn);
 #else
 		i_fatal("LDAP: sasl_bind=yes but no SASL support compiled in");
 #endif
+		conn->conn_state = LDAP_CONN_STATE_BOUND_DEFAULT;
 	} else {
 		if (db_ldap_bind(conn) < 0)
 			return -1;
-		db_ldap_get_fd(conn);
 	}
 
+	db_ldap_get_fd(conn);
 	conn->io = io_add(conn->fd, IO_READ, ldap_input, conn);
 	return 0;
 }
 
-static void ldap_conn_close(struct ldap_connection *conn, bool flush_requests)
+static void db_ldap_disconnect_timeout(struct ldap_connection *conn)
 {
-	struct hash_iterate_context *iter;
-	struct ldap_request *request, *next;
-	void *key, *value;
+	db_ldap_abort_requests(conn, -1U,
+			       DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS);
+
+	if (queue_count(conn->request_queue) == 0) {
+		/* no requests left, remove this timeout handler */
+		timeout_remove(&conn->to);
+	}
+}
 
-	if (flush_requests) {
-		iter = hash_iterate_init(conn->requests);
-		while (hash_iterate(iter, &key, &value)) {
-			request = value;
+static void db_ldap_conn_close(struct ldap_connection *conn)
+{
+	struct ldap_request *const *requests, *request;
+	unsigned int i;
+
+	conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+	conn->default_bind_msgid = -1;
 
-			request->callback(conn, request, NULL);
+	if (conn->pending_count != 0) {
+		requests = array_idx(&conn->request_array, 0);
+		for (i = 0; i < conn->pending_count; i++) {
+			request = requests[queue_idx(conn->request_queue, i)];
+
+			i_assert(request->msgid != -1);
+			request->msgid = -1;
 		}
-		hash_iterate_deinit(&iter);
-		hash_clear(conn->requests, FALSE);
-
-		request = conn->delayed_requests_head;
-		for (; request != NULL; request = next) {
-			next = request->next;
-
-			request->callback(conn, request, NULL);
-		}
-		conn->delayed_requests_head = NULL;
-		conn->delayed_requests_tail = NULL;
+		conn->pending_count = 0;
 	}
 
-	conn->connected = FALSE;
-	conn->binding = FALSE;
-
 	if (conn->io != NULL)
 		io_remove(&conn->io);
 
@@ -682,6 +741,14 @@
 		conn->ld = NULL;
 	}
 	conn->fd = -1;
+
+	if (queue_count(conn->request_queue) == 0) {
+		if (conn->to != NULL)
+			timeout_remove(&conn->to);
+	} else if (conn->to == NULL) {
+		conn->to = timeout_add(DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS *
+				       1000/2, db_ldap_disconnect_timeout, conn);
+	}
 }
 
 void db_ldap_set_attrs(struct ldap_connection *conn, const char *attrlist,
@@ -980,10 +1047,10 @@
 	pool = pool_alloconly_create("ldap_connection", 1024);
 	conn = p_new(pool, struct ldap_connection, 1);
 	conn->pool = pool;
+	conn->refcount = 1;
 
-	conn->refcount = 1;
-	conn->requests = hash_create(default_pool, pool, 0, NULL, NULL);
-
+	conn->conn_state = LDAP_CONN_STATE_DISCONNECTED;
+	conn->default_bind_msgid = -1;
 	conn->fd = -1;
 	conn->config_path = p_strdup(pool, config_path);
 	conn->set = default_ldap_settings;
@@ -1006,6 +1073,9 @@
         conn->set.ldap_deref = deref2str(conn->set.deref);
 	conn->set.ldap_scope = scope2str(conn->set.scope);
 
+	i_array_init(&conn->request_array, DB_LDAP_MAX_QUEUE_SIZE);
+	conn->request_queue = queue_init(&conn->request_array.arr);
+
 	conn->next = ldap_connections;
         ldap_connections = conn;
 	return conn;
@@ -1028,9 +1098,14 @@
 		}
 	}
 
-	ldap_conn_close(conn, TRUE);
+	db_ldap_abort_requests(conn, -1U, 0);
+	i_assert(conn->pending_count == 0);
+	db_ldap_conn_close(conn);
+	i_assert(conn->to == NULL);
 
-	hash_destroy(&conn->requests);
+	array_free(&conn->request_array);
+	queue_deinit(&conn->request_queue);
+
 	if (conn->pass_attr_map != NULL)
 		hash_destroy(&conn->pass_attr_map);
 	if (conn->user_attr_map != NULL)
--- a/src/auth/db-ldap.h	Sat Dec 29 04:11:59 2007 +0200
+++ b/src/auth/db-ldap.h	Sat Dec 29 04:14:56 2007 +0200
@@ -5,6 +5,19 @@
    This define enables them until the code here can be refactored */
 #define LDAP_DEPRECATED 1
 
+/* Maximum number of requests in queue. After this new requests are dropped. */
+#define DB_LDAP_MAX_QUEUE_SIZE 1024
+/* Maximum number of pending requests before delaying new requests. */
+#define DB_LDAP_MAX_PENDING_REQUESTS 128
+/* If LDAP connection is down, fail requests after waiting for this long. */
+#define DB_LDAP_REQUEST_DISCONNECT_TIMEOUT_SECS 4
+/* If request is still in queue after this many seconds and other requests
+   have been replied, assume the request was lost and abort it. */
+#define DB_LDAP_REQUEST_LOST_TIMEOUT_SECS 60
+/* If server disconnects us, don't reconnect if no requests have been sent
+   for this many seconds. */
+#define DB_LDAP_IDLE_RECONNECT_SECS 60
+
 #include <ldap.h>
 
 struct auth_request;
@@ -47,6 +60,48 @@
 	gid_t gid;
 };
 
+enum ldap_request_type {
+	LDAP_REQUEST_TYPE_SEARCH,
+	LDAP_REQUEST_TYPE_BIND
+};
+
+struct ldap_request {
+	enum ldap_request_type type;
+
+	/* msgid for sent requests, -1 if not sent */
+	int msgid;
+	/* timestamp when request was created */
+	time_t create_time;
+
+	db_search_callback_t *callback;
+	struct auth_request *auth_request;
+};
+
+struct ldap_request_search {
+	struct ldap_request request;
+
+	const char *base;
+	const char *filter;
+	char **attributes; /* points to pass_attr_names / user_attr_names */
+};
+
+struct ldap_request_bind {
+	struct ldap_request request;
+
+	const char *dn;
+};
+
+enum ldap_connection_state {
+	/* Not connected */
+	LDAP_CONN_STATE_DISCONNECTED,
+	/* Binding - either to default dn or doing auth bind */
+	LDAP_CONN_STATE_BINDING,
+	/* Bound to auth dn */
+	LDAP_CONN_STATE_BOUND_AUTH,
+	/* Bound to default dn */
+	LDAP_CONN_STATE_BOUND_DEFAULT
+};
+
 struct ldap_connection {
 	struct ldap_connection *next;
 
@@ -57,46 +112,30 @@
         struct ldap_settings set;
 
 	LDAP *ld;
-	int fd; /* only set when connected/connecting */
+	enum ldap_connection_state conn_state;
+	int default_bind_msgid;
+
+	int fd;
 	struct io *io;
+	struct timeout *to;
 
-	struct hash_table *requests;
-	struct ldap_request *delayed_requests_head, *delayed_requests_tail;
-	time_t last_request_stamp;
+	/* Request queue contains sent requests at tail (msgid != -1) and
+	   queued requests at head (msgid == -1). */
+	struct queue *request_queue;
+	ARRAY_DEFINE(request_array, struct ldap_request *);
+	/* Number of messages in queue with msgid != -1 */
+	unsigned int pending_count;
+
+	/* 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;
-
-	unsigned int connected:1;
-	unsigned int connecting:1;
-	unsigned int binding:1;
-	unsigned int retrying:1; /* just reconnected, resending requests */
-	unsigned int last_auth_bind:1;
 };
 
-struct ldap_request {
-	struct ldap_request *next; /* in conn->delayed_requests */
-
-	db_search_callback_t *callback;
-	void *context;
-
-	/* for bind requests, base contains the DN and filter=NULL */
-	const char *base;
-	const char *filter;
-	char **attributes; /* points to pass_attr_names / user_attr_names */
-};
-
-struct ldap_sasl_bind_context {
-	const char *authcid;
-	const char *passwd;
-	const char *realm;
-	const char *authzid;
-};
-
-void db_ldap_add_delayed_request(struct ldap_connection *conn,
-				 struct ldap_request *request);
-void db_ldap_search(struct ldap_connection *conn, struct ldap_request *request,
-		    int scope);
+/* Send/queue request */
+void db_ldap_request(struct ldap_connection *conn,
+		     struct ldap_request *request);
 
 void db_ldap_set_attrs(struct ldap_connection *conn, const char *attrlist,
 		       char ***attr_names_r, struct hash_table *attr_map,
--- a/src/auth/passdb-ldap.c	Sat Dec 29 04:11:59 2007 +0200
+++ b/src/auth/passdb-ldap.c	Sat Dec 29 04:14:56 2007 +0200
@@ -23,7 +23,11 @@
 };
 
 struct passdb_ldap_request {
-	struct ldap_request request;
+	union {
+		struct ldap_request ldap;
+		struct ldap_request_search search;
+		struct ldap_request_bind bind;
+	} request;
 
 	union {
 		verify_plain_callback_t *verify_plain;
@@ -38,30 +42,24 @@
 {
 	enum passdb_result passdb_result;
 	LDAPMessage *entry;
-	int ret;
 
 	passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
 
 	if (res != NULL) {
-		/* LDAP query returned something */
-		ret = ldap_result2error(conn->ld, res, 0);
-		if (ret != LDAP_SUCCESS) {
-			auth_request_log_error(auth_request, "ldap",
-					       "ldap_search(%s) failed: %s",
-					       request->request.filter,
-					       ldap_err2string(ret));
+		/* 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 {
-			/* get the reply */
-			entry = ldap_first_entry(conn->ld, res);
-			if (entry != NULL) {
+			if (ldap_next_entry(conn->ld, entry) == NULL) {
 				/* success */
 				return entry;
 			}
 
-			/* no entries returned */
-			auth_request_log_info(auth_request, "ldap",
-					      "unknown user");
-			passdb_result = PASSDB_RESULT_USER_UNKNOWN;
+			auth_request_log_error(auth_request, "ldap",
+				"Multiple replies found for user");
 		}
 	}
 
@@ -90,12 +88,13 @@
 	}
 }
 
-static void handle_request(struct ldap_connection *conn,
-			   struct ldap_request *request, LDAPMessage *res)
+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->context;
+        struct auth_request *auth_request = request->auth_request;
 	enum passdb_result passdb_result;
 	LDAPMessage *entry;
 	const char *password, *scheme;
@@ -161,73 +160,16 @@
 	auth_request_unref(&auth_request);
 }
 
-static void authbind_start(struct ldap_connection *conn,
-			   struct ldap_request *ldap_request)
-{
-	struct passdb_ldap_request *passdb_ldap_request =
-		(struct passdb_ldap_request *)ldap_request;
-	struct auth_request *auth_request = ldap_request->context;
-	int msgid;
-
-	i_assert(ldap_request->base != NULL);
-
-	if (*auth_request->mech_password == '\0') {
-		/* Assume that empty password fails. This is especially
-		   important with Windows 2003 AD, which always returns success
-		   with empty passwords. */
-		auth_request_log_info(auth_request, "ldap",
-				      "Login attempt with empty password");
-		passdb_ldap_request->callback.
-			verify_plain(PASSDB_RESULT_PASSWORD_MISMATCH,
-				     auth_request);
-		return;
-	}
-
-	if (conn->connected && hash_count(conn->requests) == 0) {
-		/* switch back to the default dn before doing the next search
-		   request */
-		conn->last_auth_bind = TRUE;
-		i_assert(!conn->binding);
-
-		/* the DN is kept in base variable, a bit ugly.. */
-		msgid = ldap_bind(conn->ld, ldap_request->base,
-				  auth_request->mech_password,
-				  LDAP_AUTH_SIMPLE);
-		if (msgid == -1) {
-			auth_request_log_error(auth_request, "ldap",
-				"ldap_bind(%s) failed: %s",
-				ldap_request->base, ldap_get_error(conn));
-			passdb_ldap_request->callback.
-				verify_plain(PASSDB_RESULT_INTERNAL_FAILURE,
-					     auth_request);
-			return;
-		}
-
-		conn->binding = TRUE;
-		conn->last_request_stamp = ioloop_time;
-		hash_insert(conn->requests, POINTER_CAST(msgid), ldap_request);
-
-		auth_request_log_debug(auth_request, "ldap", "bind: dn=%s",
-				       ldap_request->base);
-	} else {
-		db_ldap_add_delayed_request(conn, ldap_request);
-	}
-
-	/* Bind started */
-	auth_request_ref(auth_request);
-}
-
 static void
-handle_request_authbind(struct ldap_connection *conn,
+ldap_auth_bind_callback(struct ldap_connection *conn,
 			struct ldap_request *ldap_request, LDAPMessage *res)
 {
 	struct passdb_ldap_request *passdb_ldap_request =
 		(struct passdb_ldap_request *)ldap_request;
-	struct auth_request *auth_request = ldap_request->context;
+	struct auth_request *auth_request = ldap_request->auth_request;
 	enum passdb_result passdb_result;
 	int ret;
 
-	conn->binding = FALSE;
 	passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
 
 	if (res != NULL) {
@@ -245,24 +187,42 @@
 		}
 	}
 
-	if (conn->retrying && res == NULL) {
-		/* reconnected, retry binding */
-		authbind_start(conn, ldap_request);
-	} else {
-		passdb_ldap_request->callback.
-			verify_plain(passdb_result, auth_request);
-	}
+	passdb_ldap_request->callback.
+		verify_plain(passdb_result, auth_request);
         auth_request_unref(&auth_request);
 }
 
-static void
-handle_request_authbind_search(struct ldap_connection *conn,
-			       struct ldap_request *ldap_request,
-			       LDAPMessage *res)
+static void ldap_auth_bind(struct ldap_connection *conn,
+			   struct ldap_request_bind *brequest)
+{
+	struct passdb_ldap_request *passdb_ldap_request =
+		(struct passdb_ldap_request *)brequest;
+	struct auth_request *auth_request = brequest->request.auth_request;
+
+	if (*auth_request->mech_password == '\0') {
+		/* Assume that empty password fails. This is especially
+		   important with Windows 2003 AD, which always returns success
+		   with empty passwords. */
+		auth_request_log_info(auth_request, "ldap",
+				      "Login attempt with empty password");
+		passdb_ldap_request->callback.
+			verify_plain(PASSDB_RESULT_PASSWORD_MISMATCH,
+				     auth_request);
+		return;
+	}
+
+	brequest->request.callback = ldap_auth_bind_callback;
+	db_ldap_request(conn, &brequest->request);
+}
+
+static void ldap_bind_lookup_dn_callback(struct ldap_connection *conn,
+					 struct ldap_request *ldap_request,
+					 LDAPMessage *res)
 {
 	struct passdb_ldap_request *passdb_ldap_request =
 		(struct passdb_ldap_request *)ldap_request;
-	struct auth_request *auth_request = ldap_request->context;
+	struct ldap_request_bind *brequest;
+	struct auth_request *auth_request = ldap_request->auth_request;
 	LDAPMessage *entry;
 	char *dn;
 
@@ -273,111 +233,109 @@
 
 	ldap_query_save_result(conn, entry, auth_request);
 
+	/* convert search request to bind request */
+	brequest = &passdb_ldap_request->request.bind;
+	memset(brequest, 0, sizeof(*brequest));
+	brequest->request.type = LDAP_REQUEST_TYPE_BIND;
+	brequest->request.auth_request = auth_request;
+
 	/* switch the handler to the authenticated bind handler */
 	dn = ldap_get_dn(conn->ld, entry);
-	ldap_request->base = p_strdup(auth_request->pool, dn);
+	brequest->dn = p_strdup(auth_request->pool, dn);
 	ldap_memfree(dn);
 
-	ldap_request->filter = NULL;
-	ldap_request->callback = handle_request_authbind;
-
-        authbind_start(conn, ldap_request);
-	auth_request_unref(&auth_request);
+	ldap_auth_bind(conn, brequest);
 }
 
 static void ldap_lookup_pass(struct auth_request *auth_request,
-			     struct ldap_request *ldap_request)
+			     struct passdb_ldap_request *request)
 {
 	struct passdb_module *_module = auth_request->passdb->passdb;
 	struct ldap_passdb_module *module =
 		(struct ldap_passdb_module *)_module;
 	struct ldap_connection *conn = module->conn;
-        const struct var_expand_table *vars;
+	struct ldap_request_search *srequest = &request->request.search;
+	const struct var_expand_table *vars;
 	const char **attr_names = (const char **)conn->pass_attr_names;
 	string_t *str;
 
+	srequest->request.type = LDAP_REQUEST_TYPE_SEARCH;
 	vars = auth_request_get_var_expand_table(auth_request, ldap_escape);
 
 	str = t_str_new(512);
 	var_expand(str, conn->set.base, vars);
-	ldap_request->base = p_strdup(auth_request->pool, str_c(str));
+	srequest->base = p_strdup(auth_request->pool, str_c(str));
 
 	str_truncate(str, 0);
 	var_expand(str, conn->set.pass_filter, vars);
-	ldap_request->filter = p_strdup(auth_request->pool, str_c(str));
-
-	auth_request_ref(auth_request);
-	ldap_request->callback = handle_request;
-	ldap_request->context = auth_request;
-	ldap_request->attributes = conn->pass_attr_names;
+	srequest->filter = p_strdup(auth_request->pool, str_c(str));
+	srequest->attributes = conn->pass_attr_names;
 
 	auth_request_log_debug(auth_request, "ldap", "pass search: "
 			       "base=%s scope=%s filter=%s fields=%s",
-			       ldap_request->base, conn->set.scope,
-			       ldap_request->filter,
-			       attr_names == NULL ? "(all)" :
+			       srequest->base, conn->set.scope,
+			       srequest->filter, attr_names == NULL ? "(all)" :
 			       t_strarray_join(attr_names, ","));
 
-	db_ldap_search(conn, ldap_request, conn->set.ldap_scope);
+	srequest->request.callback = ldap_lookup_pass_callback;
+	db_ldap_request(conn, &srequest->request);
 }
 
-static void
-ldap_verify_plain_auth_bind_userdn(struct auth_request *auth_request,
-				   struct ldap_request *ldap_request)
+static void ldap_bind_lookup_dn(struct auth_request *auth_request,
+				struct passdb_ldap_request *request)
 {
 	struct passdb_module *_module = auth_request->passdb->passdb;
 	struct ldap_passdb_module *module =
 		(struct ldap_passdb_module *)_module;
 	struct ldap_connection *conn = module->conn;
-        const struct var_expand_table *vars;
-	string_t *dn;
+	struct ldap_request_search *srequest = &request->request.search;
+	const struct var_expand_table *vars;
+	string_t *str;
 
+	srequest->request.type = LDAP_REQUEST_TYPE_SEARCH;
 	vars = auth_request_get_var_expand_table(auth_request, ldap_escape);
-	dn = t_str_new(512);
-	var_expand(dn, conn->set.auth_bind_userdn, vars);
+
+	str = t_str_new(512);
+	var_expand(str, conn->set.base, vars);
+	srequest->base = p_strdup(auth_request->pool, str_c(str));
+
+	str_truncate(str, 0);
+	var_expand(str, conn->set.pass_filter, vars);
+	srequest->filter = p_strdup(auth_request->pool, str_c(str));
 
-	ldap_request->callback = handle_request_authbind;
-	ldap_request->context = auth_request;
+	/* we don't need the attributes to perform authentication, but they
+	   may contain some extra parameters. if a password is returned,
+	   it's just ignored. */
+	srequest->attributes = conn->pass_attr_names;
 
-	ldap_request->base = p_strdup(auth_request->pool, str_c(dn));
-        authbind_start(conn, ldap_request);
+	auth_request_log_debug(auth_request, "ldap",
+			       "bind search: base=%s filter=%s",
+			       srequest->base, srequest->filter);
+
+	srequest->request.callback = ldap_bind_lookup_dn_callback;
+        db_ldap_request(conn, &srequest->request);
 }
 
 static void
-ldap_verify_plain_authbind(struct auth_request *auth_request,
-			   struct ldap_request *ldap_request)
+ldap_verify_plain_auth_bind_userdn(struct auth_request *auth_request,
+				   struct passdb_ldap_request *request)
 {
 	struct passdb_module *_module = auth_request->passdb->passdb;
 	struct ldap_passdb_module *module =
 		(struct ldap_passdb_module *)_module;
 	struct ldap_connection *conn = module->conn;
-	const struct var_expand_table *vars;
-	string_t *str;
+	struct ldap_request_bind *brequest = &request->request.bind;
+        const struct var_expand_table *vars;
+	string_t *dn;
+
+	brequest->request.type = LDAP_REQUEST_TYPE_BIND;
 
 	vars = auth_request_get_var_expand_table(auth_request, ldap_escape);
-
-	str = t_str_new(512);
-	var_expand(str, conn->set.base, vars);
-	ldap_request->base = p_strdup(auth_request->pool, str_c(str));
-
-	str_truncate(str, 0);
-	var_expand(str, conn->set.pass_filter, vars);
-	ldap_request->filter = p_strdup(auth_request->pool, str_c(str));
+	dn = t_str_new(512);
+	var_expand(dn, conn->set.auth_bind_userdn, vars);
 
-	/* we don't need the attributes to perform authentication, but they
-	   may contain some extra parameters. if a password is returned,
-	   it's just ignored. */
-	ldap_request->attributes = conn->pass_attr_names;
-
-	auth_request_ref(auth_request);
-	ldap_request->context = auth_request;
-	ldap_request->callback = handle_request_authbind_search;
-
-	auth_request_log_debug(auth_request, "ldap",
-			       "bind search: base=%s filter=%s",
-			       ldap_request->base, ldap_request->filter);
-
-        db_ldap_search(conn, ldap_request, LDAP_SCOPE_SUBTREE);
+	brequest->dn = p_strdup(auth_request->pool, str_c(dn));
+        ldap_auth_bind(conn, brequest);
 }
 
 static void
@@ -401,12 +359,15 @@
 	ldap_request = p_new(request->pool, struct passdb_ldap_request, 1);
 	ldap_request->callback.verify_plain = callback;
 
+	auth_request_ref(request);
+	ldap_request->request.ldap.auth_request = request;
+
 	if (!conn->set.auth_bind)
-		ldap_lookup_pass(request, &ldap_request->request);
+		ldap_lookup_pass(request, ldap_request);
 	else if (conn->set.auth_bind_userdn == NULL)
-		ldap_verify_plain_authbind(request, &ldap_request->request);
+		ldap_bind_lookup_dn(request, ldap_request);
 	else
-		ldap_verify_plain_auth_bind_userdn(request, &ldap_request->request);
+		ldap_verify_plain_auth_bind_userdn(request, ldap_request);
 }
 
 static void ldap_lookup_credentials(struct auth_request *request,
@@ -417,7 +378,10 @@
 	ldap_request = p_new(request->pool, struct passdb_ldap_request, 1);
 	ldap_request->callback.lookup_credentials = callback;
 
-        ldap_lookup_pass(request, &ldap_request->request);
+	auth_request_ref(request);
+	ldap_request->request.ldap.auth_request = request;
+
+        ldap_lookup_pass(request, ldap_request);
 }
 
 static struct passdb_module *
--- a/src/auth/userdb-ldap.c	Sat Dec 29 04:11:59 2007 +0200
+++ b/src/auth/userdb-ldap.c	Sat Dec 29 04:14:56 2007 +0200
@@ -21,7 +21,7 @@
 };
 
 struct userdb_ldap_request {
-	struct ldap_request request;
+	struct ldap_request_search request;
         struct auth_request *auth_request;
         userdb_callback_t *userdb_callback;
 };
@@ -43,40 +43,30 @@
 	}
 }
 
-static void handle_request(struct ldap_connection *conn,
-			   struct ldap_request *request, LDAPMessage *res)
+static void userdb_ldap_lookup_callback(struct ldap_connection *conn,
+					struct ldap_request *request,
+					LDAPMessage *res)
 {
 	struct userdb_ldap_request *urequest =
 		(struct userdb_ldap_request *) request;
 	struct auth_request *auth_request = urequest->auth_request;
 	LDAPMessage *entry;
 	enum userdb_result result = USERDB_RESULT_INTERNAL_FAILURE;
-	int ret;
 
 	if (res != NULL) {
-		ret = ldap_result2error(conn->ld, res, 0);
-		if (ret != LDAP_SUCCESS) {
-			auth_request_log_error(auth_request, "ldap",
-					       "ldap_search() failed: %s", ldap_err2string(ret));
-			urequest->userdb_callback(result, auth_request);
-			return;
-		}
-	}
-
-	entry = res == NULL ? NULL : ldap_first_entry(conn->ld, res);
-	if (entry == NULL) {
-		if (res != NULL) {
+		entry = ldap_first_entry(conn->ld, res);
+		if (entry == NULL) {
 			result = USERDB_RESULT_USER_UNKNOWN;
-			auth_request_log_error(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");
+			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");
+			}
 		}
 	}
 
@@ -98,7 +88,6 @@
 
 	auth_request_ref(auth_request);
 	request = p_new(auth_request->pool, struct userdb_ldap_request, 1);
-	request->request.callback = handle_request;
 	request->auth_request = auth_request;
 	request->userdb_callback = callback;
 
@@ -121,7 +110,8 @@
 			       attr_names == NULL ? "(all)" :
 			       t_strarray_join(attr_names, ","));
 
-	db_ldap_search(conn, &request->request, conn->set.ldap_scope);
+	request->request.request.callback = userdb_ldap_lookup_callback;
+	db_ldap_request(conn, &request->request.request);
 }
 
 static struct userdb_module *