]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Rework LDAP bind auths to use trunk connection
authorNick Porter <nick@portercomputing.co.uk>
Tue, 18 Apr 2023 11:16:35 +0000 (12:16 +0100)
committerNick Porter <nick@portercomputing.co.uk>
Thu, 4 May 2023 14:27:23 +0000 (15:27 +0100)
src/lib/ldap/base.h
src/lib/ldap/bind.c
src/lib/ldap/connection.c
src/lib/ldap/sasl.c
src/modules/rlm_ldap/rlm_ldap.c

index 7e010a4873c30b0831600f552a615408c7aa35a0..92bda7ee5553d43b50a4383a82d97975ddafd9a2 100644 (file)
@@ -381,7 +381,6 @@ typedef struct {
        fr_trunk_conf_t         *trunk_conf;    //!< Module trunk config
        fr_trunk_conf_t         *bind_trunk_conf;       //!< Trunk config for bind auth trunk
        fr_event_list_t         *el;            //!< Thread event list for callbacks / timeouts
-       fr_connection_t         *conn;          //!< LDAP connection used for bind auths
        fr_ldap_thread_trunk_t  *bind_trunk;    //!< LDAP trunk used for bind auths
        fr_rb_tree_t            *binds;         //!< Tree of outstanding bind auths
 } fr_ldap_thread_t;
@@ -607,6 +606,7 @@ typedef enum {
 typedef struct {
        fr_rb_node_t            node;           //!< Entry in the tree of outstanding bind requests.
        fr_ldap_thread_t        *thread;        //!< This bind is being run by.
+       fr_trunk_request_t      *treq;          //!< Trunk request this bind is associated with.
        int                     msgid;          //!< libldap msgid for this bind.
        request_t               *request;       //!< this bind relates to.
        fr_ldap_bind_type_t     type;           //!< type of bind.
index ef53582d62729b0b233a5f6d2608e450f93880ac..90456507fa479ac05748f02f12b905b4d66ec011 100644 (file)
@@ -233,38 +233,13 @@ int fr_ldap_bind_async(fr_ldap_connection_t *c,
        return 0;
 }
 
-/** Submit an async LDAP auth bind
+/** Yield interpreter after queueing LDAP bind
  *
  */
-static unlang_action_t ldap_async_auth_bind_start(UNUSED rlm_rcode_t *p_result, UNUSED int *priority, request_t *request, void *uctx)
+static unlang_action_t ldap_async_auth_bind_start(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
+                                                 UNUSED request_t *request, UNUSED void *uctx)
 {
-       fr_ldap_bind_auth_ctx_t *bind_auth_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_auth_ctx_t);
-       fr_ldap_bind_ctx_t      *bind_ctx = bind_auth_ctx->bind_ctx;
-
-       int                     ret;
-       struct berval           cred;
-
-       RDEBUG2("Starting bind auth operation as %s", bind_ctx->bind_dn);
-
-       if (bind_ctx->password) {
-               memcpy(&cred.bv_val, &bind_ctx->password, sizeof(cred.bv_val));
-               cred.bv_len = talloc_array_length(bind_ctx->password) - 1;
-       } else {
-               cred.bv_val = NULL;
-               cred.bv_len = 0;
-       }
-
-       ret = ldap_sasl_bind(bind_auth_ctx->bind_ctx->c->handle, bind_ctx->bind_dn, LDAP_SASL_SIMPLE,
-                            &cred, NULL, NULL, &bind_auth_ctx->msgid);
-
-       switch (ret) {
-       case LDAP_SUCCESS:
-               fr_rb_insert(bind_auth_ctx->thread->binds, bind_auth_ctx);
-               return UNLANG_ACTION_YIELD;
-
-       default:
-               return UNLANG_ACTION_FAIL;
-       }
+       return UNLANG_ACTION_YIELD;
 }
 
 /** Handle the return code from parsed LDAP results to set the module rcode
@@ -274,45 +249,54 @@ static unlang_action_t ldap_async_auth_bind_results(rlm_rcode_t *p_result, UNUSE
 {
        fr_ldap_bind_auth_ctx_t *bind_auth_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_auth_ctx_t);
        fr_ldap_bind_ctx_t      *bind_ctx = bind_auth_ctx->bind_ctx;
+       rlm_rcode_t             rcode = RLM_MODULE_OK;
 
        switch (bind_auth_ctx->ret) {
        case LDAP_PROC_SUCCESS:
                RDEBUG2("Bind as user \"%s\" was successful", bind_ctx->bind_dn);
-               RETURN_MODULE_OK;
+               break;
 
        case LDAP_PROC_NOT_PERMITTED:
                RDEBUG2("Bind as user \"%s\" not permitted", bind_ctx->bind_dn);
-               RETURN_MODULE_DISALLOW;
+               rcode = RLM_MODULE_DISALLOW;
+               break;
 
        case LDAP_PROC_REJECT:
                RDEBUG2("Bind as user \"%s\" rejected", bind_ctx->bind_dn);
-               RETURN_MODULE_REJECT;
+               rcode = RLM_MODULE_REJECT;
+               break;
 
        case LDAP_PROC_BAD_DN:
-               RETURN_MODULE_INVALID;
+               rcode = RLM_MODULE_INVALID;
+               break;
 
        case LDAP_PROC_NO_RESULT:
-               RETURN_MODULE_NOTFOUND;
+               rcode = RLM_MODULE_NOTFOUND;
+               break;
 
        default:
-               RETURN_MODULE_FAIL;
-
+               rcode = RLM_MODULE_FAIL;
+               break;
        }
 
-       talloc_free(bind_auth_ctx);
+       /*
+        *      Bind auth ctx is freed by trunk request free.
+        */
+       fr_trunk_request_signal_complete(bind_auth_ctx->treq);
+
+       RETURN_MODULE_RCODE(rcode);
 }
 
 /** Signal an outstanding LDAP bind request to cancel
  *
  */
