changeset 19996:dbbcfdb60f1e

lib-ldap: Add lib-ldap implementation
author Aki Tuomi <aki.tuomi@dovecot.fi>
date Mon, 11 Apr 2016 09:37:39 +0300
parents 8891fcb7ea87
children ebf926e29527
files src/lib-ldap/Makefile.am src/lib-ldap/ldap-client.c src/lib-ldap/ldap-client.h src/lib-ldap/ldap-compare.c src/lib-ldap/ldap-connection.c src/lib-ldap/ldap-entry.c src/lib-ldap/ldap-iterator.c src/lib-ldap/ldap-private.h src/lib-ldap/ldap-search.c
diffstat 9 files changed, 1307 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/Makefile.am	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,41 @@
+pkglib_LTLIBRARIES = libdovecot-ldap.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-test \
+	-I$(top_srcdir)/src/lib-settings \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/lib-ssl-iostream \
+	$(LDAP_CFLAGS)
+
+libdovecot_ldap_la_SOURCES = \
+	ldap-client.c \
+	ldap-connection.c \
+	ldap-iterator.c \
+	ldap-search.c \
+	ldap-compare.c \
+	ldap-entry.c
+
+libdovecot_ldap_la_DEPENDENCIES =
+libdovecot_ldap_la_LDFLAGS = -export-dynamic
+libdovecot_ldap_la_LIBADD = $(LDAP_LIBS)
+
+headers = \
+	ldap-client.h
+
+noinst_HEADERS = \
+	ldap-private.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+
+test_libs = \
+	../lib-test/libtest.la \
+	../lib-ssl-iostream/libssl_iostream.la \
+	../lib/liblib.la
+
+check: check-am check-test
+check-test: all-am
+	for bin in $(test_programs); do \
+	  if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+	done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-client.c	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,55 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ldap-private.h"
+
+struct ldap_client {
+	/* for now we support just a single connection, but this could be
+	   extended to a connection pool. */
+	struct ldap_connection *conn;
+};
+
+int ldap_client_init(const struct ldap_client_settings *set,
+		     struct ldap_client **client_r, const char **error_r)
+{
+	struct ldap_client *client;
+
+	client = i_new(struct ldap_client, 1);
+	if (ldap_connection_init(client, set, &client->conn, error_r) < 0) {
+		i_free(client);
+		return -1;
+	}
+	*client_r = client;
+	return 0;
+}
+
+void ldap_client_deinit(struct ldap_client **_client)
+{
+	struct ldap_client *client = *_client;
+
+	*_client = NULL;
+
+	ldap_connection_deinit(&client->conn);
+	i_free(client);
+}
+
+void ldap_client_switch_ioloop(struct ldap_client *client)
+{
+	ldap_connection_switch_ioloop(client->conn);
+}
+
+#undef ldap_search_start
+void ldap_search_start(struct ldap_client *client,
+		       const struct ldap_search_input *input,
+		       ldap_result_callback_t *callback, void *context)
+{
+	return ldap_connection_search_start(client->conn, input, callback, context);
+}
+
+#undef ldap_compare_start
+void ldap_compare_start(struct ldap_client *client,
+			const struct ldap_compare_input *input,
+			ldap_result_callback_t *callback, void *context)
+{
+	return ldap_connection_compare_start(client->conn, input, callback, context);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-client.h	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,93 @@
+#ifndef LDAP_CLIENT_H
+#define LDAP_CLIENT_H
+
+enum ldap_scope {
+	LDAP_SEARCH_SCOPE_BASE    = 0x0000,
+	LDAP_SEARCH_SCOPE_ONE     = 0x0001,
+	LDAP_SEARCH_SCOPE_SUBTREE = 0x0002
+};
+
+struct ldap_client;
+struct ldap_result;
+struct ldap_search_iterator;
+struct ldap_entry;
+
+/* Called when the LDAP result has finished. The callback must verify first
+   if the result is valid or not by calling ldap_result_has_failed() or
+   ldap_result_get_error(). The result is freed automatically after this
+   callback finishes. */
+typedef void ldap_result_callback_t(struct ldap_result *result, void *context);
+
+struct ldap_client_settings {
+	const char *uri;
+	const char *bind_dn;
+	const char *password;
+
+	const struct ssl_iostream_settings *ssl_set;
+
+	unsigned int timeout_secs;
+	unsigned int max_idle_time;
+	unsigned int debug;
+};
+
+struct ldap_search_input {
+	const char *base_dn;
+	const char *filter;
+	const char *const *attributes;
+	enum ldap_scope scope;
+
+	unsigned int size_limit;
+
+	unsigned int timeout_secs;
+};
+
+struct ldap_compare_input {
+	const char *dn;
+	const char *attr;
+	const char *value;
+
+	unsigned int timeout_secs;
+};
+
+/* Initialize LDAP. Returns 0 on success, or -1 and error_r if initialization
+   failed with the given settings. */
+int ldap_client_init(const struct ldap_client_settings *set,
+		     struct ldap_client **client_r, const char **error_r);
+void ldap_client_deinit(struct ldap_client **client);
+void ldap_client_switch_ioloop(struct ldap_client *client);
+
+void ldap_search_start(struct ldap_client *client,
+		       const struct ldap_search_input *input,
+		       ldap_result_callback_t *callback,
+		       void *context);
+#define ldap_search_start(client, input, callback, context) \
+	ldap_search_start(client, input + \
+		CALLBACK_TYPECHECK(callback, void (*)( \
+			struct ldap_result *, typeof(context))), \
+		(ldap_result_callback_t *)callback, context)
+
+/* Returns TRUE if the LDAP query failed and result must not be used further. */
+bool ldap_result_has_failed(struct ldap_result *result);
+/* Returns the error string if the query had failed, or NULL if it hasn't. */
+const char *ldap_result_get_error(struct ldap_result *result);
+
+struct ldap_search_iterator* ldap_search_iterator_init(struct ldap_result *result);
+const struct ldap_entry *ldap_search_iterator_next(struct ldap_search_iterator *iter);
+void ldap_search_iterator_deinit(struct ldap_search_iterator **iter);
+
+void ldap_compare_start(struct ldap_client *client,
+			const struct ldap_compare_input *input,
+			ldap_result_callback_t *callback, void *context);
+#define ldap_compare_start(client, input, callback, context) \
+	ldap_compare_start(client, input + \
+		CALLBACK_TYPECHECK(callback, void (*)( \
+			struct ldap_result *, typeof(context))), \
+		(ldap_result_callback_t *)callback, context)
+/* Returns TRUE if the comparison matched, FALSE if not. */
+bool ldap_compare_result(struct ldap_result *result);
+
+const char *ldap_entry_dn(const struct ldap_entry *entry);
+const char *const *ldap_entry_get_attributes(const struct ldap_entry *entry);
+const char *const *ldap_entry_get_attribute(const struct ldap_entry *entry, const char *attribute);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-compare.c	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,121 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ldap-private.h"
+
+static int
+ldap_compare_callback(struct ldap_connection *conn,
+		      struct ldap_op_queue_entry *req,
+		      LDAPMessage *message, bool *finished_r)
+{
+	int msgtype = ldap_msgtype(message);
+	struct ldap_result res;
+	char *result_errmsg;
+	int ret, result_err;
+
+	if (msgtype != LDAP_RES_COMPARE) {
+		*finished_r = FALSE;
+		return 0;
+	}
+	*finished_r = TRUE;
+
+	ret = ldap_parse_result(conn->conn, message,
+				&result_err, NULL,
+				&result_errmsg, NULL, NULL, 0);
+	memset(&res, 0, sizeof(res));
+	res.openldap_ret = ret;
+	if (ret != LDAP_SUCCESS) {
+		res.error_string = t_strdup_printf(
+			"ldap_parse_result() failed to parse compare: %s",
+			ldap_err2string(ret));
+	} else if (result_err == LDAP_COMPARE_TRUE) {
+		res.compare_true = TRUE;
+	} else if (result_err == LDAP_COMPARE_FALSE) {
+		res.compare_true = FALSE;
+	} else {
+		const struct ldap_compare_input *input = &req->input.compare;
+		const char *error = result_errmsg != NULL ?
+			result_errmsg : ldap_err2string(result_err);
+		res.openldap_ret = result_err;
+		res.error_string = t_strdup_printf(
+			"ldap_compare_ext(dn=%s, attr=%s) failed: %s",
+			input->dn, input->attr, error);
+	}
+
+	req->result_callback(&res, req->result_callback_ctx);
+
+	if (result_errmsg != NULL)
+		ldap_memfree(result_errmsg);
+	return res.openldap_ret;
+}
+
+static int
+ldap_compare_send(struct ldap_connection *conn, struct ldap_op_queue_entry *req,
+		  const char **error_r)
+{
+	const struct ldap_compare_input *input = &req->input.compare;
+	struct berval bv = {
+		.bv_len = strlen(input->value),
+		.bv_val = (void*)input->value
+	};
+
+	LDAPControl manageDSAIT = {
+		LDAP_CONTROL_MANAGEDSAIT, {0, 0}, 0
+	};
+
+	/* try to use ManageDSAIT if available */
+	LDAPControl *sctrls[] = {
+		&manageDSAIT,
+		NULL
+	};
+
+	int ret = ldap_compare_ext(conn->conn,
+		input->dn,
+		input->attr,
+		&bv,
+		sctrls,
+		NULL,
+		&(req->msgid));
+
+	if (ret != LDAP_SUCCESS) {
+		*error_r = t_strdup_printf(
+			"ldap_compare_ext(dn=%s, attr=%s) failed: %s",
+			input->dn, input->attr, ldap_err2string(ret));
+	}
+	return ret;
+}
+
+void ldap_connection_compare_start(struct ldap_connection *conn,
+				   const struct ldap_compare_input *input,
+				   ldap_result_callback_t *callback,
+				   void *context)
+{
+	struct ldap_op_queue_entry *req;
+	pool_t pool = pool_alloconly_create(MEMPOOL_GROWING "ldap compare", 128);
+	req = p_new(pool, struct ldap_op_queue_entry, 1);
+	req->pool = pool;
+
+	req->internal_response_cb = ldap_compare_callback;
+
+	req->input.compare = *input;
+	req->result_callback = callback;
+	req->result_callback_ctx = context;
+
+	/* copy strings */
+	req->input.compare.dn = p_strdup(req->pool, input->dn);
+	req->input.compare.attr = p_strdup(req->pool, input->attr);
+	req->input.compare.value = p_strdup(req->pool, input->value);
+
+	req->send_request_cb = ldap_compare_send;
+	req->timeout_secs = input->timeout_secs;
+
+	return ldap_connection_queue_request(conn, req);
+}
+
+bool ldap_compare_result(struct ldap_result *result)
+{
+	i_assert(result->openldap_ret == LDAP_SUCCESS);
+	i_assert(result->error_string == NULL);
+
+	return result->compare_true;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-connection.c	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,600 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "aqueue.h"
+#include "ioloop.h"
+#include "ldap-private.h"
+
+static
+void ldap_connection_read_more(struct ldap_connection *conn);
+static
+int ldap_connect_next_message(struct ldap_connection *conn, struct ldap_op_queue_entry *req, bool *finished_r);
+static
+void ldap_connection_abort_request(struct ldap_op_queue_entry *req);
+static
+void ldap_connection_request_destroy(struct ldap_op_queue_entry **req);
+
+void ldap_connection_deinit(struct ldap_connection **_conn)
+{
+	struct ldap_connection *conn = *_conn;
+
+	*_conn = NULL;
+
+	ldap_connection_kill(conn);
+
+	unsigned int n = aqueue_count(conn->request_queue);
+	for (unsigned int i = 0; i < n; i++) {
+		struct ldap_op_queue_entry *const *reqp =
+			array_idx(&(conn->request_array),
+				  aqueue_idx(conn->request_queue, i));
+		if ((*reqp)->to_abort != NULL)
+			timeout_remove(&(*reqp)->to_abort);
+	}
+	pool_unref(&conn->pool);
+}
+
+static
+int ldap_connection_setup(struct ldap_connection *conn, const char **error_r)
+{
+	int ret, opt;
+
+	ret = ldap_initialize(&(conn->conn), conn->set.uri);
+	if (ret != LDAP_SUCCESS) {
+		*error_r = t_strdup_printf("ldap_initialize(uri=%s) failed: %s",
+					   conn->set.uri, ldap_err2string(ret));
+		return -1;
+	}
+
+	if (conn->ssl_set.verify_remote_cert) {
+		opt = LDAP_OPT_X_TLS_HARD;
+	} else {
+		opt = LDAP_OPT_X_TLS_ALLOW;
+	}
+
+	ldap_set_option(conn->conn, LDAP_OPT_X_TLS, &opt);
+	/* refuse to connect to SSLv2 as it's completely insecure */
+	opt = LDAP_OPT_X_TLS_PROTOCOL_SSL3;
+	ldap_set_option(conn->conn, LDAP_OPT_X_TLS_PROTOCOL_MIN, &opt);
+
+	opt = conn->set.timeout_secs;
+	/* default timeout */
+	ldap_set_option(conn->conn, LDAP_OPT_TIMEOUT, &opt);
+	ldap_set_option(conn->conn, LDAP_OPT_NETWORK_TIMEOUT, &opt);
+	/* timelimit */
+	ldap_set_option(conn->conn, LDAP_OPT_TIMELIMIT, &opt);
+
+	if (conn->ssl_set.ca_file != NULL)
+		ldap_set_option(conn->conn, LDAP_OPT_X_TLS_CACERTFILE, conn->ssl_set.ca_file);
+	if (conn->ssl_set.ca_dir != NULL)
+		ldap_set_option(conn->conn, LDAP_OPT_X_TLS_CACERTDIR, conn->ssl_set.ca_dir);
+
+	if (conn->ssl_set.cert != NULL)
+		ldap_set_option(conn->conn, LDAP_OPT_X_TLS_CERTFILE, conn->ssl_set.cert);
+	if (conn->ssl_set.key != NULL)
+		ldap_set_option(conn->conn, LDAP_OPT_X_TLS_KEYFILE, conn->ssl_set.key);
+
+	opt = conn->set.debug;
+	ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &opt);
+
+	opt = LDAP_VERSION3;
+	ldap_set_option(conn->conn, LDAP_OPT_PROTOCOL_VERSION, &opt);
+
+	ldap_set_option(conn->conn, LDAP_OPT_REFERRALS, 0);
+
+	return 0;
+}
+
+int ldap_connection_init(struct ldap_client *client,
+			 const struct ldap_client_settings *set,
+			 struct ldap_connection **conn_r, const char **error_r)
+{
+	pool_t pool = pool_alloconly_create("ldap connection", 1024);
+	struct ldap_connection *conn = p_new(pool, struct ldap_connection, 1);
+	conn->pool = pool;
+
+	i_assert(set->uri != NULL);
+
+	conn->client = client;
+	conn->set = *set;
+	/* deep copy relevant strings */
+	conn->set.uri = p_strdup(pool, set->uri);
+	conn->set.bind_dn = p_strdup(pool, set->bind_dn);
+	if (set->password != NULL) {
+		conn->set.password = p_strdup(pool, set->password);
+		ber_str2bv(conn->set.password, strlen(conn->set.password), 0, &(conn->cred));
+	}
+	/* cannot use these */
+	conn->ssl_set.ca = NULL;
+	conn->ssl_set.key_password = NULL;
+	conn->ssl_set.cert_username_field = NULL;
+	conn->ssl_set.crypto_device = NULL;
+
+	if (set->ssl_set != NULL) {
+		conn->ssl_set.protocols = p_strdup(pool, set->ssl_set->protocols);
+		conn->ssl_set.cipher_list = p_strdup(pool, set->ssl_set->cipher_list);
+		conn->ssl_set.ca_file = p_strdup(pool, set->ssl_set->ca_file);
+		conn->ssl_set.cert = p_strdup(pool, set->ssl_set->cert);
+		conn->ssl_set.key = p_strdup(pool, set->ssl_set->key);
+	}
+
+	if (ldap_connection_setup(conn, error_r) < 0) {
+		ldap_connection_deinit(&conn);
+		return -1;
+	}
+
+	p_array_init(&(conn->request_array), conn->pool, 10);
+	conn->request_queue = aqueue_init(&(conn->request_array.arr));
+
+	*conn_r = conn;
+	return 0;
+}
+
+void ldap_connection_switch_ioloop(struct ldap_connection *conn)
+{
+	if (conn->io != NULL)
+		conn->io = io_loop_move_io(&conn->io);
+	if (conn->to_disconnect != NULL)
+		conn->to_disconnect = io_loop_move_timeout(&conn->to_disconnect);
+	if (conn->to_reconnect != NULL)
+		conn->to_reconnect = io_loop_move_timeout(&conn->to_reconnect);
+	unsigned int n = aqueue_count(conn->request_queue);
+
+	for (unsigned int i = 0; i < n; i++) {
+		struct ldap_op_queue_entry *const *reqp =
+			array_idx(&(conn->request_array),
+				  aqueue_idx(conn->request_queue, i));
+		if ((*reqp)->to_abort != NULL)
+			(*reqp)->to_abort = io_loop_move_timeout(&((*reqp)->to_abort));
+	}
+}
+
+static void
+ldap_connection_result_failure(struct ldap_connection *conn,
+			       struct ldap_op_queue_entry *req,
+			       int ret, const char *error)
+{
+	struct ldap_result res;
+	memset(&res, 0, sizeof(res));
+	res.conn = conn;
+	res.openldap_ret = ret;
+	res.error_string = error;
+	if (req->result_callback != NULL)
+		req->result_callback(&res, req->result_callback_ctx);
+	else
+		i_error("%s", error);
+	ldap_connection_kill(conn);
+}
+
+static
+void ldap_connection_result_success(struct ldap_connection *conn,
+				    struct ldap_op_queue_entry *req)
+{
+	struct ldap_result res;
+	memset(&res, 0, sizeof(res));
+	res.conn = conn;
+	res.openldap_ret = LDAP_SUCCESS;
+	if (req->result_callback != NULL)
+		req->result_callback(&res, req->result_callback_ctx);
+}
+
+static
+void ldap_connection_send_next(struct ldap_connection *conn)
+{
+	unsigned int i = 0, n;
+	struct ldap_op_queue_entry *req;
+
+	if (conn->to_reconnect != NULL)
+		timeout_remove(&(conn->to_reconnect));
+
+	if (conn->state == LDAP_STATE_DISCONNECT) {
+		if (ldap_connection_connect(conn, NULL, NULL) == -1)
+			conn->to_reconnect = timeout_add(1000, ldap_connection_send_next, conn);
+		return;
+	}
+
+	if (conn->state != LDAP_STATE_CONNECT) {
+		return;
+	}
+
+	if (conn->pending > 10) return; /* try again later */
+
+	req = NULL;
+	/* get next request */
+	n = aqueue_count(conn->request_queue);
+
+	for(i=0; i < n; i++) {
+		struct ldap_op_queue_entry *const *reqp =
+			array_idx(&(conn->request_array),
+				  aqueue_idx(conn->request_queue, i));
+		if ((*reqp)->msgid > -1)
+			break;
+		req = *reqp;
+	}
+
+	i--;
+
+	/* nothing to actually send */
+	if (req == NULL) return;
+
+	i_assert(req->msgid == -1);
+
+	const char *error;
+	int ret;
+	if ((ret = req->send_request_cb(conn, req, &error)) != LDAP_SUCCESS) {
+		/* did not succeed */
+		struct ldap_result res;
+
+		memset(&res, 0, sizeof(res));
+		res.openldap_ret = ret;
+		req->result_callback(&res, req->result_callback_ctx);
+
+		ldap_connection_request_destroy(&req);
+		aqueue_delete(conn->request_queue, i);
+	} else conn->pending++;
+}
+
+static
+void ldap_connection_request_destroy(struct ldap_op_queue_entry **_req)
+{
+	struct ldap_op_queue_entry *req = *_req;
+
+	*_req = NULL;
+
+	if (req->to_abort != NULL)
+		timeout_remove(&req->to_abort);
+	pool_unref(&req->pool);
+}
+
+void ldap_connection_queue_request(struct ldap_connection *conn, struct ldap_op_queue_entry *req)
+{
+	req->msgid = -1;
+	req->conn = conn;
+	aqueue_append(conn->request_queue, &req);
+	if (req->timeout_secs > 0)
+		req->to_abort = timeout_add(req->timeout_secs * 1000, ldap_connection_abort_request, req);
+
+	ldap_connection_send_next(conn);
+}
+
+static int
+ldap_connection_connect_parse(struct ldap_connection *conn,
+			      struct ldap_op_queue_entry *req,
+			      LDAPMessage *message, bool *finished_r)
+{
+	int ret, result_err;
+	char *retoid, *result_errmsg;
+	int msgtype = ldap_msgtype(message);
+
+	*finished_r = TRUE;
+
+	ret = ldap_parse_result(conn->conn, message, &result_err, NULL,
+				&result_errmsg, NULL, NULL, 0);
+	if (ret != LDAP_SUCCESS) {
+		ldap_connection_result_failure(conn, req, ret, t_strdup_printf(
+			"ldap_parse_result() failed for connect: %s",
+			ldap_err2string(ret)));
+		return ret;
+	}
+	if (result_err != LDAP_SUCCESS) {
+		const char *error = result_errmsg != NULL ?
+			result_errmsg : ldap_err2string(result_err);
+		ldap_connection_result_failure(conn, req, result_err, t_strdup_printf(
+			"Connect failed: %s", error));
+		ldap_memfree(result_errmsg);
+		return result_err;
+	}
+
+	switch(conn->state) {
+	case LDAP_STATE_TLS:
+		if (msgtype != LDAP_RES_EXTENDED) {
+			*finished_r = FALSE;
+			return LDAP_SUCCESS;
+		}
+		ret = ldap_parse_extended_result(conn->conn, message, &retoid, NULL, 0);
+		if (strcmp(retoid, "1.3.6.1.4.1.1466.20037") == 0) {
+			ret = ldap_install_tls(conn->conn);
+		} else ret = -1; /* make it fail on next if */
+		if (ret != LDAP_SUCCESS) {
+			// FIXME: Check if SSL is mandatory and fail if yes
+			i_warning("Cannot start tls with server: %s", ldap_err2string(ret));
+			return ret;
+		}
+		ldap_memfree(retoid);
+		conn->state = LDAP_STATE_AUTH;
+		return ldap_connect_next_message(conn, req, finished_r);
+	case LDAP_STATE_AUTH:
+		if (msgtype != LDAP_RES_BIND) return 0;
+		ret = ldap_parse_sasl_bind_result(conn->conn, message, &(conn->scred), 0);
+		if (ret != LDAP_SUCCESS) {
+			const char *error = t_strdup_printf(
+				"Cannot bind with server: %s", ldap_err2string(ret));
+			ldap_connection_result_failure(conn, req, ret, error);
+			return 1;
+		}
+		conn->state = LDAP_STATE_CONNECT;
+		return ldap_connect_next_message(conn, req, finished_r);
+	default:
+		i_unreached();
+	}
+	return LDAP_SUCCESS;
+}
+
+static
+void ldap_connection_abort_request(struct ldap_op_queue_entry *req)
+{
+	struct ldap_result res;
+
+	/* too bad */
+	if (req->to_abort != NULL)
+		timeout_remove(&req->to_abort);
+	if (req->msgid > -1)
+		ldap_abandon_ext(req->conn->conn, req->msgid, NULL, NULL);
+
+	memset(&res, 0, sizeof(res));
+	res.openldap_ret = LDAP_TIMEOUT;
+	res.error_string = "Aborting LDAP request after timeout";
+	req->result_callback(&res, req->result_callback_ctx);
+
+	unsigned int n = aqueue_count(req->conn->request_queue);
+	for (unsigned int i = 0; i < n; i++) {
+		struct ldap_op_queue_entry *const *reqp =
+			array_idx(&(req->conn->request_array),
+				  aqueue_idx(req->conn->request_queue, i));
+		if (req == *reqp) {
+			aqueue_delete(req->conn->request_queue, i);
+			ldap_connection_request_destroy(&req);
+			return;
+		}
+	}
+	i_unreached();
+}
+
+static int
+ldap_connect_next_message(struct ldap_connection *conn,
+			  struct ldap_op_queue_entry *req, bool *finished_r)
+{
+	int ret;
+
+	*finished_r = TRUE;
+
+	switch(conn->state) {
+	case LDAP_STATE_DISCONNECT:
+		ret = ldap_start_tls(conn->conn, NULL, NULL, &(req->msgid));
+		if (ret != LDAP_SUCCESS) {
+			ldap_connection_result_failure(conn, req, ret, t_strdup_printf(
+				"ldap_start_tls(uri=%s) failed: %s",
+				conn->set.uri, ldap_err2string(ret)));
+			return ret;
+		}
+		conn->state = LDAP_STATE_TLS;
+		break;
+	case LDAP_STATE_AUTH:
+		ret = ldap_sasl_bind(conn->conn,
+			conn->set.bind_dn,
+			LDAP_SASL_SIMPLE,
+			&(conn->cred),
+			NULL,
+			NULL,
+			&(req->msgid));
+		if (ret != LDAP_SUCCESS) {
+			ldap_connection_result_failure(conn, req, ret, t_strdup_printf(
+				"ldap_sasl_bind(uri=%s, dn=%s) failed: %s",
+				conn->set.uri, conn->set.bind_dn, ldap_err2string(ret)));
+			return ret;
+		}
+		break;
+	case LDAP_STATE_CONNECT:
+		ldap_connection_result_success(conn, req);
+		return LDAP_SUCCESS; /* we are done here */
+	default:
+		i_unreached();
+	};
+
+	req->conn = conn;
+	*finished_r = FALSE;
+	return LDAP_SUCCESS;
+}
+
+int ldap_connection_connect(struct ldap_connection *conn, ldap_result_callback_t cb, void *ctx)
+{
+	const char *error;
+	int fd;
+	Sockbuf *sb;
+	bool finished;
+
+	if (conn->conn == NULL) {
+		/* try to reconnect after disconnection */
+		if (ldap_connection_setup(conn, &error) < 0)
+			i_error("%s", error);
+	}
+
+	pool_t pool = pool_alloconly_create(MEMPOOL_GROWING "ldap bind", 128);
+	struct ldap_op_queue_entry *req = p_new(pool, struct ldap_op_queue_entry, 1);
+	req->pool = pool;
+
+	req->result_callback = cb;
+	req->result_callback_ctx = ctx;
+	req->internal_response_cb = ldap_connection_connect_parse;
+	req->timeout_secs = conn->set.timeout_secs;
+
+	if (ldap_connect_next_message(conn, req, &finished) != LDAP_SUCCESS ||
+	    conn->conn == NULL) {
+		pool_unref(&pool);
+		return -1;
+	}
+	conn->pending++;
+	aqueue_append(conn->request_queue, &req);
+	/* start timeout */
+	if (req->timeout_secs > 0)
+		req->to_abort = timeout_add(req->timeout_secs * 1000, ldap_connection_abort_request, req);
+
+	ldap_get_option(conn->conn, LDAP_OPT_SOCKBUF, &sb);
+	ber_sockbuf_ctrl(sb, LBER_SB_OPT_GET_FD, &fd);
+	conn->io = io_add(fd, IO_READ, ldap_connection_read_more, conn);
+	if (conn->set.max_idle_time > 0)
+		conn->to_disconnect = timeout_add(conn->set.max_idle_time, ldap_connection_kill, conn);
+	return 0;
+}
+
+void ldap_connection_kill(struct ldap_connection *conn)
+{
+	if (conn->io != NULL)
+		io_remove(&(conn->io));
+	if (conn->to_disconnect != NULL)
+		timeout_remove(&(conn->to_disconnect));
+	if (conn->to_reconnect != NULL)
+		timeout_remove(&(conn->to_reconnect));
+	if (conn->request_queue) {
+		unsigned int n = aqueue_count(conn->request_queue);
+
+		for (unsigned int i = 0; i < n; i++) {
+			struct ldap_op_queue_entry *const *reqp =
+				array_idx(&(conn->request_array),
+					  aqueue_idx(conn->request_queue, i));
+			if ((*reqp)->msgid > -1)
+				ldap_abandon_ext(conn->conn, (*reqp)->msgid, NULL, NULL);
+			(*reqp)->msgid = -1;
+		}
+	}
+	if (conn->conn != NULL) {
+		ldap_destroy(conn->conn);
+		ldap_memfree(conn->scred);
+	}
+	conn->conn = NULL;
+	conn->state = LDAP_STATE_DISCONNECT;
+}
+
+int ldap_connection_check(struct ldap_connection *conn)
+{
+	/* it's not connected */
+	if (conn->state == LDAP_STATE_DISCONNECT) return -1;
+	return 0;
+}
+
+static struct ldap_op_queue_entry *
+ldap_connection_find_req_by_msgid(struct ldap_connection *conn, int msgid,
+				  unsigned int *idx_r)
+{
+	unsigned int i, n = aqueue_count(conn->request_queue);
+	for (i = 0; i < n; i++) {
+		struct ldap_op_queue_entry *const *reqp =
+			array_idx(&(conn->request_array),
+				  aqueue_idx(conn->request_queue, i));
+		if ((*reqp)->msgid == msgid) {
+			*idx_r = i;
+			return *reqp;
+		}
+	}
+	return NULL;
+}
+
+static int
+ldap_connection_handle_message(struct ldap_connection *conn,
+			       LDAPMessage *message)
+{
+	struct ldap_op_queue_entry *req;
+	unsigned int i = 0;
+	bool finished = FALSE;
+	int err = LDAP_SUCCESS;
+
+	/* we need to look at who it was for */
+	req = ldap_connection_find_req_by_msgid(conn, ldap_msgid(message), &i);
+	if (req != NULL)
+		err = req->internal_response_cb(conn, req, message, &finished);
+	ldap_msgfree(message);
+
+	switch(err) {
+	case LDAP_SUCCESS:
+		break;
+	case LDAP_SERVER_DOWN:
+#ifdef LDAP_CONNECT_ERROR
+	case LDAP_CONNECT_ERROR:
+#endif
+	case LDAP_UNAVAILABLE:
+		ldap_connection_kill(conn);
+		/* fall through */
+	case LDAP_OPERATIONS_ERROR:
+	case LDAP_BUSY:
+		/* requeue */
+		ldap_connection_kill(conn);
+		ldap_connection_send_next(conn);
+		finished = FALSE;
+		break;
+	case LDAP_SIZELIMIT_EXCEEDED:
+	case LDAP_TIMELIMIT_EXCEEDED:
+	case LDAP_NO_SUCH_ATTRIBUTE:
+	case LDAP_UNDEFINED_TYPE:
+	case LDAP_INAPPROPRIATE_MATCHING:
+	case LDAP_CONSTRAINT_VIOLATION:
+	case LDAP_TYPE_OR_VALUE_EXISTS:
+	case LDAP_INVALID_SYNTAX:
+	case LDAP_NO_SUCH_OBJECT:
+	case LDAP_ALIAS_PROBLEM:
+	case LDAP_INVALID_DN_SYNTAX:
+	case LDAP_IS_LEAF:
+	case LDAP_ALIAS_DEREF_PROBLEM:
+	case LDAP_FILTER_ERROR:
+	case LDAP_LOCAL_ERROR:
+	case LDAP_INVALID_CREDENTIALS:
+		finished = TRUE;
+		break;
+	default:
+		/* ignore */
+		break;
+	}
+
+	if (finished) {
+		i_assert(req != NULL);
+		ldap_connection_request_destroy(&req);
+		conn->pending--;
+		aqueue_delete(conn->request_queue, i);
+		return 1;
+	}
+	return 0;
+}
+
+static
+void ldap_connection_read_more(struct ldap_connection *conn)
+{
+	struct timeval tv = {
+		.tv_sec = 0,
+		.tv_usec = 0
+	};
+
+	LDAPMessage *message;
+	int ret;
+
+	/* try get a message */
+	ret = ldap_result(conn->conn, LDAP_RES_ANY, 0, &tv, &message);
+	if (ret > 0)
+		ret = ldap_connection_handle_message(conn, message);
+
+	if (ret == -1) {
+		if (ldap_get_option(conn->conn, LDAP_OPT_RESULT_CODE, &ret) != LDAP_SUCCESS)
+			i_unreached();
+		if (ret != LDAP_SERVER_DOWN)
+			i_error("ldap_result() failed: %s", ldap_err2string(ret));
+		else
+			i_error("Connection lost to LDAP server, reconnecting");
+		/* kill me */
+		ldap_connection_kill(conn);
+	} else if (ret != 0) {
+		ldap_connection_send_next(conn);
+	}
+	/* reset timeout */
+	if (conn->to_disconnect != NULL)
+		timeout_reset(conn->to_disconnect);
+}
+
+bool ldap_result_has_failed(struct ldap_result *result)
+{
+	i_assert((result->openldap_ret == LDAP_SUCCESS) == (result->error_string == NULL));
+	return result->openldap_ret != LDAP_SUCCESS;
+}
+
+const char *ldap_result_get_error(struct ldap_result *result)
+{
+	i_assert((result->openldap_ret == LDAP_SUCCESS) == (result->error_string == NULL));
+	return result->error_string;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-entry.c	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,72 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ldap-private.h"
+
+int ldap_entry_init(struct ldap_entry *obj, struct ldap_result *result,
+	LDAPMessage *message)
+{
+	ARRAY_TYPE(const_string) attr_names;
+	struct berval **values;
+	int count;
+	BerElement *bptr;
+	char *tmp;
+	tmp = ldap_get_dn(result->conn->conn, message);
+	obj->dn = p_strdup(result->pool, tmp);
+	obj->result = result;
+	ldap_memfree(tmp);
+
+	tmp = ldap_first_attribute(result->conn->conn, message, &bptr);
+
+	p_array_init(&attr_names, result->pool, 8);
+	p_array_init(&(obj->attributes), result->pool, 8);
+
+	while(tmp != NULL) {
+		struct ldap_attribute *attr = p_new(result->pool, struct ldap_attribute, 1);
+		attr->name = p_strdup(result->pool, tmp);
+		array_append(&attr_names, &(attr->name), 1);
+		values = ldap_get_values_len(result->conn->conn, message, tmp);
+		if (values != NULL) {
+			count = ldap_count_values_len(values);
+			p_array_init(&(attr->values), result->pool, count);
+			for(int i = 0; i < count; i++) {
+				const char *ptr = p_strndup(result->pool, values[i]->bv_val, values[i]->bv_len);
+				array_append(&(attr->values), &ptr, 1);
+			}
+			ldap_value_free_len(values);
+		}
+		array_append_zero(&(attr->values));
+		ldap_memfree(tmp);
+		array_append(&(obj->attributes), attr, 1);
+		tmp = ldap_next_attribute(result->conn->conn, message, bptr);
+	}
+
+	ber_free(bptr, 0);
+
+	array_append_zero(&attr_names);
+	obj->attr_names = array_idx(&attr_names, 0);
+
+	return 0;
+}
+
+const char *ldap_entry_dn(const struct ldap_entry *entry)
+{
+	return entry->dn;
+}
+
+const char *const *ldap_entry_get_attributes(const struct ldap_entry *entry)
+{
+	return entry->attr_names;
+}
+
+const char *const *ldap_entry_get_attribute(const struct ldap_entry *entry, const char *attribute)
+{
+	const struct ldap_attribute *attr;
+	array_foreach(&(entry->attributes), attr) {
+		if (strcasecmp(attr->name, attribute) == 0) {
+			return array_idx(&(attr->values), 0);
+		}
+	}
+	return NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-iterator.c	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,29 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ldap-private.h"
+
+struct ldap_search_iterator* ldap_search_iterator_init(struct ldap_result *result)
+{
+	struct ldap_search_iterator *iter;
+
+	i_assert(result->openldap_ret == LDAP_SUCCESS);
+	i_assert(result->error_string == NULL);
+
+	iter = p_new(result->pool, struct ldap_search_iterator, 1);
+	iter->result = result;
+	return iter;
+}
+
+const struct ldap_entry *ldap_search_iterator_next(struct ldap_search_iterator *iter)
+{
+	if (iter->idx >= array_count(&(iter->result->entries)))
+		return NULL;
+	return array_idx(&(iter->result->entries), iter->idx++);
+}
+
+void ldap_search_iterator_deinit(struct ldap_search_iterator **iter)
+{
+	*iter = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-private.h	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,128 @@
+#ifndef LDAP_PRIVATE_H
+#define LDAP_PRIVATE_H
+
+#include "iostream-ssl.h"
+#include "ldap-client.h"
+
+#include <ldap.h>
+
+#define DOVE_LDAP_CONTINUE 0
+#define DOVE_LDAP_COMPLETE 1
+#define DOVE_LDAP_REQUEUE 2
+
+struct ldap_connection;
+struct ldap_result;
+
+struct ldap_op_queue_entry;
+/* Handle an LDAP response. Returns 0 on success, otherwise the OpenLDAP error
+   number. */
+typedef int ldap_response_callback_t(struct ldap_connection *conn,
+				     struct ldap_op_queue_entry *entry,
+				     LDAPMessage *msg, bool *finished_r);
+/* Send the request. Returns 0 on success, otherwise the OpenLDAP error number
+   and sets error_r string. */
+typedef int ldap_send_request_t(struct ldap_connection *conn,
+				struct ldap_op_queue_entry *entry,
+				const char **error_r);
+
+struct ldap_op_queue_entry {
+	pool_t pool;
+	struct ldap_connection *conn;
+	ldap_response_callback_t *internal_response_cb;
+	void *ctx;
+
+	int msgid;
+
+	unsigned int timeout_secs;
+	struct timeout *to_abort;
+
+	ldap_send_request_t *send_request_cb;
+
+	ldap_result_callback_t *result_callback;
+	void *result_callback_ctx;
+
+	struct {
+		struct ldap_search_input search;
+		struct ldap_compare_input compare;
+	} input;
+};
+
+struct ldap_connection {
+	pool_t pool;
+	struct ldap_client *client;
+
+	LDAP *conn;
+	enum {
+		LDAP_STATE_DISCONNECT,
+		LDAP_STATE_TLS,
+		LDAP_STATE_AUTH,
+		LDAP_STATE_CONNECT
+	} state;
+
+	BerValue cred; /* needed for SASL */
+	BerVarray scred;
+
+	struct ldap_client_settings set;
+	struct ssl_iostream_settings ssl_set;
+
+	struct aqueue *request_queue;
+	ARRAY(struct ldap_op_queue_entry *) request_array;
+
+	unsigned int sent;
+	unsigned int pending;
+
+	struct io *io;
+	struct timeout *to_disconnect;
+	struct timeout *to_reconnect;
+};
+
+struct ldap_attribute {
+	const char *name;
+	ARRAY_TYPE(const_string) values;
+};
+
+struct ldap_entry {
+	struct ldap_result *result;
+	char *dn;
+	ARRAY(struct ldap_attribute) attributes;
+	const char *const *attr_names;
+};
+
+struct ldap_result {
+	pool_t pool;
+	struct ldap_connection *conn;
+
+	ARRAY(struct ldap_entry) entries;
+	int openldap_ret;
+	bool compare_true;
+	const char *error_string;
+};
+
+struct ldap_search_iterator {
+	unsigned int idx;
+	struct ldap_result *result;
+};
+
+int ldap_connection_init(struct ldap_client *client,
+			 const struct ldap_client_settings *set,
+			 struct ldap_connection **conn_r, const char **error_r);
+void ldap_connection_deinit(struct ldap_connection **_conn);
+void ldap_connection_switch_ioloop(struct ldap_connection *conn);
+
+void ldap_connection_search_start(struct ldap_connection *conn,
+				  const struct ldap_search_input *input,
+				  ldap_result_callback_t *callback,
+				  void *context);
+void ldap_connection_compare_start(struct ldap_connection *conn,
+				   const struct ldap_compare_input *input,
+				   ldap_result_callback_t *callback,
+				   void *context);
+
+int ldap_connection_connect(struct ldap_connection *conn, ldap_result_callback_t *, void *ctx);
+void ldap_connection_kill(struct ldap_connection *conn);
+int ldap_connection_check(struct ldap_connection *conn);
+void ldap_connection_queue_request(struct ldap_connection *conn, struct ldap_op_queue_entry *req);
+
+int ldap_entry_init(struct ldap_entry *obj, struct ldap_result *result, LDAPMessage *message);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ldap/ldap-search.c	Mon Apr 11 09:37:39 2016 +0300
@@ -0,0 +1,168 @@
+/* Copyright (c) 2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ldap-private.h"
+
+#include <stdio.h>
+
+struct ldap_search_ctx {
+	const struct ldap_search_input *input;
+	struct ldap_result res;
+};
+
+static void
+ldap_search_result_failure(struct ldap_op_queue_entry *req,
+			   int ret, const char *error)
+{
+	struct ldap_search_ctx *sctx = req->ctx;
+	sctx->res.openldap_ret = ret;
+	sctx->res.error_string = error;
+	req->result_callback(&(sctx->res), req->result_callback_ctx);
+}
+
+static void ldap_search_result_success(struct ldap_op_queue_entry *req)
+{
+	struct ldap_search_ctx *sctx = req->ctx;
+	sctx->res.openldap_ret = LDAP_SUCCESS;
+	req->result_callback(&(sctx->res), req->result_callback_ctx);
+}
+
+static int
+ldap_search_callback(struct ldap_connection *conn,
+		     struct ldap_op_queue_entry *req,
+		     LDAPMessage *message, bool *finished_r)
+{
+	struct ldap_search_ctx *sctx = req->ctx;
+	int msgtype = ldap_msgtype(message);
+	char *result_errmsg = NULL;
+	int ret, result_err;
+
+	if (msgtype != LDAP_RES_SEARCH_ENTRY &&
+	    msgtype != LDAP_RES_SEARCH_RESULT) {
+		*finished_r = FALSE;
+		return LDAP_SUCCESS;
+	}
+	*finished_r = TRUE;
+
+	ret = ldap_parse_result(conn->conn, message, &result_err, NULL,
+				&result_errmsg, NULL, NULL, 0);
+	if (ret == LDAP_NO_RESULTS_RETURNED) {
+		ret = LDAP_SUCCESS;
+	} else if (ret != LDAP_SUCCESS) {
+		ldap_search_result_failure(req, ret, t_strdup_printf(
+			"ldap_parse_result() failed for search: %s", ldap_err2string(ret)));
+		return ret;
+	} else if (result_err != LDAP_SUCCESS) {
+		const struct ldap_search_input *input = &req->input.search;
+		const char *error = result_errmsg != NULL ?
+			result_errmsg : ldap_err2string(result_err);
+		ldap_search_result_failure(req, result_err, t_strdup_printf(
+			"ldap_search_ext(base=%s, scope=%d, filter=%s) failed: %s",
+			input->base_dn, input->scope, input->filter, error));
+		ldap_memfree(result_errmsg);
+		return result_err;
+	}
+
+	LDAPMessage *res = ldap_first_entry(conn->conn, message);
+
+	while(res != NULL) {
+		struct ldap_entry *obj = p_new(req->pool, struct ldap_entry, 1);
+		ldap_entry_init(obj, &(sctx->res), message);
+		array_append(&(sctx->res.entries), obj, 1);
+		res = ldap_next_entry(conn->conn, res);
+	}
+
+	if (msgtype == LDAP_RES_SEARCH_RESULT) {
+		ldap_search_result_success(req);
+		return LDAP_SUCCESS;
+	}
+
+	*finished_r = FALSE;
+	return LDAP_SUCCESS;
+}
+
+static int
+ldap_search_send(struct ldap_connection *conn, struct ldap_op_queue_entry *req,
+		 const char **error_r)
+{
+	const struct ldap_search_input *input = &req->input.search;
+	LDAPControl manageDSAIT = {
+		LDAP_CONTROL_MANAGEDSAIT, {0, 0}, 0
+	};
+	/* try to use ManageDSAIT if available */
+	LDAPControl *sctrls[] = {
+		&manageDSAIT,
+		NULL
+	};
+
+	struct timeval tv = {
+		.tv_sec = req->timeout_secs,
+		.tv_usec = 0
+	};
+
+	int ret = ldap_search_ext(conn->conn,
+		input->base_dn,
+		input->scope,
+		input->filter,
+		(char**)input->attributes,
+		0,
+		sctrls,
+		NULL,
+		&tv,
+		input->size_limit,
+		&(req->msgid));
+
+	if (ret != LDAP_SUCCESS) {
+		*error_r = t_strdup_printf(
+			"ldap_search_ext(base=%s, scope=%d, filter=%s) failed: %s",
+			input->base_dn, input->scope, input->filter,
+			ldap_err2string(ret));
+	}
+	return ret;
+}
+
+void ldap_connection_search_start(struct ldap_connection *conn,
+				  const struct ldap_search_input *input,
+				  ldap_result_callback_t *callback,
+				  void *context)
+{
+	struct ldap_op_queue_entry *req;
+	pool_t pool = pool_alloconly_create(MEMPOOL_GROWING "ldap search", 128);
+	req = p_new(pool, struct ldap_op_queue_entry, 1);
+	req->pool = pool;
+
+	struct ldap_search_ctx *sctx = p_new(pool, struct ldap_search_ctx, 1);
+	sctx->res.conn = conn;
+	sctx->res.pool = pool;
+
+	p_array_init(&(sctx->res.entries), req->pool, 8);
+
+	req->internal_response_cb = ldap_search_callback;
+
+	req->result_callback = callback;
+	req->result_callback_ctx = context;
+	req->input.search = *input;
+
+	/* copy strings */
+	req->input.search.base_dn = p_strdup(req->pool, input->base_dn);
+	req->input.search.filter = p_strdup(req->pool, input->filter);
+
+	if (input->attributes != NULL) {
+		ARRAY_TYPE(const_string) arr;
+		p_array_init(&arr, req->pool, 8);
+		for(const char **ptr = (const char**)input->attributes; *ptr != NULL; ptr++) {
+			const char *tmp = p_strdup(req->pool, *ptr);
+			array_append(&arr, &tmp, 1);
+		}
+		array_append_zero(&arr);
+		req->input.search.attributes = array_idx_modifiable(&arr, 0);
+	}
+
+	req->send_request_cb = ldap_search_send;
+	sctx->input = &req->input.search;
+	req->ctx = sctx;
+	req->timeout_secs = input->timeout_secs;
+
+	ldap_connection_queue_request(conn, req);
+}