]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
v4: Final set of background changes to LDAP code (#4264)
authorNick Porter <nick@portercomputing.co.uk>
Mon, 11 Oct 2021 17:46:52 +0000 (18:46 +0100)
committerGitHub <noreply@github.com>
Mon, 11 Oct 2021 17:46:52 +0000 (12:46 -0500)
* s/LDAP_OPT_ERROR_NUMBER/LDAP_OPT_RESULT_CODE/

LDAP_OPT_RESULT_CODE is the current name for this option.

* Define custom LDAP results parser

Used where LDAP queries are not part of processing a request, e.g.
querying the rootDSE after a connection comes up to establish which type
of directory server is connected to.

* Define async method to retrieve LDAP directory type

For use with trunk connections

* Queue a query to discover LDAP directory type for trunk connections

* Launch LDAP trunk connections for module default server

* We handle referral chasing so always set libldap option to off

* No need to re-bind since the trunk is only ever bound as admin user

* Move definition of fr_ldap_bind_ctx_t to base.h

* Define fr_ldap_bind_auth_ctx_t for holding details of bind auth requests

* Define fr_ldap_bind_auth_cmp for comparing two bind requests

* Define callbacks for handling LDAP bind responses

* Define watcher to add handlers to LDAP connection being used for bind auths

* Initialise a thread specific LDAP connection for bind auths

* Old referral rebind callback no longer needed

* Define callbacks to support use of async LDAP binds in place of sync ones

A temporary set of wrapper functions before fully rewriting rlm_ldap to
be fully async

* Define fr_ldap_bind_auth_async()

src/lib/ldap/base.c
src/lib/ldap/base.h
src/lib/ldap/bind.c
src/lib/ldap/connection.c
src/lib/ldap/directory.c
src/modules/rlm_ldap/rlm_ldap.c

index 1a7955660f0b32b3133ca39f482f5f989c09b046..a280848ec17663d81595f048d79e15721acb13c5 100644 (file)
@@ -173,7 +173,7 @@ void fr_ldap_timeout_debug(request_t *request, fr_ldap_connection_t const *conn,
 char const *fr_ldap_error_str(fr_ldap_connection_t const *conn)
 {
        int lib_errno;
-       ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno);
+       ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &lib_errno);
        if (lib_errno == LDAP_SUCCESS) {
                return "unknown";
        }
@@ -208,7 +208,7 @@ fr_ldap_rcode_t fr_ldap_error_check(LDAPControl ***ctrls, fr_ldap_connection_t c
        if (ctrls) *ctrls = NULL;
 
        if (!msg) {
-               ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno);
+               ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &lib_errno);
                if (lib_errno != LDAP_SUCCESS) goto process_error;
 
                fr_strerror_const("No result available");
@@ -255,7 +255,7 @@ fr_ldap_rcode_t fr_ldap_error_check(LDAPControl ***ctrls, fr_ldap_connection_t c
         */
        if (lib_errno != LDAP_SUCCESS) {
                fr_assert(!ctrls || !*ctrls);
-               ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno);
+               ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &lib_errno);
        }
 
 process_error:
@@ -420,7 +420,7 @@ fr_ldap_rcode_t fr_ldap_result(LDAPMessage **result, LDAPControl ***ctrls,
        /*
         *      Check if there was an error sending the request
         */
-       ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, &lib_errno);
+       ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &lib_errno);
        if (lib_errno != LDAP_SUCCESS) return fr_ldap_error_check(NULL, conn, NULL, dn);
 
        if (!fr_time_delta_ispos(timeout)) our_timeout = conn->config->res_timeout;