-static void ldap_async_auth_bind_cancel(UNUSED request_t *request, UNUSED fr_signal_t action, void *uctx)
+static void ldap_async_auth_bind_cancel(request_t *request, UNUSED fr_signal_t action, void *uctx)
 {
        fr_ldap_bind_auth_ctx_t *bind_auth_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_auth_ctx_t);
 
-       ldap_abandon_ext(bind_auth_ctx->bind_ctx->c->handle, bind_auth_ctx->msgid, NULL, NULL);
-       fr_rb_remove(bind_auth_ctx->thread->binds, bind_auth_ctx);
-
-       talloc_free(bind_auth_ctx);
+       RWARN("Cancelling bind auth");
+       if (bind_auth_ctx->msgid > 0) fr_rb_remove(bind_auth_ctx->thread->binds, bind_auth_ctx);
+       fr_trunk_request_signal_cancel(bind_auth_ctx->treq);
 }
 
 /** Initiate an async LDAP bind for authentication
@@ -328,28 +312,52 @@ static void ldap_async_auth_bind_cancel(UNUSED request_t *request, UNUSED fr_sig
 int fr_ldap_bind_auth_async(request_t *request, fr_ldap_thread_t *thread, char const *bind_dn, char const *password)
 {
        fr_ldap_bind_auth_ctx_t *bind_auth_ctx;
-       fr_ldap_connection_t    *ldap_conn = talloc_get_type_abort(thread->conn->h, fr_ldap_connection_t);
+       fr_trunk_request_t      *treq;
+       fr_ldap_thread_trunk_t  *ttrunk = fr_thread_ldap_bind_trunk_get(thread);
+       fr_trunk_enqueue_t      ret;
 
-       if (ldap_conn->state != FR_LDAP_STATE_RUN) {
-       connection_fault:
-               fr_connection_signal_reconnect(ldap_conn->conn, FR_CONNECTION_FAILED);
+       if (!ttrunk) {
+               ERROR("Failed to get trunk connection for LDAP bind");
                return -1;
        }
 
-       if (ldap_conn->fd < 0) goto connection_fault;
+       treq = fr_trunk_request_alloc(ttrunk->trunk, request);
+       if (!treq) {
+               ERROR ("Failed to allocate trunk request for LDAP bind");
+               return -1;
+       }
+
+       MEM(bind_auth_ctx = talloc(treq, fr_ldap_bind_auth_ctx_t));
+       *bind_auth_ctx = (fr_ldap_bind_auth_ctx_t) {
+               .treq = treq,
+               .request = request,
+               .thread = thread,
+               .ret = LDAP_PROC_NO_RESULT
+       };
+
+       MEM(bind_auth_ctx->bind_ctx = talloc(bind_auth_ctx, fr_ldap_bind_ctx_t));
+       *bind_auth_ctx->bind_ctx = (fr_ldap_bind_ctx_t) {
+               .bind_dn = bind_dn,
+               .password = password
+       };
+
+       ret = fr_trunk_request_enqueue(&bind_auth_ctx->treq, ttrunk->trunk, request, bind_auth_ctx, NULL);
+
+       switch (ret) {
+       case FR_TRUNK_ENQUEUE_OK:
+       case FR_TRUNK_ENQUEUE_IN_BACKLOG:
+               break;
 
-       MEM(bind_auth_ctx = talloc_zero(request, fr_ldap_bind_auth_ctx_t));
-       MEM(bind_auth_ctx->bind_ctx = talloc_zero(bind_auth_ctx, fr_ldap_bind_ctx_t));
-       bind_auth_ctx->bind_ctx->c = ldap_conn;
-       bind_auth_ctx->bind_ctx->bind_dn = bind_dn;
-       bind_auth_ctx->bind_ctx->password = password;
-       bind_auth_ctx->request = request;
-       bind_auth_ctx->thread = thread;
-       bind_auth_ctx->ret = LDAP_PROC_NO_RESULT;
+       default:
+               ERROR("Failed to enqueue bind request");
+               fr_trunk_request_free(&treq);
+               return -1;
+       }
 
        return unlang_function_push(request,
                                    ldap_async_auth_bind_start,
                                    ldap_async_auth_bind_results,
                                    ldap_async_auth_bind_cancel,
-                                   ~FR_SIGNAL_CANCEL, UNLANG_TOP_FRAME, bind_auth_ctx);
+                                   ~FR_SIGNAL_CANCEL, UNLANG_SUB_FRAME,
+                                   bind_auth_ctx) == UNLANG_ACTION_PUSHED_CHILD ? 0 : -1;
 }
index 38f2c77e99a910253be7518af85fba071aa548a7..469c8bce5f930d33254fee1b21ba2fc58bae85a3 100644 (file)
@@ -1048,6 +1048,266 @@ fr_trunk_state_t fr_thread_ldap_trunk_state(fr_ldap_thread_t *thread, char const
        return (found) ? found->trunk->state : FR_TRUNK_STATE_MAX;
 }
 
+/** Free LDAP bind auth ctx when trunk request is "freed" with fr_trunk_request_free()
+ *
+ */
+static void ldap_trunk_bind_auth_free(UNUSED request_t *request, void *preq_to_free, UNUSED void *uctx)
+{
+       fr_ldap_bind_auth_ctx_t *bind = talloc_get_type_abort(preq_to_free, fr_ldap_bind_auth_ctx_t);
+
+       talloc_free(bind);
+}
+
+/** Take pending LDAP bind auths from the queue and send them.
+ *
+ * @param[in] el       Event list for timers.
+ * @param[in] tconn    Trunk handle.
+ * @param[in] conn     on which to send the queries
+ * @param[in] uctx     User context passed to fr_trunk_alloc
+ */
+static void ldap_trunk_bind_auth_mux(UNUSED fr_event_list_t *el, fr_trunk_connection_t *tconn,
+                                  fr_connection_t *conn, void *uctx)
+{
+       fr_ldap_connection_t    *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
+       fr_ldap_thread_trunk_t  *ttrunk = talloc_get_type_abort(uctx, fr_ldap_thread_trunk_t);
+       fr_ldap_thread_t        *thread = ttrunk->t;
+       fr_trunk_request_t      *treq;
+
+       fr_ldap_bind_auth_ctx_t *bind = NULL;
+       int                     ret = 0;
+       struct berval           cred;
+       request_t               *request;
+
+       if (fr_trunk_connection_pop_request(&treq, tconn) != 0) return;
+
+       /* Pacify clang scan */
+       if (!treq) return;
+
+       bind = talloc_get_type_abort(treq->preq, fr_ldap_bind_auth_ctx_t);
+       request = bind->request;
+
+       switch (bind->type) {
+       case LDAP_BIND_SIMPLE:
+       {
+               fr_ldap_bind_ctx_t      *bind_ctx = bind->bind_ctx;
+
+               RDEBUG2("Starting bind auth operation as %s", bind_ctx->bind_dn);
+
+               if (bind_ctx->password) {
+                       memcpy(&cred.bv_val, &bind_ctx->password, sizeof(cred.bv_val));
+                       cred.bv_len = talloc_array_length(bind_ctx->password) - 1;
+               } else {
+                       cred.bv_val = NULL;
+                       cred.bv_len = 0;
+               }
+
+               ret = ldap_sasl_bind(ldap_conn->handle, bind_ctx->bind_dn, LDAP_SASL_SIMPLE,
+                                    &cred, NULL, NULL, &bind->msgid);
+
+               switch (ret) {
+               case LDAP_SUCCESS:
+                       fr_rb_insert(thread->binds, bind);
+                       RDEBUG3("Bind auth sent as LDAP msgid %d", bind->msgid);
+                       break;
+
+               default:
+                       bind->ret = LDAP_PROC_ERROR;
+                       unlang_interpret_mark_runnable(treq->request);
+                       RERROR("Failed to send bind auth");
+                       break;
+               }
+       }
+               break;
+
+       case LDAP_BIND_SASL:
+       {
+               fr_ldap_sasl_ctx_t      *sasl_ctx = bind->sasl_ctx;
+
+               RDEBUG2("%s SASL bind auth operation as %s", sasl_ctx->rmech ? "Continuing" : "Starting",
+                       sasl_ctx->identity);
+
+               ret = fr_ldap_sasl_bind_auth_send(sasl_ctx, &bind->msgid, ldap_conn);
+
+               switch (ret) {
+               case LDAP_SASL_BIND_IN_PROGRESS:
+                       /*
+                        *      Add the bind to the list of pending binds.
+                        */
+                       fr_rb_insert(thread->binds, bind);
+                       RDEBUG3("SASL bind auth sent as LDAP msgid %d", bind->msgid);
+                       break;
+
+               case LDAP_SUCCESS:
+                       bind->ret = LDAP_PROC_SUCCESS;
+                       unlang_interpret_mark_runnable(treq->request);
+                       break;
+
+               default:
+                       bind->ret = LDAP_PROC_ERROR;
+                       unlang_interpret_mark_runnable(treq->request);
+                       RERROR("Failed to send SASL bind auth");
+                       break;
+               }
+       }
+               break;
+       }
+       /*
+        *      The request is marked as sent, to remove from the pending list.
+        *      This is regardless of whether the sending was successful or not as
+        *      the different states are handled by the resume function which then
+        *      marks the request as complete triggering the tidy up.
+        */
+       fr_trunk_request_signal_sent(treq);
+}
+
+/** Read LDAP bind auth responses
+ *
+ * @param[in] el       To insert timers into.
+ * @param[in] tconn    Trunk connection associated with these results.
+ * @param[in] conn     Connection handle for these results.
+ * @param[in] uctx     Thread specific trunk structure - contains tree of pending queries.
+ */
+static void ldap_trunk_bind_auth_demux(UNUSED fr_event_list_t *el, UNUSED fr_trunk_connection_t *tconn,
+                                      fr_connection_t *conn, void *uctx)
+{
+       fr_ldap_connection_t    *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
+       fr_ldap_thread_trunk_t  *ttrunk = talloc_get_type_abort(uctx, fr_ldap_thread_trunk_t);
+       fr_ldap_thread_t        *thread = ttrunk->t;
+       fr_ldap_bind_auth_ctx_t find = { .msgid = -1 }, *bind = NULL;
+
+       int                     ret = 0;
+       LDAPMessage             *result = NULL;
+       request_t               *request;
+       bool                    really_no_result = false;
+
+       do {
+               /*
+                *      The first time ldap_result is called when there's pending network
+                *      data, it may read the data, but actually return a timeout.
+                *
+                *      In order to fix the spurious debugging messages and overhead,
+                *      if this is the first iteration through the loop and fr_ldap_result
+                *      returns a timeout, we call it again.
+                */
+               ret = fr_ldap_result(&result, NULL, ldap_conn, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, fr_time_delta_wrap(10));
+               if (ret == LDAP_PROC_TIMEOUT) {
+                       if (really_no_result) return;
+                       really_no_result = true;
+                       continue;
+               }
+
+               if (!result) return;
+
+               really_no_result = true;
+               find.msgid = ldap_msgid(result);
+               bind = fr_rb_find(thread->binds, &find);
+
+               if (!bind) {
+                       WARN("Ignoring bind result msgid %i - doesn't match any outstanding binds", find.msgid);
+                       ldap_msgfree(result);
+                       result = NULL;
+                       continue;
+               }
+       } while (!bind);
+
+       /*
+        *      There will only ever be one bind in flight at a time on a given
+        *      connection - so having got a result, no need to loop.
+        */
+
+       fr_rb_remove(thread->binds, bind);
+       request = bind->request;
+       bind->ret = ret;
+
+       switch (ret) {
+       /*
+        *      Accept or reject will be SUCCESS, NOT_PERMITTED or REJECT
+        */
+       case LDAP_PROC_NOT_PERMITTED:
+       case LDAP_PROC_REJECT:
+       case LDAP_PROC_BAD_DN:
+       case LDAP_PROC_NO_RESULT:
+               break;
+
+       case LDAP_PROC_SUCCESS:
+               if (bind->type == LDAP_BIND_SIMPLE) break;
+
+               /*
+                *      With SASL binds, we will be here after ldap_sasl_interactive_bind
+                *      returned LDAP_SASL_BIND_IN_PROGRESS.  That always requires a further
+                *      call of ldap_sasl_interactive_bind to get the final result.
+                */
+               bind->ret = LDAP_PROC_CONTINUE;
+               FALL_THROUGH;
+
+       case LDAP_PROC_CONTINUE:
+       {
+               fr_ldap_sasl_ctx_t      *sasl_ctx = bind->sasl_ctx;
+               struct berval           *srv_cred;
+
+               /*
+                *      Free any previous result and track the new one.
+                */
+               if (sasl_ctx->result) ldap_msgfree(sasl_ctx->result);
+               sasl_ctx->result = result;
+               result = NULL;
+
+               ret = ldap_parse_sasl_bind_result(ldap_conn->handle, sasl_ctx->result, &srv_cred, 0);
+               if (ret != LDAP_SUCCESS) {
+                       RERROR("SASL decode failed (bind failed): %s", ldap_err2string(ret));
+                       break;
+               }
+
+               if (srv_cred) {
+                       RDEBUG3("SASL response  : %pV",
+                               fr_box_strvalue_len(srv_cred->bv_val, srv_cred->bv_len));
+                       ber_bvfree(srv_cred);
+               }
+
+               if (sasl_ctx->rmech) RDEBUG3("Continuing SASL mech %s...", sasl_ctx->rmech);
+       }
+               break;
+
+       default:
+               break;
+       }
+
+       ldap_msgfree(result);
+       unlang_interpret_mark_runnable(request);
+}
+
+/** Callback to cancel LDAP bind auth
+ *
+ * Inform the remote LDAP server that we no longer want responses to specific bind.
+ *
+ * @param[in] el       For timer management.
+ * @param[in] tconn    The trunk connection handle
+ * @param[in] conn     The specific connection binds will be cancelled on
+ * @param[in] uctx     Context provided to fr_trunk_alloc
+ */
+static void ldap_bind_auth_cancel_mux(UNUSED fr_event_list_t *el, fr_trunk_connection_t *tconn,
+                                   fr_connection_t *conn, UNUSED void *uctx)
+{
+       fr_trunk_request_t      *treq;
+       fr_ldap_connection_t    *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
+       fr_ldap_bind_auth_ctx_t *bind;
+
+       while ((fr_trunk_connection_pop_cancellation(&treq, tconn)) == 0) {
+               bind = talloc_get_type_abort(treq->preq, fr_ldap_bind_auth_ctx_t);
+               if (bind->type == LDAP_BIND_SASL) {
+                       /*
+                        *      With SASL binds, abandoning the bind part way through
+                        *      seems to leave the connection in an unpredictable state
+                        *      so safer to restart.
+                        */
+                       fr_trunk_connection_signal_reconnect(tconn, FR_CONNECTION_FAILED);
+               } else {
+                       ldap_abandon_ext(ldap_conn->handle, bind->msgid, NULL, NULL);
+               }
+               fr_trunk_request_signal_cancel_complete(treq);
+       }
+}
+
 /** Find the thread specific trunk to use for LDAP bind auths
  *
  * If there is no current trunk then a new one is created.
@@ -1073,6 +1333,10 @@ fr_ldap_thread_trunk_t *fr_thread_ldap_bind_trunk_get(fr_ldap_thread_t *thread)
                                       &(fr_trunk_io_funcs_t){
                                              .connection_alloc = ldap_trunk_connection_alloc,
                                              .connection_notify = ldap_trunk_connection_notify,
+                                             .request_mux = ldap_trunk_bind_auth_mux,
+                                             .request_demux = ldap_trunk_bind_auth_demux,
+                                             .request_cancel_mux = ldap_bind_auth_cancel_mux,
+                                             .request_free = ldap_trunk_bind_auth_free
                                        },
                                       thread->bind_trunk_conf,
                                       "rlm_ldap bind auth", ttrunk, false);
index 9445ee15bd9bea0182428794fed503e76817492f..073f7afbce3cb251a9aebfc9386c51ad502de49e 100644 (file)
@@ -356,7 +356,6 @@ int fr_ldap_sasl_bind_async(fr_ldap_connection_t *c,
        return 0;
 }
 
-
 /** Send a SASL LDAP auth bind
  *
  * Shares the same callback as SASL admin binds
@@ -374,40 +373,13 @@ int fr_ldap_sasl_bind_auth_send(fr_ldap_sasl_ctx_t *sasl_ctx, int *msgid,
                                          &sasl_ctx->rmech, msgid);
 }
 
-/** Submit an async SASL LDAP auth bind
+/** Yield interpreter after enqueueing sasl auth bind
  *
- * @param[in] p_result Unused.
- * @param[in] priority Unused.
- * @param[in] request  being processed.
- * @param[in] uctx     bind auth ctx.
- * @return     unlang action.
  */
