summaryrefslogtreecommitdiffstats
path: root/src/lib-ldap/ldap-connection.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-ldap/ldap-connection.c714
1 files changed, 714 insertions, 0 deletions
diff --git a/src/lib-ldap/ldap-connection.c b/src/lib-ldap/ldap-connection.c
new file mode 100644
index 0000000..95a91ea
--- /dev/null
+++ b/src/lib-ldap/ldap-connection.c
@@ -0,0 +1,714 @@
+/* Copyright (c) 2016-2018 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);
+static
+int ldap_connection_connect(struct ldap_connection *conn);
+static
+void ldap_connection_send_next(struct ldap_connection *conn);
+
+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 *req =
+ array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue, i));
+ timeout_remove(&req->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);
+ ldap_set_option(conn->conn, LDAP_OPT_X_TLS_REQUIRE_CERT, &opt);
+#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN
+ /* 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);
+#endif
+ 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.cert != NULL)
+ ldap_set_option(conn->conn, LDAP_OPT_X_TLS_CERTFILE, conn->ssl_set.cert.cert);
+ if (conn->ssl_set.cert.key != NULL)
+ ldap_set_option(conn->conn, LDAP_OPT_X_TLS_KEYFILE, conn->ssl_set.cert.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);
+
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+ opt = 0;
+ ldap_set_option(conn->conn, LDAP_OPT_X_TLS_NEWCTX, &opt);
+#endif
+
+ return 0;
+}
+
+bool ldap_connection_have_settings(struct ldap_connection *conn,
+ const struct ldap_client_settings *set)
+{
+ const struct ldap_client_settings *conn_set = &conn->set;
+
+ if (strcmp(conn_set->uri, set->uri) != 0)
+ return FALSE;
+ if (null_strcmp(conn_set->bind_dn, set->bind_dn) != 0)
+ return FALSE;
+ if (null_strcmp(conn_set->password, set->password) != 0)
+ return FALSE;
+ if (conn_set->timeout_secs != set->timeout_secs ||
+ conn_set->max_idle_time_secs != set->max_idle_time_secs ||
+ conn_set->debug != set->debug ||
+ conn_set->require_ssl != set->require_ssl ||
+ conn_set->start_tls != set->start_tls)
+ return FALSE;
+
+ if (set->ssl_set == NULL || !set->start_tls)
+ return TRUE;
+
+ /* check SSL settings */
+ if (null_strcmp(conn->ssl_set.min_protocol, set->ssl_set->min_protocol) != 0)
+ return FALSE;
+ if (null_strcmp(conn->ssl_set.cipher_list, set->ssl_set->cipher_list) != 0)
+ return FALSE;
+ if (null_strcmp(conn->ssl_set.ca_file, set->ssl_set->ca_file) != 0)
+ return FALSE;
+ if (null_strcmp(conn->ssl_set.cert.cert, set->ssl_set->cert.cert) != 0)
+ return FALSE;
+ if (null_strcmp(conn->ssl_set.cert.key, set->ssl_set->cert.key) != 0)
+ return FALSE;
+ return TRUE;
+}
+
+int ldap_connection_init(struct ldap_client *client,
+ const struct ldap_client_settings *set,
+ struct ldap_connection **conn_r, const char **error_r)
+{
+ i_assert(set->uri != NULL);
+
+ if (set->require_ssl &&
+ !set->start_tls &&
+ strncmp("ldaps://",set->uri,8) != 0) {
+ *error_r = t_strdup_printf("ldap_connection_init(uri=%s) failed: %s", set->uri,
+ "uri does not start with ldaps and ssl required without start TLS");
+ return -1;
+ }
+
+ pool_t pool = pool_alloconly_create("ldap connection", 1024);
+ struct ldap_connection *conn = p_new(pool, struct ldap_connection, 1);
+ conn->pool = pool;
+
+ 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.cert.key_password = NULL;
+ conn->ssl_set.cert_username_field = NULL;
+ conn->ssl_set.crypto_device = NULL;
+
+ if (set->ssl_set != NULL) {
+ /* keep in sync with ldap_connection_have_settings() */
+ conn->set.ssl_set = &conn->ssl_set;
+ conn->ssl_set.min_protocol = p_strdup(pool, set->ssl_set->min_protocol);
+ 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.cert = p_strdup(pool, set->ssl_set->cert.cert);
+ conn->ssl_set.cert.key = p_strdup(pool, set->ssl_set->cert.key);
+ }
+ i_assert(ldap_connection_have_settings(conn, set));
+
+ 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 *req =
+ array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue, i));
+ if (req->to_abort != NULL)
+ req->to_abort = io_loop_move_timeout(&req->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;
+ i_zero(&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;
+ i_zero(&res);
+ res.conn = conn;
+ res.openldap_ret = LDAP_SUCCESS;
+ if (req->result_callback != NULL)
+ req->result_callback(&res, req->result_callback_ctx);
+}
+
+static struct ldap_op_queue_entry *
+ldap_connection_next_unsent_request(struct ldap_connection *conn,
+ unsigned int *index_r)
+{
+ struct ldap_op_queue_entry *last_req = NULL;
+ *index_r = 0;
+
+ for (unsigned int i = 0; i < aqueue_count(conn->request_queue); i++) {
+ struct ldap_op_queue_entry *req =
+ array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue, i));
+ if (req->msgid > -1)
+ break;
+ *index_r = i;
+ last_req = req;
+ }
+ return last_req;
+}
+
+static
+void ldap_connection_send_next(struct ldap_connection *conn)
+{
+ unsigned int index;
+ struct ldap_op_queue_entry *req;
+
+ timeout_remove(&conn->to_reconnect);
+
+ if (conn->state == LDAP_STATE_DISCONNECT) {
+ if (ldap_connection_connect(conn) == -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 = ldap_connection_next_unsent_request(conn, &index);
+ /* 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;
+
+ i_zero(&res);
+ res.openldap_ret = ret;
+ res.error_string = error;
+ if (req->result_callback != NULL)
+ req->result_callback(&res, req->result_callback_ctx);
+
+ ldap_connection_request_destroy(&req);
+ aqueue_delete(conn->request_queue, index);
+ } else conn->pending++;
+}
+
+static
+void ldap_connection_request_destroy(struct ldap_op_queue_entry **_req)
+{
+ struct ldap_op_queue_entry *req = *_req;
+
+ *_req = 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);
+
+ switch(conn->state) {
+ case LDAP_STATE_TLS:
+ if (msgtype != LDAP_RES_EXTENDED) {
+ *finished_r = FALSE;
+ return LDAP_SUCCESS;
+ }
+ if (ret != 0) {
+ 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;
+ } else if (result_err != 0) {
+ if (conn->set.require_ssl) {
+ ldap_connection_result_failure(conn, req, result_err, t_strdup_printf(
+ "ldap_start_tls(uri=%s) failed: %s",
+ conn->set.uri, result_errmsg));
+ ldap_memfree(result_errmsg);
+ return LDAP_INVALID_CREDENTIALS; /* make sure it disconnects */
+ }
+ } else {
+ ret = ldap_parse_extended_result(conn->conn, message, &retoid, NULL, 0);
+ /* retoid can be NULL even if ret == 0 */
+ if (ret == 0) {
+ ret = ldap_install_tls(conn->conn);
+ if (ret != 0) {
+ // if this fails we have to abort
+ ldap_connection_result_failure(conn, req, ret, t_strdup_printf(
+ "ldap_start_tls(uri=%s) failed: %s",
+ conn->set.uri, ldap_err2string(ret)));
+ return LDAP_INVALID_CREDENTIALS;
+ }
+ }
+ if (ret != LDAP_SUCCESS) {
+ if (conn->set.require_ssl) {
+ ldap_connection_result_failure(conn, req, ret, t_strdup_printf(
+ "ldap_start_tls(uri=%s) failed: %s",
+ conn->set.uri, ldap_err2string(ret)));
+ return LDAP_UNAVAILABLE;
+ }
+ } else {
+ if (conn->set.debug > 0)
+ i_debug("Using TLS connection to remote LDAP server");
+ }
+ ldap_memfree(retoid);
+ }
+ conn->state = LDAP_STATE_AUTH;
+ return ldap_connect_next_message(conn, req, finished_r);
+ case LDAP_STATE_AUTH:
+ 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;
+ }
+ 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();
+ }
+ i_unreached();
+}
+
+static
+void ldap_connection_abort_request(struct ldap_op_queue_entry *req)
+{
+ struct ldap_result res;
+
+ /* too bad */
+ timeout_remove(&req->to_abort);
+ if (req->msgid > -1)
+ ldap_abandon_ext(req->conn->conn, req->msgid, NULL, NULL);
+
+ i_zero(&res);
+ res.openldap_ret = LDAP_TIMEOUT;
+ res.error_string = "Aborting LDAP request after timeout";
+ if (req->result_callback != NULL)
+ 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 *arr_req =
+ array_idx_elem(&req->conn->request_array,
+ aqueue_idx(req->conn->request_queue, i));
+ if (req == arr_req) {
+ aqueue_delete(req->conn->request_queue, i);
+ ldap_connection_request_destroy(&req);
+ return;
+ }
+ }
+ i_unreached();
+}
+
+static
+void ldap_connection_abort_all_requests(struct ldap_connection *conn)
+{
+ struct ldap_result res;
+ i_zero(&res);
+ res.openldap_ret = LDAP_TIMEOUT;
+ res.error_string = "Aborting LDAP requests due to failure";
+
+ unsigned int n = aqueue_count(conn->request_queue);
+ for (unsigned int i = 0; i < n; i++) {
+ struct ldap_op_queue_entry **reqp =
+ array_idx_modifiable(&conn->request_array,
+ aqueue_idx(conn->request_queue, i));
+ timeout_remove(&(*reqp)->to_abort);
+ if ((*reqp)->result_callback != NULL)
+ (*reqp)->result_callback(&res, (*reqp)->result_callback_ctx);
+ ldap_connection_request_destroy(reqp);
+ }
+ aqueue_clear(conn->request_queue);
+}
+
+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:
+ /* if we should not disable SSL, and the URI is not ldaps:// */
+ if (!conn->set.start_tls || strstr(conn->set.uri, "ldaps://") == NULL) {
+ 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;
+ }
+ conn->state = LDAP_STATE_AUTH;
+ /* fall through */
+ 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;
+}
+
+static
+int ldap_connection_connect(struct ldap_connection *conn)
+{
+ 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->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_secs > 0)
+ conn->to_disconnect = timeout_add(conn->set.max_idle_time_secs * 1000, ldap_connection_kill, conn);
+ return 0;
+}
+
+void ldap_connection_kill(struct ldap_connection *conn)
+{
+ io_remove_closed(&conn->io);
+ timeout_remove(&conn->to_disconnect);
+ timeout_remove(&conn->to_reconnect);
+ if (conn->request_queue != NULL) {
+ unsigned int n = aqueue_count(conn->request_queue);
+
+ for (unsigned int i = 0; i < n; i++) {
+ struct ldap_op_queue_entry *req =
+ array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue, i));
+ if (req->msgid > -1)
+ ldap_abandon_ext(conn->conn, req->msgid, NULL, NULL);
+ req->msgid = -1;
+ }
+ }
+ if (conn->conn != NULL) {
+ ldap_unbind_ext(conn->conn, NULL, NULL);
+ 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 *req =
+ array_idx_elem(&conn->request_array,
+ aqueue_idx(conn->request_queue, i));
+ if (req->msgid == msgid) {
+ *idx_r = i;
+ return req;
+ }
+ }
+ 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:
+ case LDAP_OPERATIONS_ERROR:
+ case LDAP_BUSY:
+ /* requeue */
+ ldap_connection_kill(conn);
+ ldap_connection_send_next(conn);
+ finished = FALSE;
+ break;
+ case LDAP_INVALID_CREDENTIALS: {
+ /* fail everything */
+ ldap_connection_kill(conn);
+ ldap_connection_abort_all_requests(conn);
+ return 0;
+ }
+ 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:
+ 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;
+}