@@ -711,8 +711,6 @@ fr_ldap_rcode_t fr_ldap_search_async(int *msgid, request_t *request,
                                     char const *dn, int scope, char const *filter, char const * const *attrs,
                                     LDAPControl **serverctrls, LDAPControl **clientctrls)
 {
-       fr_ldap_rcode_t                 status = LDAP_PROC_ERROR;
-
        fr_ldap_config_t const  *handle_config = (*pconn)->config;
 
        struct timeval                  tv;             // Holds timeout values.
@@ -738,21 +736,6 @@ fr_ldap_rcode_t fr_ldap_search_async(int *msgid, request_t *request,
        char **search_attrs;
        memcpy(&search_attrs, &attrs, sizeof(attrs));
 
-       /*
-        *      Do all searches as the admin user.
-        */
-       if ((*pconn)->rebound) {
-               status = fr_ldap_bind(request, pconn,
-                                     (*pconn)->config->admin_identity, (*pconn)->config->admin_password,
-                                     &(*pconn)->config->admin_sasl, fr_time_delta_wrap(0),
-                                     NULL, NULL);
-               if (status != LDAP_PROC_SUCCESS) return LDAP_PROC_ERROR;
-
-               fr_assert(*pconn);
-
-               (*pconn)->rebound = false;
-       }
-
        if (filter) {
                ROPTIONAL(RDEBUG2, DEBUG2, "Performing search in \"%s\" with filter \"%s\", scope \"%s\"", dn, filter,
                          fr_table_str_by_value(fr_ldap_scope, scope, "<INVALID>"));
@@ -771,7 +754,7 @@ fr_ldap_rcode_t fr_ldap_search_async(int *msgid, request_t *request,
                            0, our_serverctrls, our_clientctrls, NULL, 0, msgid) != LDAP_SUCCESS) {
                int ldap_errno;
 
-               ldap_get_option((*pconn)->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
+               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
                ERROR("Failed performing search: %s", ldap_err2string(ldap_errno));
 
                return LDAP_PROC_ERROR;
@@ -1022,7 +1005,7 @@ int fr_ldap_global_config(int debug_level, char const *tls_random_file)
 #define do_ldap_global_option(_option, _name, _value) \
        if (ldap_set_option(NULL, _option, _value) != LDAP_OPT_SUCCESS) { \
                int _ldap_errno; \
-               ldap_get_option(NULL, LDAP_OPT_ERROR_NUMBER, &_ldap_errno); \
+               ldap_get_option(NULL, LDAP_OPT_RESULT_CODE, &_ldap_errno); \
                ERROR("Failed setting global option %s: %s", _name, \
                         (_ldap_errno != LDAP_SUCCESS) ? ldap_err2string(_ldap_errno) : "Unknown error"); \
                return -1;\
index 906f585b29d3dde002d3d1d112f712f5cc582a0a..6a1a24786e549f5af03377deefff05b0befb9dfa 100644 (file)
@@ -397,6 +397,10 @@ typedef struct fr_ldap_thread_trunk_s {
 
 typedef struct fr_ldap_referral_s fr_ldap_referral_t;
 
+typedef struct fr_ldap_query_s fr_ldap_query_t;
+
+typedef void (*fr_ldap_result_parser_t)(fr_ldap_query_t *query, LDAPMessage *head);
+
 /** LDAP query structure
  *
  * Used to hold the elements of an LDAP query and track its progress.
@@ -442,6 +446,8 @@ typedef struct fr_ldap_query_s {
        uint16_t                referral_depth; //!< How many referrals we have followed
        fr_ldap_referral_t      *referral;      //!< Referral actually being followed
 
+       fr_ldap_result_parser_t parser;         //!< Custom results parser.
+
        LDAPMessage             *result;        //!< Head of LDAP results list.
 
        fr_ldap_result_code_t   ret;            //!< Result code
@@ -463,6 +469,33 @@ typedef struct fr_ldap_referral_s {
        fr_ldap_thread_trunk_t  *ttrunk;        //!< Trunk this referral should use
 } fr_ldap_referral_t;
 
+/** Holds arguments for the async bind operation
+ *
+ */
+typedef struct {
+       fr_ldap_connection_t    *c;                     //!< to bind.
+       char const              *bind_dn;               //!< of the user, may be NULL to bind anonymously.
+       char const              *password;              //!< of the user, may be NULL if no password is specified.
+       LDAPControl             **serverctrls;          //!< Controls to pass to the server.
+       LDAPControl             **clientctrls;          //!< Controls to pass to the client (library).
+
+       int                     msgid;
+} fr_ldap_bind_ctx_t;
+
+
+/** Holds arguments for async bind auth requests
+ *
+ * Used when LDAP binds are being used to authenticate users, rather than admin binds.
+ * Allows tracking of multiple bind requests on a single connection.
+ */
+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.
+       int                     msgid;          //!< libldap msgid for this bind.
+       request_t               *request;       //!< this bind relates to.
+       fr_ldap_bind_ctx_t      *bind_ctx;      //!< Data relating to the user being bound.
+       fr_ldap_result_code_t   ret;            //!< Return code of bind operation.
+} fr_ldap_bind_auth_ctx_t;
 
 /** Codes returned by fr_ldap internal functions
  *
@@ -551,6 +584,19 @@ static inline int8_t fr_ldap_query_cmp(void const *one, void const *two)
 
 fr_ldap_query_t *fr_ldap_query_alloc(TALLOC_CTX *ctx);
 
+/** Compare two ldap bind auth structures on msgid
+ *
+ * @param[in] one      first bind request to compare.
+ * @param[in] two      second bind request to compare.
+ * @return CMP(one,two)
+ */
+static inline int8_t fr_ldap_bind_auth_cmp(void const *one, void const *two)
+{
+       fr_ldap_bind_auth_ctx_t const   *a = one, *b = two;
+
+       return CMP(a->msgid, b->msgid);
+}
+
 int fr_ldap_trunk_search(TALLOC_CTX *ctx, fr_ldap_query_t **query, request_t *request, fr_ldap_thread_trunk_t *ttrunk,
                         char const *base_dn, int scope, char const *filter, char const * const *attrs,
                         LDAPControl **serverctrls, LDAPControl **clientctrls);
@@ -639,6 +685,8 @@ int         fr_ldap_control_add_session_tracking(fr_ldap_connection_t *conn, request_t
  */
 int            fr_ldap_directory_alloc(TALLOC_CTX *ctx, fr_ldap_directory_t **out, fr_ldap_connection_t **pconn);
 
+int            fr_ldap_trunk_directory_alloc_async(TALLOC_CTX *ctx, fr_ldap_thread_trunk_t *ttrunk);
+
 /*
  *     edir.c - Edirectory integrations
  */
@@ -724,6 +772,8 @@ int         fr_ldap_bind_async(fr_ldap_connection_t *c,
                                   char const *bind_dn, char const *password,
                                   LDAPControl **serverctrls, LDAPControl **clientctrls);
 
+int            fr_ldap_bind_auth_async(request_t *request, fr_ldap_thread_t *thread,
+                                       char const *bind_dn, char const *password);
 
 /*
  *     uti.c - Utility functions
index 6cef0cf4210d9301338bded9bcbe64639fab1b56..58ea321f07ea25923b3918d51d2ea05723d6f6b8 100644 (file)
@@ -27,19 +27,7 @@ USES_APPLE_DEPRECATED_API
 
 #include <freeradius-devel/ldap/base.h>
 #include <freeradius-devel/util/debug.h>
-
-/** Holds arguments for the bind operation
- *
- */
-typedef struct {
-       fr_ldap_connection_t    *c;                     //!< to bind.
-       char const              *bind_dn;               //!< of the user, may be NULL to bind anonymously.
-       char const              *password;              //!< of the user, may be NULL if no password is specified.
-       LDAPControl             **serverctrls;          //!< Controls to pass to the server.
-       LDAPControl             **clientctrls;          //!< Controls to pass to the client (library).
-
-       int                     msgid;
-} fr_ldap_bind_ctx_t;
+#include <freeradius-devel/unlang/function.h>
 
 /** Error reading from or writing to the file descriptor
  *
@@ -245,3 +233,117 @@ int fr_ldap_bind_async(fr_ldap_connection_t *c,
 
        return 0;
 }
+
+/** Submit an async LDAP auth bind
+ *
+ */
+static unlang_action_t ldap_async_auth_bind_start(UNUSED 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_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;
+       }
+}
+
+/** Handle the return code from parsed LDAP results to set the module rcode
+ *
+ */
+static unlang_action_t ldap_async_auth_bind_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_bind_ctx_t      *bind_ctx = bind_auth_ctx->bind_ctx;
+
+       switch (bind_auth_ctx->ret) {
+       case LDAP_PROC_SUCCESS:
+               RDEBUG2("Bind as user \"%s\" was successful", bind_ctx->bind_dn);
+               RETURN_MODULE_OK;
+
+       case LDAP_PROC_NOT_PERMITTED:
+               RDEBUG2("Bind as user \"%s\" not permitted", bind_ctx->bind_dn);
+               RETURN_MODULE_DISALLOW;
+
+       case LDAP_PROC_REJECT:
+               RETURN_MODULE_REJECT;
+
+       case LDAP_PROC_BAD_DN:
+               RETURN_MODULE_INVALID;
+
+       case LDAP_PROC_NO_RESULT:
+               RETURN_MODULE_NOTFOUND;
+
+       default:
+               RETURN_MODULE_FAIL;
+
+       }
+}
+
+/** Signal an outstanding LDAP bind request to cancel
+ *
+ */
+static void ldap_async_auth_bind_cancel(UNUSED request_t *request, fr_state_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);
+
+       if (action != FR_SIGNAL_CANCEL) return;
+
+       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);
+}
+
+/** Initiate an async LDAP bind for authentication
+ *
+ * @param[in] request          this bind relates to.
+ * @param[in] thread           whose connection the bind should be performed on.
+ * @param[in] bind_dn          Identity to bind with.
+ * @param[in] password         Password to bind with.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+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);
+
+       if (ldap_conn->state != FR_LDAP_STATE_RUN) {
+       connection_fault:
+               fr_connection_signal_reconnect(ldap_conn->conn, FR_CONNECTION_FAILED);
+               return -1;
+       }
+
+       if (ldap_conn->fd < 0) goto connection_fault;
+
+       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_RESULT_PENDING;
+
+       return unlang_function_push(request, ldap_async_auth_bind_start, ldap_async_auth_bind_results, ldap_async_auth_bind_cancel, UNLANG_TOP_FRAME, bind_auth_ctx);
+}
index d586b2fcf0e116eccb5c632b58ea1c6f7993a678..b53382e4597514fb69dccc5dfdbb1e93653432ec 100644 (file)
@@ -45,119 +45,6 @@ static char const *ldap_msg_types[UINT8_MAX] = {
        [LDAP_RES_INTERMEDIATE]         = "intermediate response"
 };
 
-#if LDAP_SET_REBIND_PROC_ARGS == 3
-/** Callback for OpenLDAP to rebind and chase referrals
- *
- * Called by OpenLDAP when it receives a referral and has to rebind.
- *
- * @param handle to rebind.
- * @param url to bind to.
- * @param request that triggered the rebind.
- * @param msgid that triggered the rebind.
- * @param ctx fr_ldap configuration.
- */
-static int fr_ldap_rebind(LDAP *handle, LDAP_CONST char *url,
-                         UNUSED ber_tag_t request, UNUSED ber_int_t msgid, void *ctx)
-{
-       fr_ldap_rcode_t                 status;
-       fr_ldap_connection_t                    *conn = talloc_get_type_abort(ctx, fr_ldap_connection_t);
-       fr_ldap_config_t const  *handle_config = conn->config;
-
-       char const                      *admin_identity = NULL;
-       char const                      *admin_password = NULL;
-
-       int                             ldap_errno;
-
-       conn->referred = true;
-       conn->rebound = true;   /* not really, but oh well... */
-       fr_assert(handle == conn->handle);
-
-       DEBUG("Rebinding to URL %s", url);
-
-#  ifdef HAVE_LDAP_URL_PARSE
-       /*
-        *      Use bindname and x-bindpw extensions to get the bind credentials
-        *      SASL mech is inherited from the module that defined the connection
-        *      pool.
-        */
-       if (handle_config->use_referral_credentials) {
-               LDAPURLDesc     *ldap_url;
-               int             ret;
-               char            **ext;
-
-               ret = ldap_url_parse(url, &ldap_url);
-               if (ret != LDAP_SUCCESS) {
-                       ERROR("Failed parsing LDAP URL \"%s\": %s", url, ldap_err2string(ret));
-                       return -1;
-               }
-
-               /*
-                *      If there are no extensions, OpenLDAP doesn't
-                *      bother allocating an array.
-                */
-               for (ext = ldap_url->lud_exts; ext && *ext; ext++) {
-                       char const *p;
-                       bool critical = false;
-
-                       p = *ext;
-
-                       if (*p == '!') {
-                               critical = true;
-                               p++;
-                       }
-
-                       /*
-                        *      LDAP Parse URL unescapes the extensions for us
-                        */
-                       switch (fr_table_value_by_substr(fr_ldap_supported_extensions, p, -1, LDAP_EXT_UNSUPPORTED)) {
-                       case LDAP_EXT_BINDNAME:
-                               p = strchr(p, '=');
-                               if (!p) {
-                               bad_ext:
-                                       ERROR("Failed parsing extension \"%s\": "
-                                             "No attribute/value delimiter '='", *ext);
-                                       ldap_free_urldesc(ldap_url);
-                                       return LDAP_OTHER;
-                               }
-                               admin_identity = p + 1;
-                               break;
-
-                       case LDAP_EXT_BINDPW:
-                               p = strchr(p, '=');
-                               if (!p) goto bad_ext;
-                               admin_password = p + 1;
-                               break;
-
-                       default:
-                               if (critical) {
-                                       ERROR("Failed parsing critical extension \"%s\": "
-                                             "Not supported by FreeRADIUS", *ext);
-                                       ldap_free_urldesc(ldap_url);
-                                       return LDAP_OTHER;
-                               }
-                               DEBUG2("Skipping unsupported extension \"%s\"", *ext);
-                               continue;
-                       }
-               }
-               ldap_free_urldesc(ldap_url);
-       } else
-#  endif
-       {
-               admin_identity = handle_config->admin_identity;
-               admin_password = handle_config->admin_password;
-       }
-
-       status = fr_ldap_bind(NULL, &conn, admin_identity, admin_password,
-                             &conn->config->admin_sasl, fr_time_delta_wrap(0), NULL, NULL);
-       if (status != LDAP_PROC_SUCCESS) {
-               ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
-
-               return ldap_errno;
-       }
-
-       return LDAP_SUCCESS;
-}
-#endif
 
 /** Allocate and configure a new connection
  *
@@ -231,21 +118,10 @@ DIAG_ON(unused-macros)
        if (config->dereference_str) do_ldap_option(LDAP_OPT_DEREF, "dereference", &(config->dereference));
 
        /*
-        *      Leave "chase_referrals" unset to use the OpenLDAP default.
+        *      We handle our own referral chasing as there is no way to
+        *      get the fd for a referred query.
         */
-       if (!config->chase_referrals_unset) {
-               if (config->chase_referrals) {
-                       do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", LDAP_OPT_ON);
-
-                       if (config->rebind == true) {
-#if LDAP_SET_REBIND_PROC_ARGS == 3
-                               ldap_set_rebind_proc(c->handle, fr_ldap_rebind, c);
-#endif
-                       }
-               } else {
-                       do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", LDAP_OPT_OFF);
-               }
-       }
+       do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", LDAP_OPT_OFF);
 
 #ifdef LDAP_OPT_NETWORK_TIMEOUT
        /*
@@ -1004,6 +880,11 @@ static void ldap_trunk_request_demux(UNUSED fr_trunk_connection_t *tconn, fr_con
 
                query->result = result;
 
+               /*
+                *      If we have a specific parser to handle the result, call it
+                */
+               if (query->parser) query->parser(query, result);
+
                /*
                 *      Remove the query from the outstanding list and tidy up
                 */
@@ -1068,6 +949,7 @@ fr_ldap_thread_trunk_t *fr_thread_ldap_trunk_get(fr_ldap_thread_t *thread, char
                                      "rlm_ldap", found, false);
 
        if (!found->trunk) {
+       error:
                ROPTIONAL(REDEBUG, ERROR, "Unable to create LDAP connection");
                talloc_free(found);
                return NULL;
@@ -1081,6 +963,11 @@ fr_ldap_thread_trunk_t *fr_thread_ldap_trunk_get(fr_ldap_thread_t *thread, char
        fr_event_timer_in(thread, thread->el, &found->ev, thread->config->idle_timeout,
                          _ldap_trunk_idle_timeout, found);
 
+       /*
+        *      Attempt to discover what type directory we are talking to
+        */
+       if (fr_ldap_trunk_directory_alloc_async(found, found) < 0) goto error;
+
        fr_rb_insert(thread->trunks, found);
 
        return found;
index 6534037e78462e4e665f9e6ca8aab4685651ce55..921bd2152836f4d8d675f8951d74bdd16c1ec7ab 100644 (file)
@@ -29,7 +29,7 @@ RCSID("$Id$")
 USES_APPLE_DEPRECATED_API
 
 #define LOG_PREFIX "%s - "
-#define LOG_PREFIX_ARGS (*pconn)->config->name
+#define LOG_PREFIX_ARGS name
 
 #include <freeradius-devel/ldap/base.h>
 
@@ -49,75 +49,28 @@ static fr_table_num_sorted_t const fr_ldap_directory_type_table[] = {
 };
 static size_t fr_ldap_directory_type_table_len = NUM_ELEMENTS(fr_ldap_directory_type_table);
 
-/** Extract useful information from the rootDSE of the LDAP server
- *
- * @param[in] ctx      to allocate fr_ldap_directory_t in.
- * @param[out] out     where to write pointer to new fr_ldap_directory_t struct.
- * @param[in,out] pconn        connection for querying the directory.
- * @return
- *     - 0 on success.
- *     - 1 if we failed identifying the directory server.
- *     - -1 on error.
- */
-int fr_ldap_directory_alloc(TALLOC_CTX *ctx, fr_ldap_directory_t **out, fr_ldap_connection_t **pconn)
+static int ldap_directory_result_parse(fr_ldap_directory_t *directory, LDAP *handle,
+                                      LDAPMessage *result, char const *name)
 {
-       static char const       *attrs[] = { "vendorname",
-                                            "vendorversion",
-                                            "isGlobalCatalogReady",
-                                            "objectClass",
-                                            "orcldirectoryversion",
-                                            NULL };
-       fr_ldap_rcode_t         status;
-       int                     entry_cnt;
-       int                     ldap_errno;
-       int                     i, num;
-       int                     rcode = 0;
-       struct                  berval **values = NULL;
-       fr_ldap_directory_t     *directory;
-
-       LDAPMessage *result = NULL, *entry;
-
-       *out = NULL;
+       int                     entry_cnt, i, num, ldap_errno;
+       LDAPMessage             *entry;
+       struct berval           **values = NULL;
 
-       directory = talloc_zero(ctx, fr_ldap_directory_t);
-       if (!directory) return -2;
-       *out = directory;
-
-       directory->type = FR_LDAP_DIRECTORY_UNKNOWN;
-
-       status = fr_ldap_search(&result, NULL, pconn, "", LDAP_SCOPE_BASE, "(objectclass=*)",
-                               attrs, NULL, NULL);
-       switch (status) {
-       case LDAP_PROC_SUCCESS:
-               break;
-
-       case LDAP_PROC_NO_RESULT:
-               WARN("Capability check failed: Can't access rootDSE");
-               rcode = 1;
-               goto finish;
-
-       default:
-               rcode = 1;
-               goto finish;
-       }
-
-       entry_cnt = ldap_count_entries((*pconn)->handle, result);
+       entry_cnt = ldap_count_entries(handle, result);
        if (entry_cnt != 1) {
                WARN("Capability check failed: Ambiguous result for rootDSE, expected 1 entry, got %i", entry_cnt);
-               rcode = 1;
-               goto finish;
+               return 1;
        }
 
-       entry = ldap_first_entry((*pconn)->handle, result);
+       entry = ldap_first_entry(handle, result);
        if (!entry) {
-               ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
+               ldap_get_option(handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
 
                WARN("Capability check failed: Failed retrieving entry: %s", ldap_err2string(ldap_errno));
-               rcode = 1;
-               goto finish;
+               return 1;
        }
 
-       values = ldap_get_values_len((*pconn)->handle, entry, "vendorname");
+       values = ldap_get_values_len(handle, entry, "vendorname");
        if (values) {
                directory->vendor_str = fr_ldap_berval_to_string(directory, values[0]);
                INFO("Directory vendor: %s", directory->vendor_str);
@@ -125,7 +78,7 @@ int fr_ldap_directory_alloc(TALLOC_CTX *ctx, fr_ldap_directory_t **out, fr_ldap_
                ldap_value_free_len(values);
        }
 
-       values = ldap_get_values_len((*pconn)->handle, entry, "vendorversion");
+       values = ldap_get_values_len(handle, entry, "vendorversion");
        if (values) {
                directory->version_str = fr_ldap_berval_to_string(directory, values[0]);
                INFO("Directory version: %s", directory->version_str);
@@ -180,7 +133,7 @@ int fr_ldap_directory_alloc(TALLOC_CTX *ctx, fr_ldap_directory_t **out, fr_ldap_
         *      isGlobalCatalogReady is only present on ActiveDirectory
         *      instances. AD doesn't provide vendorname or vendorversion
         */
-       values = ldap_get_values_len((*pconn)->handle, entry, "isGlobalCatalogReady");
+       values = ldap_get_values_len(handle, entry, "isGlobalCatalogReady");
        if (values) {
                directory->type = FR_LDAP_DIRECTORY_ACTIVE_DIRECTORY;
                ldap_value_free_len(values);
@@ -190,7 +143,7 @@ int fr_ldap_directory_alloc(TALLOC_CTX *ctx, fr_ldap_directory_t **out, fr_ldap_
        /*
         *      OpenLDAP has a special objectClass for its RootDSE
         */
-       values = ldap_get_values_len((*pconn)->handle, entry, "objectClass");
+       values = ldap_get_values_len(handle, entry, "objectClass");
        if (values) {
                num = ldap_count_values_len(values);
                for (i = 0; i < num; i++) {
@@ -205,7 +158,7 @@ int fr_ldap_directory_alloc(TALLOC_CTX *ctx, fr_ldap_directory_t **out, fr_ldap_
        /*
         *      Oracle Virtual Directory and Oracle Internet Directory
         */
-       values = ldap_get_values_len((*pconn)->handle, entry, "orcldirectoryversion");
+       values = ldap_get_values_len(handle, entry, "orcldirectoryversion");
        if (values) {
                if (memmem(values[0]->bv_val, values[0]->bv_len, "OID", 3)) {
                        directory->type = FR_LDAP_DIRECTORY_ORACLE_INTERNET_DIRECTORY;
@@ -229,8 +182,106 @@ found:
                break;
        }
 
+       return 0;
+}
+
+/** Extract useful information from the rootDSE of the LDAP server
+ *
+ * @param[in] ctx      to allocate fr_ldap_directory_t in.
+ * @param[out] out     where to write pointer to new fr_ldap_directory_t struct.
+ * @param[in,out] pconn        connection for querying the directory.
+ * @return
+ *     - 0 on success.
+ *     - 1 if we failed identifying the directory server.
+ *     - -1 on error.
+ */
+int fr_ldap_directory_alloc(TALLOC_CTX *ctx, fr_ldap_directory_t **out, fr_ldap_connection_t **pconn)
+{
+       static char const       *attrs[] = { "vendorname",
+                                            "vendorversion",
+                                            "isGlobalCatalogReady",
+                                            "objectClass",
+                                            "orcldirectoryversion",
+                                            NULL };
+       fr_ldap_rcode_t         status;
+       int                     rcode = 0;
+       fr_ldap_directory_t     *directory;
+       char const              *name = (*pconn)->config->name;
+
+       LDAPMessage *result = NULL;
+
+       *out = NULL;
+
+       directory = talloc_zero(ctx, fr_ldap_directory_t);
+       if (!directory) return -2;
+       *out = directory;
+
+       directory->type = FR_LDAP_DIRECTORY_UNKNOWN;
+
+       status = fr_ldap_search(&result, NULL, pconn, "", LDAP_SCOPE_BASE, "(objectclass=*)",
+                               attrs, NULL, NULL);
+       switch (status) {
+       case LDAP_PROC_SUCCESS:
+               break;
+
+       case LDAP_PROC_NO_RESULT:
+               WARN("Capability check failed: Can't access rootDSE");
+               rcode = 1;
+               goto finish;
+
+       default:
+               rcode = 1;
+               goto finish;
+       }
+
+       rcode = ldap_directory_result_parse(directory, (*pconn)->handle, result, name);
+
 finish:
        if (result) ldap_msgfree(result);
 
        return rcode;
 }
+
+/** Parse results of search on rootDSE to gather data on LDAP server
+ *
+ * @param[in] query    which requested the rootDSE.
+ * @param[in] result   head of LDAP results message chain.
+ */
+static void ldap_trunk_directory_alloc_read(fr_ldap_query_t *query, LDAPMessage *result)
+{
+       fr_ldap_thread_trunk_t  *ttrunk = query->ttrunk;
+       fr_ldap_config_t const  *config = query->ldap_conn->config;
+
+       (void)ldap_directory_result_parse(ttrunk->directory, query->ldap_conn->handle, result, config->name);
+}
+
+/** Async extract useful information from the rootDSE of the LDAP server
+ *
+ * This is called once for each new thread trunk when it first connects.
+ *
+ * @param[in] ctx      to allocate fr_ldap_directory_t in.
+ * @param[in] ttrunk   Thread trunk connection to be queried
+ * @return
+ *     - 0 on success
+ *     < 0 on failure
+ */
+int fr_ldap_trunk_directory_alloc_async(TALLOC_CTX *ctx, fr_ldap_thread_trunk_t *ttrunk)
+{
+       fr_ldap_query_t         *query;
+
+       ttrunk->directory = talloc_zero(ctx, fr_ldap_directory_t);
+       if (!ttrunk->directory) return -1;
+
+       ttrunk->directory->type = FR_LDAP_DIRECTORY_UNKNOWN;
+
+       query = fr_ldap_query_alloc(ctx);
+       query->type = LDAP_REQUEST_SEARCH;
+       query->ttrunk = ttrunk;
+       query->parser = ldap_trunk_directory_alloc_read;
+       ldap_url_parse("ldap:///?vendorname,vendorversion,isGlobalCatalogReady,objectClass,orcldirectoryversion"
+                      "?base?(objectClass=*)", &query->ldap_url);
+
+       fr_trunk_request_enqueue(&query->treq, ttrunk->trunk, NULL, query, NULL);
+
+       return 0;
+}
index 8fa2a29c711ebb48013e717dd93c209b0f762bae..0871940ce6a551d49374e3d01f4cabfddf7ad9d1 100644 (file)
@@ -583,6 +583,110 @@ 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;
+
+next_result:
+       /*
+        *      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;
+
+       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);
+               goto next_result;
+       }
+
+       /*
+        *      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) {
+       case LDAP_PROC_SUCCESS:
+       case LDAP_PROC_NOT_PERMITTED:
+               break;
+
+       default:
+               fr_ldap_state_error(bind_auth_ctx->bind_ctx->c);        /* Restart the connection state machine */
+               break;
+       }
+       unlang_interpret_mark_runnable(bind_auth_ctx->request);
+
+       goto next_result;
+}
+
+/** 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.
@@ -1695,6 +1799,7 @@ static int mod_thread_instatiate(UNUSED CONF_SECTION const *conf, void *instance
 {
        rlm_ldap_t              *inst = instance;
        fr_ldap_thread_t        *this_thread = thread;
+       fr_ldap_thread_trunk_t  *ttrunk;
 
        /*
         *      Initialise tree for connection trunks used by this thread
@@ -1706,6 +1811,25 @@ static int mod_thread_instatiate(UNUSED CONF_SECTION const *conf, void *instance
        this_thread->trunk_conf = &inst->trunk_conf;
        this_thread->el = el;
 
+       /*
+        *      Launch trunk for module default connection
+        */
+       ttrunk = fr_thread_ldap_trunk_get(this_thread, inst->handle_config.server, inst->handle_config.admin_identity,
+                                         inst->handle_config.admin_password, NULL, &inst->handle_config);
+       if (!ttrunk) {
+               ERROR("Unable to launch LDAP trunk");
+               return -1;
+       }
+
+       /*
+        *      Set up a per-thread LDAP connection to use for bind auths
+        */
+       this_thread->conn = fr_ldap_connection_state_alloc(this_thread, el, this_thread->config, inst->name);
+       fr_connection_add_watch_post(this_thread->conn, FR_CONNECTION_STATE_CONNECTED, _ldap_async_bind_auth_watch, false, this_thread);
+       fr_connection_signal_init(this_thread->conn);
+
+       MEM(this_thread->binds = fr_rb_inline_talloc_alloc(this_thread, fr_ldap_bind_auth_ctx_t, node, fr_ldap_bind_auth_cmp, NULL));
+
        return 0;
 }