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";
}
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");
*/
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:
/*
* 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;
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.
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>"));
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;
#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;\
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.
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
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
*
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);
*/
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
*/
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
#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
*
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);
+}
[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
*
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
/*
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
*/
"rlm_ldap", found, false);
if (!found->trunk) {
+ error:
ROPTIONAL(REDEBUG, ERROR, "Unable to create LDAP connection");
talloc_free(found);
return NULL;
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;
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>
};
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);
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);
* 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);
/*
* 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++) {
/*
* 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;
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;
+}
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.
{
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
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;
}