-static unlang_action_t ldap_async_sasl_auth_bind_start(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
-                                                      request_t *request, void *uctx)
+static unlang_action_t ldap_async_sasl_bind_auth_start(UNUSED rlm_rcode_t *p_result, UNUSED int *priority,
+                                                      UNUSED request_t *request, UNUSED void *uctx)
 {
-       fr_ldap_bind_auth_ctx_t *bind_auth_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_auth_ctx_t);
-       fr_ldap_sasl_ctx_t      *sasl_ctx = bind_auth_ctx->sasl_ctx;
-       int                     ret;
-
-       RDEBUG2("%s SASL bind auth operation as %s", sasl_ctx->rmech ? "Continuing" : "Starting", sasl_ctx->dn);
-
-       ret = ldap_sasl_interactive_bind(sasl_ctx->c->handle, NULL, sasl_ctx->mechs,
-                                        NULL, NULL, LDAP_SASL_AUTOMATIC,
-                                        _sasl_interact, sasl_ctx, sasl_ctx->result,
-                                        &sasl_ctx->rmech, &bind_auth_ctx->msgid);
-
-       switch (ret) {
-       case LDAP_SUCCESS:
-               bind_auth_ctx->ret = LDAP_PROC_SUCCESS;
-               return UNLANG_ACTION_CALCULATE_RESULT;
-
-       case LDAP_SASL_BIND_IN_PROGRESS:
-               fr_rb_insert(bind_auth_ctx->thread->binds, bind_auth_ctx);
-               return UNLANG_ACTION_YIELD;
-
-       default:
-               return UNLANG_ACTION_FAIL;
-       }
+       return UNLANG_ACTION_YIELD;
 }
 
 /** Signal an outstanding SASL LDAP bind to cancel
@@ -416,14 +388,13 @@ static unlang_action_t ldap_async_sasl_auth_bind_start(UNUSED rlm_rcode_t *p_res
  * @param[in] action   Signal to handle.
  * @param[in] uctx     bind auth ctx.
  */
