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