Mercurial > dovecot > core-2.2
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); +}