-static void ldap_async_sasl_auth_bind_cancel(UNUSED request_t *request, UNUSED fr_signal_t action, void *uctx)
+static void ldap_async_sasl_bind_auth_cancel(request_t *request, UNUSED fr_signal_t action, void *uctx)
 {
        fr_ldap_bind_auth_ctx_t *bind_auth_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_auth_ctx_t);
 
-       ldap_abandon_ext(bind_auth_ctx->sasl_ctx->c->handle, bind_auth_ctx->msgid, NULL, NULL);
-       fr_rb_remove(bind_auth_ctx->thread->binds, bind_auth_ctx);
-
-       talloc_free(bind_auth_ctx);
+       RWARN("Cancelling SASL bind auth");
+       if (bind_auth_ctx->msgid > 0) fr_rb_remove(bind_auth_ctx->thread->binds, bind_auth_ctx);
+       fr_trunk_request_signal_cancel(bind_auth_ctx->treq);
 }
 
 /** Handle the return code from parsed LDAP results to set the module rcode
@@ -434,11 +405,12 @@ static void ldap_async_sasl_auth_bind_cancel(UNUSED request_t *request, UNUSED f
  * @param[in] uctx     bind auth ctx.
  * @return     unlang action.
  */
-static unlang_action_t ldap_async_sasl_auth_bind_results(rlm_rcode_t *p_result, UNUSED int *priority, request_t *request, void *uctx)
+static unlang_action_t ldap_async_sasl_bind_auth_results(rlm_rcode_t *p_result, UNUSED int *priority, request_t *request, void *uctx)
 {
        fr_ldap_bind_auth_ctx_t *bind_auth_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_auth_ctx_t);
        fr_ldap_sasl_ctx_t      *sasl_ctx = bind_auth_ctx->sasl_ctx;
        fr_ldap_rcode_t         ret = bind_auth_ctx->ret;
