From: Nick Porter Date: Tue, 18 Apr 2023 11:16:35 +0000 (+0100) Subject: Rework LDAP bind auths to use trunk connection X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=60028e5eb4951971adeb9f10e80b483fbe5b5e65;p=thirdparty%2Ffreeradius-server.git Rework LDAP bind auths to use trunk connection --- diff --git a/src/lib/ldap/base.h b/src/lib/ldap/base.h index 7e010a4873c..92bda7ee555 100644 --- a/src/lib/ldap/base.h +++ b/src/lib/ldap/base.h @@ -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. diff --git a/src/lib/ldap/bind.c b/src/lib/ldap/bind.c index ef53582d627..90456507fa4 100644 --- a/src/lib/ldap/bind.c +++ b/src/lib/ldap/bind.c @@ -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; } diff --git a/src/lib/ldap/connection.c b/src/lib/ldap/connection.c index 38f2c77e99a..469c8bce5f9 100644 --- a/src/lib/ldap/connection.c +++ b/src/lib/ldap/connection.c @@ -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); diff --git a/src/lib/ldap/sasl.c b/src/lib/ldap/sasl.c index 9445ee15bd9..073f7afbce3 100644 --- a/src/lib/ldap/sasl.c +++ b/src/lib/ldap/sasl.c @@ -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; } diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index 97d92cbd347..fc98ef9c5ad 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -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));