>From eb13ff0e6e3039c18b0876c7f0e1de3dc76e8a9d Mon Sep 17 00:00:00 2001
From: Martin Nagy <mnagy@redhat.com>
Date: Tue, 20 Apr 2010 21:08:05 +0200
Subject: [PATCH 1/2] Make ldap simple bind asynchronous

We make openldap use asynchronous connections by setting the
LDAP_OPT_CONNECT_ASYNC option. When ldap_sasl_bind() is first called, it
can either return LDAP_X_CONNECTING without blocking, or succeed
immediately without blocking (e.g. when connecting to localhost). If
LDAP_X_CONNECTING is returned, the function has to be called again. We
create a callback that is called whenever openldap creates a new file
descriptor. This callback registers the file descriptor with tevent to
watch for its availability for reading and writing.
---
 src/providers/ldap/sdap.h                  |   11 ++
 src/providers/ldap/sdap_async.c            |   42 ++++++++
 src/providers/ldap/sdap_async_connection.c |  155 ++++++++++++++++++++--------
 src/providers/ldap/sdap_async_private.h    |    5 +
 src/providers/ldap/sdap_fd_events.c        |   66 +++++++++++-
 5 files changed, 231 insertions(+), 48 deletions(-)

diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index aca9845..8c15a3a 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -53,18 +53,29 @@ struct sdap_op {
     struct sdap_msg *last;
 };
 
+struct ldap_cb_data;
+
+struct request_guard {
+    struct fd_event_item *ptr;
+};
+
 struct fd_event_item {
     struct fd_event_item *prev;
     struct fd_event_item *next;
 
     int fd;
     struct tevent_fd *fde;
+
+    struct ldap_cb_data *cb_data;
+    struct tevent_req *wakeup_req;
+    struct request_guard *guard;
 };
 
 struct ldap_cb_data {
     struct sdap_handle *sh;
     struct tevent_context *ev;
     struct fd_event_item *fd_list;
+    struct tevent_req *fd_wakeup_req;
 };
 
 struct sdap_handle {
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
index a25c50b..2d27bb6 100644
--- a/src/providers/ldap/sdap_async.c
+++ b/src/providers/ldap/sdap_async.c
@@ -117,6 +117,8 @@ static void sdap_handle_release(struct sdap_handle *sh)
 }
 
 /* ==Parse-Results-And-Handle-Disconnections============================== */
+static void sdap_finish_bind(struct ldap_cb_data *cb_data,
+                             struct tevent_fd *fde);
 static void sdap_process_message(struct tevent_context *ev,
                                  struct sdap_handle *sh, LDAPMessage *msg);
 static void sdap_process_result(struct tevent_context *ev, void *pvt);
@@ -124,6 +126,22 @@ static void sdap_process_next_reply(struct tevent_context *ev,
                                     struct tevent_timer *te,
                                     struct timeval tv, void *pvt);
 