+       fr_ldap_connection_t    *ldap_conn = NULL;
 
        switch (bind_auth_ctx->ret) {
        case LDAP_PROC_SUCCESS:
@@ -454,14 +426,36 @@ static unlang_action_t ldap_async_sasl_auth_bind_results(rlm_rcode_t *p_result,
                break;
 
        case LDAP_PROC_CONTINUE:
-               return unlang_function_push(request, ldap_async_sasl_auth_bind_start, ldap_async_sasl_auth_bind_results,
-                                   ldap_async_sasl_auth_bind_cancel, ~FR_SIGNAL_CANCEL, UNLANG_SUB_FRAME, bind_auth_ctx);
+               if (fr_trunk_request_requeue(bind_auth_ctx->treq) != FR_TRUNK_ENQUEUE_OK) {
+                       ret = LDAP_PROC_ERROR;
+                       break;
+               }
+
+               /*
+                *      Once the next SASL exchange has completed repeat this function to
+                *      process the results
+                */
+               if (unlang_function_repeat_set(request, ldap_async_sasl_bind_auth_results) < 0) {
+                       /*
+                        *      Not strictly an LDAP error but if this happens we will want to reset
+                        *      the connection to get a known state.
+                        */
+                       ret = LDAP_PROC_ERROR;
+                       break;
+               }
+               return UNLANG_ACTION_YIELD;
 
        default:
                break;
        }
 
-       talloc_free(bind_auth_ctx);
+       if (bind_auth_ctx->treq->tconn) ldap_conn = talloc_get_type_abort(bind_auth_ctx->treq->tconn->conn->h,
+                                                                         fr_ldap_connection_t);
+
+       /*
+        *      Will free bind_auth_ctx
+        */
+       fr_trunk_request_signal_complete(bind_auth_ctx->treq);
 
        switch (ret) {
        case LDAP_PROC_SUCCESS:
@@ -480,6 +474,10 @@ static unlang_action_t ldap_async_sasl_auth_bind_results(rlm_rcode_t *p_result,
                RETURN_MODULE_NOTFOUND;
 
        default:
+               if (ldap_conn) {
+                       RPERROR("LDAP connection returned an error - restarting the connection");
+                       fr_ldap_state_error(ldap_conn);
+               }
                RETURN_MODULE_FAIL;
        }
 }
@@ -502,18 +500,33 @@ int fr_ldap_sasl_bind_auth_async(request_t *request, fr_ldap_thread_t *thread, c
                                 char const *identity, char const *password, char const *proxy, char const *realm)
 {
        fr_ldap_bind_auth_ctx_t *bind_auth_ctx;
-       fr_ldap_connection_t    *ldap_conn = talloc_get_type_abort(thread->conn->h, fr_ldap_connection_t);
+       fr_trunk_request_t      *treq;
+       fr_ldap_thread_trunk_t  *ttrunk = fr_thread_ldap_bind_trunk_get(thread);
+       fr_trunk_enqueue_t      ret;
 
-       if ((ldap_conn->state != FR_LDAP_STATE_RUN) || (ldap_conn->fd < 0)) {
-               fr_connection_signal_reconnect(ldap_conn->conn, FR_CONNECTION_FAILED);
+       if (!ttrunk) {
+               ERROR("Failed to get trunk connection for LDAP bind");
                return -1;
        }
 
-       MEM(bind_auth_ctx = talloc_zero(request, fr_ldap_bind_auth_ctx_t));
+       treq = fr_trunk_request_alloc(ttrunk->trunk, request);
+       if (!treq) {
+               ERROR("Failed to allocate trunk request for LDAP bind");
+               return -1;
+       }
+
+       MEM(bind_auth_ctx = talloc_zero(treq, fr_ldap_bind_auth_ctx_t));
+       *bind_auth_ctx = (fr_ldap_bind_auth_ctx_t) {
+               .treq = treq,
+               .request = request,
+               .thread = thread,
+               .ret = LDAP_PROC_NO_RESULT,
+               .type = LDAP_BIND_SASL
+       };
+
        MEM(bind_auth_ctx->sasl_ctx = talloc(bind_auth_ctx, fr_ldap_sasl_ctx_t));
        talloc_set_destructor(bind_auth_ctx->sasl_ctx, _sasl_ctx_free);
        *bind_auth_ctx->sasl_ctx = (fr_ldap_sasl_ctx_t) {
-               .c = ldap_conn,
                .mechs = mechs,
                .dn = dn,
                .identity = identity,
@@ -521,11 +534,24 @@ int fr_ldap_sasl_bind_auth_async(request_t *request, fr_ldap_thread_t *thread, c
                .proxy = proxy,
                .realm = realm,
        };
-       bind_auth_ctx->request = request;
-       bind_auth_ctx->thread = thread;
-       bind_auth_ctx->ret = LDAP_PROC_NO_RESULT;
-       bind_auth_ctx->type = LDAP_BIND_SASL;
 
-       return unlang_function_push(request, ldap_async_sasl_auth_bind_start, ldap_async_sasl_auth_bind_results,
-                                   ldap_async_sasl_auth_bind_cancel, ~FR_SIGNAL_CANCEL, UNLANG_TOP_FRAME, bind_auth_ctx);
+       ret = fr_trunk_request_enqueue(&bind_auth_ctx->treq, ttrunk->trunk, request, bind_auth_ctx, NULL);
+
+       switch (ret) {
+       case FR_TRUNK_ENQUEUE_OK:
+       case FR_TRUNK_ENQUEUE_IN_BACKLOG:
+               break;
+
+       default:
+               ERROR("Failed to enqueue bind request");
+               fr_trunk_request_free(&treq);
+               return -1;
+       }
+
+       return unlang_function_push(request,
+                                   ldap_async_sasl_bind_auth_start,
+                                   ldap_async_sasl_bind_auth_results,
+                                   ldap_async_sasl_bind_auth_cancel,
+                                   ~FR_SIGNAL_CANCEL, UNLANG_SUB_FRAME,
+                                   bind_auth_ctx) == UNLANG_ACTION_PUSHED_CHILD ? 0 : -1;
 }
index 97d92cbd347b2ac2d5ab551d694104bbf2849b58..fc98ef9c5ad3cc837a921afc6fa88af4b78980e3 100644 (file)
@@ -641,166 +641,6 @@ static int ldap_map_verify(CONF_SECTION *cs, UNUSED void *mod_inst, UNUSED void
        return 0;
 }
 
-/** Error reading from or writing to the file descriptor
- *
- * @param[in] el       the event occurred in.
- * @param[in] fd       the event occurred on.
- * @param[in] flags    from kevent.
- * @param[in] fd_errno The error that ocurred.
- * @param[in] uctx     LDAP thread the connection that faulted relates to.
- */
-static void _ldap_bind_auth_io_error(UNUSED fr_event_list_t *el, UNUSED int fd,
-                                    UNUSED int flags, UNUSED int fd_errno, void *uctx)
-{
-       fr_ldap_thread_t        *thread = talloc_get_type_abort(uctx, fr_ldap_thread_t);
-       fr_ldap_connection_t    *c = talloc_get_type_abort(thread->conn->h, fr_ldap_connection_t);
-
-       fr_ldap_state_error(c);         /* Restart the connection state machine */
-}
-
-/** Callback used to process LDAP bind auth results
- *
- * @param[in] el       the read event occurred in.
- * @param[in] fd       the read event occurred on.
- * @param[in] flags    from kevent.
- * @param[in] uctx     LDAP thread associated with the event.
- */
-static void _ldap_bind_auth_io_read(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
-{
-       fr_ldap_thread_t        *thread = talloc_get_type_abort(uctx, fr_ldap_thread_t);
-       fr_ldap_connection_t    *ldap_conn = talloc_get_type_abort(thread->conn->h, fr_ldap_connection_t);
-       fr_ldap_bind_auth_ctx_t find = { .msgid = -1 }, *bind_auth_ctx;
-       LDAPMessage             *result = NULL;
-
-       int                     ret;
-
-       do {
-               /*
-                *      Fetch the next LDAP result which has been fully received
-                */
-               ret = fr_ldap_result(&result, NULL, ldap_conn, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, fr_time_delta_wrap(10));
-
-               /*
-                *      Timeout in this case really means no results to read - we've
-                *      handled everything that was available
-                */
-               if (ret == LDAP_PROC_TIMEOUT) return;
-
-               /*
-                *      If there is no result don't try to process one
-                */
-               if (!result) return;
-
-               find.msgid = ldap_msgid(result);
-               bind_auth_ctx = fr_rb_find(thread->binds, &find);
-
-               if (!bind_auth_ctx) {
-                       WARN("Ignoring bind result msgid %i - doesn't match any outstanidng binds", find.msgid);
-                       ldap_msgfree(result);
-                       continue;
-               }
-
-               /*
-                *      Remove from the list of pending bind requests
-                */
-               fr_rb_remove(bind_auth_ctx->thread->binds, bind_auth_ctx);
-
-               bind_auth_ctx->ret = ret;
-
-               switch (ret) {
-               /*
-                *      Accept or reject will be SUCCESS, NOT_PERMITTED or REJECT
-                */
-               case LDAP_PROC_NOT_PERMITTED:
-               case LDAP_PROC_REJECT:
-               case LDAP_PROC_BAD_DN:
-               case LDAP_PROC_NO_RESULT:
-                       break;
-
-               case LDAP_PROC_SUCCESS:
-                       if (bind_auth_ctx->type == LDAP_BIND_SIMPLE) break;
-
-                       /*
-                        *      With SASL binds, we will be here after ldap_sasl_interactive_bind
-                        *      returned LDAP_SASL_BIND_IN_PROGRESS.  That always requires a further
-                        *      call of ldap_sasl_interactive_bind to get the final result.
-                        */
-                       bind_auth_ctx->ret = LDAP_PROC_CONTINUE;
-                       FALL_THROUGH;
-
-               case LDAP_PROC_CONTINUE:
-               {
-                       fr_ldap_sasl_ctx_t      *sasl_ctx = bind_auth_ctx->sasl_ctx;
-                       struct berval           *srv_cred;
-
-                       /*
-                        *      Free any previous result and track the new one.
-                        */
-                       if (sasl_ctx->result) ldap_msgfree(sasl_ctx->result);
-                       sasl_ctx->result = result;
-                       result = NULL;
-
-                       ret = ldap_parse_sasl_bind_result(ldap_conn->handle, sasl_ctx->result, &srv_cred, 0);
-                       if (ret != LDAP_SUCCESS) {
-                               ERROR("SASL decode failed (bind failed): %s", ldap_err2string(ret));
-                               break;
-                       }
-
-                       if (srv_cred) {
-                               DEBUG3("SASL response  : %pV",
-                                       fr_box_strvalue_len(srv_cred->bv_val, srv_cred->bv_len));
-                               ber_bvfree(srv_cred);
-                       }
-
-                       if (sasl_ctx->rmech) DEBUG3("Continuing SASL mech %s...", sasl_ctx->rmech);
-               }
-                       break;
-
-               default:
-                       PERROR("LDAP connection returned an error - restarting the connection");
-                       fr_ldap_state_error(bind_auth_ctx->bind_ctx->c);        /* Restart the connection state machine */
-                       break;
-               }
-               unlang_interpret_mark_runnable(bind_auth_ctx->request);
-
-               /*
-                *      Clear up the libldap results if they are not being tracked.
-                */
-               if (result) ldap_msgfree(result);
-
-       } while (1);
-}
-
-/** Watch callback to add fd read callback to LDAP connection
- *
- * To add "bind" specific callbacks to LDAP conneciton being used
- * for bind auths, when the connection becomes connected.
- *
- * @param[in] conn     to watch.
- * @param[in] prev     connection state.
- * @param[in] state    the connection is now in.
- * @param[in] uctx     LDAP thread this connection relates to.
- */
-static void _ldap_async_bind_auth_watch(fr_connection_t *conn, UNUSED fr_connection_state_t prev,
-                                       UNUSED fr_connection_state_t state, void *uctx)
-{
-       fr_ldap_thread_t        *thread = talloc_get_type_abort(uctx, fr_ldap_thread_t);
-       fr_ldap_connection_t    *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
-
-       if (ldap_conn->fd < 0) {
-       connection_failed:
-               fr_connection_signal_reconnect(conn, FR_CONNECTION_FAILED);
-               return;
-       }
-       if (fr_event_fd_insert(conn, conn->el, ldap_conn->fd,
-                              _ldap_bind_auth_io_read,
-                              NULL,
-                              _ldap_bind_auth_io_error,
-                              thread) < 0) {
-               goto connection_failed;
-       };
-}
-
 /** Perform a search and map the result of the search to server attributes
  *
  * Unlike LDAP xlat, this can be used to process attributes from multiple entries.
@@ -1159,7 +999,7 @@ static unlang_action_t mod_authenticate_resume(rlm_rcode_t *p_result, UNUSED int
        }
 
        /*
-        *      Attempt a bind using the thread specific connection for bind auths
+        *      Attempt a bind using the thread specific trunk for bind auths
         */
        if (auth_ctx->mod_env->user_sasl_mech.type == FR_TYPE_STRING) {
 #ifdef WITH_SASL
@@ -1176,8 +1016,7 @@ static unlang_action_t mod_authenticate_resume(rlm_rcode_t *p_result, UNUSED int
        } else {
                if (fr_ldap_bind_auth_async(request, auth_ctx->thread, auth_ctx->dn, auth_ctx->password) < 0) goto fail;
        }
-
-       RETURN_MODULE_RCODE(unlang_interpret_synchronous(unlang_interpret_event_list(request), request));
+       return UNLANG_ACTION_PUSHED_CHILD;
 }
 
 static unlang_action_t CC_HINT(nonnull) mod_authenticate(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
@@ -2052,11 +1891,9 @@ static int mod_thread_instatiate(module_thread_inst_ctx_t const *mctx)
        }
 
        /*
-        *      Set up a per-thread LDAP connection to use for bind auths
+        *      Set up a per-thread LDAP trunk to use for bind auths
         */
-       t->conn = fr_ldap_connection_state_alloc(t, mctx->el, t->config, mctx->inst->name);
-       fr_connection_add_watch_post(t->conn, FR_CONNECTION_STATE_CONNECTED, _ldap_async_bind_auth_watch, false, t);
-       fr_connection_signal_init(t->conn);
+       t->bind_trunk = fr_thread_ldap_bind_trunk_get(t);
 
        MEM(t->binds = fr_rb_inline_talloc_alloc(t, fr_ldap_bind_auth_ctx_t, node, fr_ldap_bind_auth_cmp, NULL));