From: Nick Porter Date: Mon, 11 Oct 2021 17:46:52 +0000 (+0100) Subject: v4: Final set of background changes to LDAP code (#4264) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b14da23f44d43c838a0426264d1b1c1bcac5c16a;p=thirdparty%2Ffreeradius-server.git v4: Final set of background changes to LDAP code (#4264) * 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() --- diff --git a/src/lib/ldap/base.c b/src/lib/ldap/base.c index 1a7955660f0..a280848ec17 100644 --- a/src/lib/ldap/base.c +++ b/src/lib/ldap/base.c @@ -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, "")); @@ -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;\ diff --git a/src/lib/ldap/base.h b/src/lib/ldap/base.h index 906f585b29d..6a1a24786e5 100644 --- a/src/lib/ldap/base.h +++ b/src/lib/ldap/base.h @@ -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 diff --git a/src/lib/ldap/bind.c b/src/lib/ldap/bind.c index 6cef0cf4210..58ea321f07e 100644 --- a/src/lib/ldap/bind.c +++ b/src/lib/ldap/bind.c @@ -27,19 +27,7 @@ USES_APPLE_DEPRECATED_API #include #include - -/** 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 /** 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); +} diff --git a/src/lib/ldap/connection.c b/src/lib/ldap/connection.c index d586b2fcf0e..b53382e4597 100644 --- a/src/lib/ldap/connection.c +++ b/src/lib/ldap/connection.c @@ -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; diff --git a/src/lib/ldap/directory.c b/src/lib/ldap/directory.c index 6534037e784..921bd215283 100644 --- a/src/lib/ldap/directory.c +++ b/src/lib/ldap/directory.c @@ -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 @@ -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; +} diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index 8fa2a29c711..0871940ce6a 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -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; }