+
+void sdap_async_ldap_result(struct tevent_context *ev,
+                            struct tevent_fd *fde,
+                            uint16_t flags, void *pvt)
+{
+    struct ldap_cb_data *cb_data = talloc_get_type(pvt, struct ldap_cb_data);
+
+    if (flags & TEVENT_FD_WRITE) {
+        sdap_finish_bind(cb_data, fde);
+    }
+
+    if (flags & TEVENT_FD_READ) {
+        sdap_process_result(ev, cb_data->sh);
+    }
+}
+
 void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde,
                       uint16_t flags, void *pvt)
 {
@@ -137,6 +155,30 @@ static void sdap_ldap_next_result(struct tevent_context *ev,
     sdap_process_result(ev, pvt);
 }
 
+static void sdap_finish_bind(struct ldap_cb_data *cb_data,
+                             struct tevent_fd *fde)
+{
+    struct fd_event_item *fd_event_item;
+
+    DEBUG(8, ("Trace: sh[%p], ldap[%p], fde[%p]\n",
+              cb_data->sh, cb_data->sh->ldap, fde));
+
+    DLIST_FOR_EACH(fd_event_item, cb_data->fd_list) {
+        if (fd_event_item->fde == fde) {
+            break;
+        }
+    }
+    if (fd_event_item != NULL && fd_event_item->wakeup_req != NULL) {
+        if (fd_event_item->guard) {
+            fd_event_item->guard->ptr = NULL;
+        }
+        tevent_req_notify_callback(fd_event_item->wakeup_req);
+    } else if (fd_event_item == NULL) {
+        DEBUG(1, ("Bug: Couldn't find fd_event_item\n"));
+    }
+    tevent_fd_set_flags(fde, tevent_fd_get_flags(fde) & ~TEVENT_FD_WRITE);
+}
+
 static void sdap_process_result(struct tevent_context *ev, void *pvt)
 {
     struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle);
diff --git a/src/providers/ldap/sdap_async_connection.c b/src/providers/ldap/sdap_async_connection.c
index 9871dc2..6a17579 100644
--- a/src/providers/ldap/sdap_async_connection.c
+++ b/src/providers/ldap/sdap_async_connection.c
@@ -259,11 +259,21 @@ struct simple_bind_state {
     struct sdap_msg *reply;
     struct sdap_ppolicy_data *ppolicy;
     int result;
+    int msgid;
+
+    enum {
+        /* Initial state. */
+        SBS_INIT,
+        /* Bind has started, we are waiting for the
+         * file descriptor to be ready. */
+        SBS_BIND_INITIALIZED,
+        /* Bind request was sent, but the file descriptor
+         * callback was not called yet. */
+        SBS_BIND_SENT,
+    } state;
 };
 
-static void simple_bind_done(struct sdap_op *op,
-                             struct sdap_msg *reply,
-                             int error, void *pvt);
+static void simple_bind_step(struct tevent_req *subreq);
 
 static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx,
                                            struct tevent_context *ev,
@@ -272,16 +282,13 @@ static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx,
                                            struct berval *pw)
 {
     struct tevent_req *req;
+    struct tevent_req *subreq;
     struct simple_bind_state *state;
-    int ret = EOK;
-    int msgid;
-    int ldap_err;
-    LDAPControl *request_controls[2];
 
     req = tevent_req_create(memctx, &state, struct simple_bind_state);
     if (!req) return NULL;
 
-    state->reply = talloc(state, struct sdap_msg);
+    state->reply = talloc_zero(state, struct sdap_msg);
     if (!state->reply) {
         talloc_zfree(req);
         return NULL;
@@ -291,58 +298,120 @@ static struct tevent_req *simple_bind_send(TALLOC_CTX *memctx,
     state->sh = sh;
     state->user_dn = user_dn;
     state->pw = pw;
+    state->state = SBS_INIT;
 
-    ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
-                                  0, NULL, 0, &request_controls[0]);
-    if (ret != LDAP_SUCCESS) {
-        DEBUG(1, ("sss_ldap_control_create failed.\n"));
-        goto fail;
+    /* FIXME: get timeouts from configuration, for now 5 secs. */
+    subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs(5, 0));
+    if (subreq == NULL) {
+        talloc_zfree(req);
+        return NULL;
     }
-    request_controls[1] = NULL;
+    tevent_req_set_callback(subreq, simple_bind_step, req);
+    tevent_req_notify_callback(subreq);
 
-    DEBUG(4, ("Executing simple bind as: %s\n", state->user_dn));
+    return req;
+}
 
-    ret = ldap_sasl_bind(state->sh->ldap, state->user_dn, LDAP_SASL_SIMPLE,
-                         state->pw, request_controls, NULL, &msgid);
-    ldap_control_free(request_controls[0]);
-    if (ret == -1 || msgid == -1) {
-        ret = ldap_get_option(state->sh->ldap,
-                              LDAP_OPT_RESULT_CODE, &ldap_err);
-        if (ret != LDAP_OPT_SUCCESS) {
-            DEBUG(1, ("ldap_bind failed (couldn't get ldap error)\n"));
-            ret = LDAP_LOCAL_ERROR;
-        } else {
-            DEBUG(1, ("ldap_bind failed (%d)[%s]\n",
-                      ldap_err, ldap_err2string(ldap_err)));
-            ret = ldap_err;
+static void simple_bind_done(struct sdap_op *op,
+                             struct sdap_msg *reply,
+                             int error, void *pvt);
+
+static void simple_bind_step(struct tevent_req *subreq)
+{
+    struct simple_bind_state *state;
+    int ret;
+    struct sdap_handle *sh;
+    LDAPControl *request_controls[2];
+    struct tevent_req *req;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct simple_bind_state);
+    sh = state->sh;
+
+    enum tevent_req_state err_state;
+    uint64_t error;
+
+    if (tevent_req_is_error(subreq, &err_state, &error)) {
+        if (err_state == TEVENT_REQ_TIMED_OUT) {
+            DEBUG(2, ("Connection timed out\n"));
+            goto fail;
+        }
+    }
+
+    /* SBS_BIND_INITIALIZED means the file descriptor is ready and
+     * the fd callback was called. */
+    if (state->state == SBS_INIT || state->state == SBS_BIND_INITIALIZED) {
+        DEBUG(4, ("Executing simple bind as: %s\n", state->user_dn));
+
+        ret = sss_ldap_control_create(LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+                                      0, NULL, 0, &request_controls[0]);
+        if (ret != LDAP_SUCCESS) {
+            DEBUG(1, ("sss_ldap_control_create failed.\n"));
+            goto fail;
+        }
+        request_controls[1] = NULL;
+
+        set_fd_wakeup_req(sh, subreq);
+        ret = ldap_sasl_bind(sh->ldap, state->user_dn, LDAP_SASL_SIMPLE,
+                             state->pw, request_controls, NULL, &state->msgid);
+        ldap_control_free(request_controls[0]);
+        set_fd_wakeup_req(sh, NULL);
+
+        if (ret == LDAP_X_CONNECTING) {
+            /* The ldap socket is not connected yet, we will let the LDAP
+             * connect callback call us again. */
+            DEBUG(4, ("Simple bind is in progress\n"));
+            if (state->state == SBS_BIND_INITIALIZED) {
+                DEBUG(1, ("Bug: ldap_sasl_bind() shouldn't be called "
+                          "more than twice\n"));
+            }
+            state->state = SBS_BIND_INITIALIZED;
+            return;
+        } else if (ret == -1 || state->msgid == -1) {
+            int ldap_err = -1;
+            const char *str;
+
+            ret = ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &ldap_err);
+            str = ret == LDAP_OPT_SUCCESS ? ldap_err2string(ldap_err)
+                                          : "unknown error";
+            DEBUG(1, ("ldap_bind failed (%d)[%s]\n", ldap_err, str));
+            goto fail;
+        }
+
+        if (state->state == SBS_INIT) {
+            /* We managed to connect on the first try, but we have to give
+             * the file descriptor callback a chance to be called. */
+            state->state = SBS_BIND_SENT;
+            return;
         }
-        goto fail;
     }
-    DEBUG(8, ("ldap simple bind sent, msgid = %d\n", msgid));
+
+    /* At this point, everything is setup, we can wait for the response. */
+    DEBUG(8, ("ldap simple bind sent, msgid = %d\n", state->msgid));
 
     if (!sh->connected) {
-        ret = sdap_set_connected(sh, ev);
+        ret = sdap_set_connected(sh, state->ev);
         if (ret) goto fail;
     }
 
+    tevent_wakeup_recv(subreq);
+    talloc_zfree(subreq);
+
     /* FIXME: get timeouts from configuration, for now 5 secs. */
-    ret = sdap_op_add(state, ev, sh, msgid,
+    ret = sdap_op_add(state, state->ev, state->sh, state->msgid,
                       simple_bind_done, req, 5, &state->op);
     if (ret) {
         DEBUG(1, ("Failed to set up operation!\n"));
-        goto fail;
+        tevent_req_error(req, EIO);
     }
 
-    return req;
+    return;
 
 fail:
-    if (ret == LDAP_SERVER_DOWN) {
-        tevent_req_error(req, ETIMEDOUT);
-    } else {
-        tevent_req_error(req, EIO);
-    }
-    tevent_req_post(req, ev);
-    return req;
+    tevent_wakeup_recv(subreq);
+    talloc_zfree(subreq);
+
+    tevent_req_error(req, EIO);
 }
 
 static void simple_bind_done(struct sdap_op *op,
diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h
index 75597c6..4b8388f 100644
--- a/src/providers/ldap/sdap_async_private.h
+++ b/src/providers/ldap/sdap_async_private.h
@@ -30,6 +30,11 @@ struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx);
 
 void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde,
                       uint16_t flags, void *pvt);
+void set_fd_wakeup_req(struct sdap_handle *sh,
+                       struct tevent_req *wakeup_req);
+void sdap_async_ldap_result(struct tevent_context *ev,
+                            struct tevent_fd *fde,
+                            uint16_t flags, void *pvt);
 
 int setup_ldap_connection_callbacks(struct sdap_handle *sh,
                                     struct tevent_context *ev);
diff --git a/src/providers/ldap/sdap_fd_events.c b/src/providers/ldap/sdap_fd_events.c
index 1152778..fb274fd 100644
--- a/src/providers/ldap/sdap_fd_events.c
+++ b/src/providers/ldap/sdap_fd_events.c
@@ -34,6 +34,15 @@ struct sdap_fd_events {
 };
 
 #ifdef HAVE_LDAP_CONNCB
+void set_fd_wakeup_req(struct sdap_handle *sh,
+                       struct tevent_req *wakeup_req)
+{
+    struct ldap_cb_data *cb_data;
+
+    cb_data = talloc_get_type(sh->sdap_fd_events->conncb->lc_arg, struct ldap_cb_data);
+    cb_data->fd_wakeup_req = wakeup_req;
+}
+
 static int remove_connection_callback(TALLOC_CTX *mem_ctx)
 {
     int lret;
@@ -51,6 +60,24 @@ static int remove_connection_callback(TALLOC_CTX *mem_ctx)
     return lret;
 }
 
+static int request_guard_destructor(struct request_guard *guard)
+{
+    if (guard->ptr) {
+        guard->ptr->guard = NULL;
+        talloc_free(guard->ptr);
+    }
+    return 0;
+}
+
+static int fd_event_item_destructor(struct fd_event_item *fd_event_item)
+{
+    if (fd_event_item->guard) {
+        fd_event_item->guard->ptr = NULL;
+    }
+    DLIST_REMOVE(fd_event_item->cb_data->fd_list, fd_event_item);
+    return 0;
+}
+
 static int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb,
                                           LDAPURLDesc *srv,
                                           struct sockaddr *addr,
@@ -59,6 +86,7 @@ static int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb,
     int ret;
     ber_socket_t ber_fd;
     struct fd_event_item *fd_event_item;
+    struct request_guard *guard;
     struct ldap_cb_data *cb_data = talloc_get_type(ctx->lc_arg,
                                                    struct ldap_cb_data);
 
@@ -83,16 +111,35 @@ static int sdap_ldap_connect_callback_add(LDAP *ld, Sockbuf *sb,
     }
 
     fd_event_item->fde = tevent_add_fd(cb_data->ev, fd_event_item, ber_fd,
+#ifdef LDAP_OPT_CONNECT_ASYNC
+                                       TEVENT_FD_READ | TEVENT_FD_WRITE,
+                                       sdap_async_ldap_result,
+#else
                                        TEVENT_FD_READ, sdap_ldap_result,
-                                       cb_data->sh);
+#endif
+                                       cb_data);
     if (fd_event_item->fde == NULL) {
         DEBUG(1, ("tevent_add_fd failed.\n"));
         talloc_free(fd_event_item);
         return ENOMEM;
     }
     fd_event_item->fd = ber_fd;
+    fd_event_item->cb_data = cb_data;
+
+    fd_event_item->wakeup_req = cb_data->fd_wakeup_req;
+    if (fd_event_item->wakeup_req) {
+        guard = talloc(fd_event_item->wakeup_req, struct request_guard);
+        if (guard == NULL) {
+            talloc_free(fd_event_item);
+            return ENOMEM;
+        }
+        guard->ptr = fd_event_item;
+        fd_event_item->guard = guard;
+        talloc_set_destructor(guard, request_guard_destructor);
+    }
 
     DLIST_ADD(cb_data->fd_list, fd_event_item);
+    talloc_set_destructor(fd_event_item, fd_event_item_destructor);
 
     return LDAP_SUCCESS;
 }
@@ -133,7 +180,7 @@ static void sdap_ldap_connect_callback_del(LDAP *ld, Sockbuf *sb,
     return;
 }
 
-#else
+#else /* !HAVE_LDAP_CONNCB */
 
 static int get_fd_from_ldap(LDAP *ldap, int *fd)
 {
@@ -185,7 +232,7 @@ static int sdap_install_ldap_callbacks(struct sdap_handle *sh,
     return EOK;
 }
 
-#endif
+#endif /* HAVE_LDAP_CONNCB */
 
 
 errno_t setup_ldap_connection_callbacks(struct sdap_handle *sh,
@@ -231,6 +278,15 @@ errno_t setup_ldap_connection_callbacks(struct sdap_handle *sh,
         goto fail;
     }
 
+#ifdef LDAP_OPT_CONNECT_ASYNC
+    ret = ldap_set_option(sh->ldap, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_ON);
+    if (ret != LDAP_OPT_SUCCESS) {
+        DEBUG(1, ("Failed to set connection as asynchronous\n"));
+        ret = EFAULT;
+        goto fail;
+    }
+#endif
+
     talloc_set_destructor((TALLOC_CTX *) sh->sdap_fd_events->conncb,
                           remove_connection_callback);
 
@@ -239,10 +295,10 @@ errno_t setup_ldap_connection_callbacks(struct sdap_handle *sh,
 fail:
     talloc_zfree(sh->sdap_fd_events);
     return ret;
-#else
+#else /* !HAVE_LDAP_CONNCB */
     DEBUG(9, ("LDAP connection callbacks are not supported.\n"));
     return EOK;
-#endif
+#endif /* HAVE_LDAP_CONNCB */
 }
 
 errno_t sdap_set_connected(struct sdap_handle *sh, struct tevent_context *ev)
-- 
1.6.2.5

