TARGET := $(TARGETNAME).a
endif
-SOURCES := base.c bind.c connection.c control.c directory.c edir.c map.c start_tls.c state.c util.c @SASL@
+SOURCES := base.c bind.c connection.c control.c directory.c edir.c map.c referral.c start_tls.c state.c util.c @SASL@
SRC_CFLAGS := @mod_cflags@
TGT_LDLIBS := @mod_ldflags@
fr_strerror_const("Success");
break;
+ case LDAP_REFERRAL:
+ fr_strerror_const("Referral");
+ status = LDAP_PROC_REFERRAL;
+ break;
+
case LDAP_SASL_BIND_IN_PROGRESS:
fr_strerror_const("Continuing");
status = LDAP_PROC_CONTINUE;
return status;
}
+/** Modify something in the LDAP directory
+ *
+ * @param[in] request Current request.
+ * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
+ * @param[in] dn of the object to modify.
+ * @param[in] mods to make, see 'man ldap_modify' for more information.
+ * @param[in] serverctrls Search controls to pass to the server. May be NULL.
+ * @param[in] clientctrls Search controls for ldap_modify. May be NULL.
+ * @return One of the LDAP_PROC_* (#fr_ldap_rcode_t) values.
+ */
+fr_ldap_rcode_t fr_ldap_modify_async(int *msgid, request_t *request, fr_ldap_connection_t **pconn,
+ char const *dn, LDAPMod *mods[],
+ LDAPControl **serverctrls, LDAPControl **clientctrls)
+{
+ LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS];
+ LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS];
+
+ fr_ldap_control_merge(our_serverctrls, our_clientctrls,
+ NUM_ELEMENTS(our_serverctrls),
+ NUM_ELEMENTS(our_clientctrls),
+ *pconn, serverctrls, clientctrls);
+
+ fr_assert(*pconn && (*pconn)->handle);
+
+ if (RDEBUG_ENABLED4) fr_ldap_timeout_debug(request, *pconn, fr_time_delta_wrap(0), __FUNCTION__);
+
+ RDEBUG2("Modifying object with DN \"%s\"", dn);
+ if(ldap_modify_ext((*pconn)->handle, dn, mods, our_serverctrls, our_clientctrls, msgid) != LDAP_SUCCESS) {
+ int ldap_errno;
+
+ ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
+ ROPTIONAL(RPEDEBUG, RPERROR, "Failed modifying object %s", ldap_err2string(ldap_errno));
+
+ return LDAP_PROC_ERROR;
+ }
+
+ return LDAP_PROC_SUCCESS;
+}
+
/** Change settings global to libldap
*
* May only be called once. Subsequent calls will be ignored.
return 0;
}
+/** Free any libldap structures when an fr_ldap_query_t is freed
+ *
+ */
+static int _ldap_query_free(fr_ldap_query_t *query)
+{
+ int i;
+
+ /*
+ * Free any results which were retrieved
+ */
+ if (query->result) ldap_msgfree(query->result);
+
+ /*
+ * Free any server and client controls that need freeing
+ */
+ for (i = 0; i < LDAP_MAX_CONTROLS; i++) {
+ if (!query->serverctrls[i].control) break;
+ if (query->serverctrls[i].freeit) ldap_control_free(query->serverctrls[i].control);
+ }
+
+ for (i = 0; i < LDAP_MAX_CONTROLS; i++) {
+ if (!query->clientctrls[i].control) break;
+ if (query->clientctrls[i].freeit) ldap_control_free(query->clientctrls[i].control);
+ }
+
+ /*
+ * If a URL was parsed, free it.
+ */
+ if (query->ldap_url) ldap_free_urldesc(query->ldap_url);
+
+ /*
+ * If any referrals were followed, the parsed referral URLS should be freed
+ */
+ if (query->referral_urls) ldap_memvfree((void **)query->referral_urls);
+
+ fr_dlist_talloc_free(&query->referrals);
+
+ return 0;
+}
+
+/** Allocate an fr_ldap_query_t, setting the talloc destructor
+ *
+ */
+fr_ldap_query_t *fr_ldap_query_alloc(TALLOC_CTX *ctx)
+{
+ fr_ldap_query_t *query;
+
+ MEM(query = talloc_zero(ctx, fr_ldap_query_t));
+ talloc_set_destructor(query, _ldap_query_free);
+
+ query->ret = LDAP_RESULT_PENDING;
+
+ return query;
+}
+
/** Initialise libldap and check library versions
*
* @return
#include <freeradius-devel/server/base.h>
#include <freeradius-devel/server/connection.h>
#include <freeradius-devel/server/map.h>
+#include <freeradius-devel/server/trunk.h>
#define LDAP_DEPRECATED 0 /* Quiet warnings about LDAP_DEPRECATED not being defined */
FR_LDAP_STATE_ERROR //!< Connection is in an error state.
} fr_ldap_state_t;
+/** Types of LDAP requests
+ *
+ */
+typedef enum {
+ LDAP_REQUEST_SEARCH = 1, //!< A lookup in an LDAP directory
+ LDAP_REQUEST_MODIFY //!< A modification to an LDAP entity
+} fr_ldap_request_type_t;
+
+/** LDAP query result codes
+ *
+ */
+typedef enum {
+ LDAP_RESULT_PENDING = 1, //!< Result not yet returned
+ LDAP_RESULT_SUCCESS = 0, //!< Successfully got LDAP results
+ LDAP_RESULT_ERROR = -1, //!< A general error occurred
+ LDAP_RESULT_TIMEOUT = -2, //!< The query timed out
+ LDAP_RESULT_BAD_DN = -3, //!< The requested DN does not exist
+ LDAP_RESULT_NO_RESULT = -4, //!< No results returned
+ LDAP_RESULT_REFERRAL_FAIL = -5, //!< Initial results indicated a referral was needed
+ ///< but the referral could not be followed
+ LDAP_RESULT_EXCESS_REFERRALS = -6, //!< The referral chain took too many hops
+ LDAP_RESULT_MISSING_REFERRAL = -7, //!< A referral was indicated but no URL was provided
+} fr_ldap_result_code_t;
+
typedef struct {
char const *vendor_str; //!< As returned from the vendorName attribute in the
///< rootDSE.
bool use_referral_credentials; //!< If true use credentials from the referral URL.
+ uint16_t referral_depth; //!< How many referrals to chase
+
bool rebind; //!< Controls whether we set an ldad_rebind_proc function
///< and so determines if we can bind to other servers whilst
///< chasing referrals. If this is false, we will still chase
fr_time_delta_t tls_handshake_timeout; //!< How long we wait for the TLS handshake to complete.
fr_time_delta_t reconnection_delay; //!< How long to wait before attempting to reconnect.
+
+ fr_time_delta_t idle_timeout; //!< How long to wait before closing unused connections.
} fr_ldap_config_t;
+typedef struct fr_ldap_thread_trunk_s fr_ldap_thread_trunk_t;
+
/** Tracks the state of a libldap connection handle
*
*/
int fd; //!< File descriptor for this connection.
+ fr_rb_tree_t *queries; //!< Outstanding queries on this connection
+
void *uctx; //!< User data associated with the handle.
} fr_ldap_connection_t;
int count; //!< Index on next free element.
} fr_ldap_map_exp_t;
+typedef struct ldap_inst_s rlm_ldap_t;
+
+/** Thread specific structure to manage LDAP trunk connections.
+ *
+ */
+typedef struct {
+ fr_rb_tree_t *trunks; //!< Tree of LDAP trunks used by this thread
+ rlm_ldap_t *inst; //!< Module instance data
+ fr_ldap_config_t *config; //!< Module instance config
+ fr_trunk_conf_t *trunk_conf; //!< Module trunk config
+ fr_event_list_t *el; //!< Thread event list for callbacks / timeouts
+ fr_connection_t *conn; //!< LDAP connection used for bind auths
+ fr_rb_tree_t *binds; //!< Tree of outstanding bind auths
+} fr_ldap_thread_t;
+
+/** Thread LDAP trunk structure
+ *
+ * One fr_ldap_thread_trunk_t will be allocated for each destination a thread needs
+ * to create an LDAP trunk connection to.
+ *
+ * Used to hold config regarding the LDAP connection and associate pending queries
+ * with the trunk they are running on.
+ */
+typedef struct fr_ldap_thread_trunk_s {
+ fr_rb_node_t node; //!< Entry in the tree of connections
+ char const *uri; //!< Server URI for this connection
+ char const *bind_dn; //!< DN connection is bound as
+ fr_ldap_config_t config; //!< Config used for this connection
+ fr_ldap_directory_t *directory; //!< The type of directory we're connected to.
+ fr_trunk_t *trunk; //!< Connection trunk
+ fr_ldap_thread_t *t; //!< Thread this connection is associated with
+ fr_event_timer_t const *ev; //!< Event to close the thread when it has been idle.
+} fr_ldap_thread_trunk_t;
+
+typedef struct fr_ldap_referral_s fr_ldap_referral_t;
+
+/** LDAP query structure
+ *
+ * Used to hold the elements of an LDAP query and track its progress.
+ * libldap structures will be freed by the talloc destructor.
+ * The same structure is used both for search queries and modifications
+ */
+typedef struct fr_ldap_query_s {
+ fr_rb_node_t node; //!< Entry in the tree of outstanding queries.
+
+ LDAPURLDesc *ldap_url; //!< parsed URL for current query if the source
+ ///< of the query was a URL.
+
+ char const *dn; //!< Base DN for searches, DN for modifications.
+
+ union {
+ struct {
+ char const **attrs; //!< Attributes being requested in a search.
+ int scope; //!< Search scope.
+ char const *filter; //!< Filter for search.
+ } search;
+ LDAPMod **mods; //!< Changes to be applied if this query is a modification.
+ };
+
+ fr_ldap_request_type_t type; //!< What type of query this is.
+
+ fr_ldap_control_t serverctrls[LDAP_MAX_CONTROLS]; //!< Server controls specific to this query.
+ fr_ldap_control_t clientctrls[LDAP_MAX_CONTROLS]; //!< Client controls specific to this query.
+
+
+ int msgid; //!< The unique identifier for this query.
+ ///< Uniqueness is only per connection.
+
+ fr_ldap_thread_trunk_t *ttrunk; //!< Trunk this query is running on
+ fr_trunk_request_t *treq; //!< Trunk request this query is associated with
+ fr_ldap_connection_t *ldap_conn; //!< LDAP connection this query is running on.
+
+ request_t *request; //!< The request this query relates to
+
+ fr_event_timer_t const *ev; //!< Event for timing out the query
+
+ char **referral_urls; //!< Referral results to follow
+ fr_dlist_head_t referrals; //!< List of parsed referrals
+ uint16_t referral_depth; //!< How many referrals we have followed
+ fr_ldap_referral_t *referral; //!< Referral actually being followed
+
+ LDAPMessage *result; //!< Head of LDAP results list.
+
+ fr_ldap_result_code_t ret; //!< Result code
+} fr_ldap_query_t;
+
+/** Parsed LDAP referral structure
+ *
+ * When LDAP servers respond with a referral, it is parsed into one or more fr_ldap_referral_t
+ * and kept until the referral has been followed.
+ * Avoids repeated parsing of the referrals as provided by libldap.
+ */
+typedef struct fr_ldap_referral_s {
+ fr_dlist_t entry; //!< Entry in list of possible referrals
+ fr_ldap_query_t *query; //!< Query this referral relates to
+ LDAPURLDesc *referral_url; //!< URL for the referral
+ char *host_uri; //!< Host URI used for referral conneciton
+ char const *identity; //!< Bind identity for referral connection
+ char const *password; //!< Bind password for referral connecition
+ fr_ldap_thread_trunk_t *ttrunk; //!< Trunk this referral should use
+} fr_ldap_referral_t;
+
+
/** Codes returned by fr_ldap internal functions
*
*/
typedef enum {
+ LDAP_PROC_REFERRAL = 2, //!< LDAP server returned referral URLs.
LDAP_PROC_CONTINUE = 1, //!< Operation is in progress.
LDAP_PROC_SUCCESS = 0, //!< Operation was successfull.
fr_value_box_memdup_shallow(value, NULL, (uint8_t *)berval->bv_val, berval->bv_len, true);
}
+/** Compare two ldap trunk structures on connection URI / DN
+ *
+ * @param[in] one first connection to compare.
+ * @param[in] two second connection to compare.
+ * @return CMP(one, two)
+ */
+static inline int8_t fr_ldap_trunk_cmp(void const *one, void const *two)
+{
+ fr_ldap_thread_trunk_t const *a = one, *b = two;
+ int8_t uricmp = CMP(strcmp(a->uri, b->uri), 0);
+
+ if (uricmp !=0) return uricmp;
+ return CMP(strcmp(a->bind_dn, b->bind_dn), 0);
+}
+
+/** Compare two ldap query structures on msgid
+ *
+ * @param[in] one first query to compare.
+ * @param[in] two second query to compare.
+ * @return CMP(one,two)
+ */
+static inline int8_t fr_ldap_query_cmp(void const *one, void const *two)
+{
+ fr_ldap_query_t const *a = one, *b = two;
+
+ return CMP(a->msgid, b->msgid);
+}
+
+fr_ldap_query_t *fr_ldap_query_alloc(TALLOC_CTX *ctx);
+
/*
* ldap.c - Wrappers arounds OpenLDAP functions.
*/
char const *dn, LDAPMod *mods[],
LDAPControl **serverctrls, LDAPControl **clientctrls);
+fr_ldap_rcode_t fr_ldap_modify_async(int *msgid, request_t *request, fr_ldap_connection_t **pconn,
+ char const *dn, LDAPMod *mods[],
+ LDAPControl **serverctrls, LDAPControl **clientctrls);
+
fr_ldap_rcode_t fr_ldap_error_check(LDAPControl ***ctrls, fr_ldap_connection_t const *conn,
LDAPMessage *msg, char const *dn);
int fr_ldap_connection_timeout_reset(fr_ldap_connection_t const *conn);
+fr_ldap_thread_trunk_t *fr_thread_ldap_trunk_get(fr_ldap_thread_t *thread, char const *uri,
+ char const *bind_dn, char const *bind_password,
+ request_t *request, fr_ldap_config_t const *config);
+
+fr_trunk_state_t fr_thread_ldap_trunk_state(fr_ldap_thread_t *thread, char const *uri, char const *bind_dn);
+
/*
* state.c - Connection state machine
*/
int fr_ldap_parse_url_extensions(LDAPControl **sss, request_t *request,
fr_ldap_connection_t *conn, char **extensions);
+
+/*
+ * referral.c - Handle LDAP referrals
+ */
+fr_ldap_referral_t *fr_ldap_referral_alloc(TALLOC_CTX *ctx);
+
+int fr_ldap_referral_follow(fr_ldap_query_t *query);
+
+int fr_ldap_referral_next(fr_ldap_query_t *query);
#include <freeradius-devel/ldap/base.h>
#include <freeradius-devel/util/debug.h>
+/*
+ * Lookup of libldap result message types to meaningful strings
+ */
+static char const *ldap_msg_types[UINT8_MAX] = {
+ [LDAP_RES_BIND] = "bind response",
+ [LDAP_RES_SEARCH_ENTRY] = "search entry",
+ [LDAP_RES_SEARCH_REFERENCE] = "search reference",
+ [LDAP_RES_SEARCH_RESULT] = "search result",
+ [LDAP_RES_MODIFY] = "modify response",
+ [LDAP_RES_ADD] = "add response",
+ [LDAP_RES_DELETE] = "delete response",
+ [LDAP_RES_MODDN] = "modify dn response",
+ [LDAP_RES_COMPARE] = "compare response",
+ [LDAP_RES_EXTENDED] = "extended response",
+ [LDAP_RES_INTERMEDIATE] = "intermediate response"
+};
+
#if LDAP_SET_REBIND_PROC_ARGS == 3
/** Callback for OpenLDAP to rebind and chase referrals
*
*/
static fr_connection_state_t _ldap_connection_init(void **h, fr_connection_t *conn, void *uctx)
{
- fr_ldap_config_t const *config = talloc_get_type_abort(uctx, fr_ldap_config_t);
+ fr_ldap_config_t const *config = uctx;
fr_ldap_connection_t *c;
fr_ldap_state_t state;
error:
return -1;
}
+
+/** Callback for closing idle LDAP trunk
+ *
+ */
+static void _ldap_trunk_idle_timeout(fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
+{
+ fr_ldap_thread_trunk_t *ttrunk = talloc_get_type_abort(uctx, fr_ldap_thread_trunk_t);
+
+ if (ttrunk->trunk->req_alloc == 0) {
+ DEBUG2("Removing idle LDAP trunk to %s", ttrunk->uri);
+ talloc_free(ttrunk->trunk);
+ talloc_free(ttrunk);
+ } else {
+ /*
+ * There are still pending queries - insert a new event
+ */
+ fr_event_timer_in(ttrunk->t, el, &ttrunk->ev, ttrunk->t->config->idle_timeout,
+ _ldap_trunk_idle_timeout, ttrunk);
+ }
+}
+
+/** Callback to cancel LDAP queries
+ *
+ * Inform the remote LDAP server that we no longer want responses to specific queries.
+ *
+ * @param[in] tconn The trunk connection handle
+ * @param[in] conn The specific connection queries will be cancelled on
+ * @param[in] uctx Context provided to fr_trunk_alloc
+ */
+static void ldap_request_cancel_mux(fr_trunk_connection_t *tconn, fr_connection_t *conn, UNUSED void *uctx)
+{
+ fr_trunk_request_t *treq;
+ fr_ldap_connection_t *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
+ fr_ldap_query_t *query;
+
+ while ((fr_trunk_connection_pop_cancellation(&treq, tconn)) == 0) {
+ query = treq->preq;
+ ldap_abandon_ext(ldap_conn->handle, query->msgid, NULL, NULL);
+ fr_rb_remove(ldap_conn->queries, query);
+
+ fr_trunk_request_signal_cancel_complete(treq);
+ }
+}
+
+
+/** I/O read function
+ *
+ * Underlying FD is now readable - call the trunk to read any pending requests.
+ *
+ * @param[in] el The event list signalling.
+ * @param[in] fd that's now readable.
+ * @param[in] flags describing the read event.
+ * @param[in] uctx The trunk connection handle.
+ */
+static void ldap_conn_readable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
+{
+ fr_trunk_connection_t *tconn = talloc_get_type_abort(uctx, fr_trunk_connection_t);
+
+ fr_trunk_connection_signal_readable(tconn);
+}
+
+
+/** I/O write function
+ *
+ * Underlying FD is now writable - call the trunk to write any pending requests.
+ *
+ * @param[in] el The event list signalling.
+ * @param[in] fd that's now writable.
+ * @param[in] flags describing the write event.
+ * @param[in] uctx The trunk connection handle
+ */
+static void ldap_conn_writable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
+{
+ fr_trunk_connection_t *tconn = talloc_get_type_abort(uctx, fr_trunk_connection_t);
+
+ fr_trunk_connection_signal_writable(tconn);
+}
+
+
+/** I/O error function
+ *
+ * The event loop signalled that a fatal error occurec on this connection.
+ *
+ * @param[in] el The event list signalling.
+ * @param[in] fd that errored.
+ * @param[in] flags EL flags.
+ * @param[in] fd_errno The nature of the error.
+ * @param[in] uctx The trunk connection handle
+ */
+static void ldap_conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
+{
+ fr_trunk_connection_t *tconn = talloc_get_type_abort(uctx, fr_trunk_connection_t);
+
+ ERROR("rlm_ldap - Connection failed: %s", fr_syserror(fd_errno));
+
+ fr_connection_signal_reconnect(tconn->conn, FR_CONNECTION_FAILED);
+}
+
+/** Setup callbacks requested by LDAP trunk connections
+ *
+ * @param[in] tconn Trunk handle.
+ * @param[in] conn Individual connection callbacks are to be installed for.
+ * @param[in] el The event list to install events in.
+ * @param[in] notify_on The types of event the trunk wants to be notified on.
+ * @param[in] uctx Context provided to fr_trunk_alloc.
+ */
+static void ldap_trunk_connection_notify(fr_trunk_connection_t *tconn, fr_connection_t *conn,
+ fr_event_list_t *el,
+ fr_trunk_connection_event_t notify_on, UNUSED void *uctx)
+{
+ fr_ldap_connection_t *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
+ fr_event_fd_cb_t read_fn = NULL;
+ fr_event_fd_cb_t write_fn = NULL;
+ switch (notify_on) {
+ case FR_TRUNK_CONN_EVENT_NONE:
+ fr_event_fd_delete(el, ldap_conn->fd, FR_EVENT_FILTER_IO);
+ return;
+
+ case FR_TRUNK_CONN_EVENT_READ:
+ read_fn = ldap_conn_readable;
+ break;
+
+ case FR_TRUNK_CONN_EVENT_WRITE:
+ write_fn = ldap_conn_writable;
+ break;
+
+ case FR_TRUNK_CONN_EVENT_BOTH:
+ read_fn = ldap_conn_readable;
+ write_fn = ldap_conn_writable;
+ break;
+ }
+
+ if (fr_event_fd_insert(ldap_conn, el, ldap_conn->fd,
+ read_fn,
+ write_fn,
+ ldap_conn_error,
+ tconn) < 0) {
+ PERROR("Failed inserting FD event");
+ fr_trunk_connection_signal_reconnect(tconn, FR_CONNECTION_FAILED);
+ }
+}
+
+/** Allocate an LDAP trunk connection
+ *
+ * @param[in] tconn Trunk handle.
+ * @param[in] el Event list which will be used for I/O and timer events.
+ * @param[in] conn_conf Configuration of the connnection.
+ * @param[in] log_prefix What to prefix log messages with.
+ * @param[in] uctx User context passed to fr_trunk_alloc.
+ */
+static fr_connection_t *ldap_trunk_connection_alloc(fr_trunk_connection_t *tconn, fr_event_list_t *el,
+ UNUSED fr_connection_conf_t const *conn_conf,
+ char const *log_prefix, void *uctx)
+{
+ fr_ldap_thread_trunk_t *thread_trunk = talloc_get_type_abort(uctx, fr_ldap_thread_trunk_t);
+
+ return fr_ldap_connection_state_alloc(tconn, el, &thread_trunk->config, log_prefix);
+}
+
+#define POPULATE_LDAP_CONTROLS(_dest, _src) do { \
+ int i; \
+ for (i = 0; (_src[i].control) && (i < LDAP_MAX_CONTROLS); i++) { \
+ _dest[i] = _src[i].control; \
+ } \
+ _dest[i] = NULL; \
+} while (0)
+
+/** Take LDAP pending queries from the queue and send them.
+ *
+ * @param[in] el Event list for timers.
+ * @param[in] tconn Trunk handle.
+ * @param[in] conn on which to send the queries
+ * @param[in] uctx User context passed to fr_trunk_alloc
+ */
+static void ldap_trunk_request_mux(UNUSED fr_event_list_t *el, fr_trunk_connection_t *tconn,
+ fr_connection_t *conn, UNUSED void *uctx)
+{
+ fr_ldap_connection_t *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
+ fr_trunk_request_t *treq;
+
+ LDAPURLDesc *referral_url = NULL;
+
+ fr_ldap_query_t *query = NULL;
+ fr_ldap_rcode_t status = LDAP_PROC_ERROR;
+
+ while (fr_trunk_connection_pop_request(&treq, tconn) == 0) {
+
+ LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS + 1];
+ LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS + 1];
+
+ if (!treq) break;
+
+ query = talloc_get_type_abort(treq->preq, fr_ldap_query_t);
+
+ switch (query->type) {
+ case LDAP_REQUEST_SEARCH:
+ /*
+ * This query is a LDAP search
+ */
+ if (query->referral) referral_url = query->referral->referral_url;
+
+ /*
+ * Queries can be from parsed URLs, if so point at the relevant
+ * parts of the parsed structure
+ */
+ if (query->ldap_url) {
+ query->dn = query->ldap_url->lud_dn;
+ memcpy(&query->search.attrs, &query->ldap_url->lud_attrs, sizeof(query->search.attrs));
+ query->search.scope = query->ldap_url->lud_scope;
+ query->search.filter = query->ldap_url->lud_filter;
+
+ /*
+ * Parsing LDAP server extensions from the URL is only
+ * possible once we know which conneciton the query will be
+ * handled by as the conneciton handle is used by the parsing
+ * function.
+ */
+ if (query->ldap_url->lud_exts) {
+ LDAPControl *serverctrls[LDAP_MAX_CONTROLS];
+ int i;
+
+ if (fr_ldap_parse_url_extensions(serverctrls, query->request,
+ ldap_conn, query->ldap_url->lud_exts) < 0) {
+ error:
+ fr_trunk_request_signal_fail(query->treq);
+ return;
+ }
+ for (i = 0; i < LDAP_MAX_CONTROLS; i++) {
+ if (!serverctrls[i]) break;
+ query->serverctrls[i].control = serverctrls[i];
+ query->serverctrls[i].freeit = true;
+ }
+ }
+ }
+
+ POPULATE_LDAP_CONTROLS(our_serverctrls, query->serverctrls);
+ POPULATE_LDAP_CONTROLS(our_clientctrls, query->clientctrls);
+
+ /*
+ * If we are chasing a referral, referral_url will be populated and may
+ * have a base dn or scope to override the original query
+ */
+ status = fr_ldap_search_async(&query->msgid, query->request, &ldap_conn,
+ (referral_url && referral_url->lud_dn) ?
+ referral_url->lud_dn : query->dn,
+ (referral_url && referral_url->lud_scope) ?
+ referral_url->lud_scope : query->search.scope,
+ query->search.filter, query->search.attrs,
+ our_serverctrls, our_clientctrls);
+ break;
+
+ case LDAP_REQUEST_MODIFY:
+ /*
+ * This query is an LDAP modification
+ */
+ POPULATE_LDAP_CONTROLS(our_serverctrls, query->serverctrls);
+ POPULATE_LDAP_CONTROLS(our_clientctrls, query->clientctrls);
+
+ status = fr_ldap_modify_async(&query->msgid, query->request, &ldap_conn, query->dn, query->mods, our_serverctrls, our_clientctrls);
+ break;
+
+ default:
+ ERROR("Invalid LDAP query for trunk connection");
+ goto error;
+
+ }
+
+ if (status != LDAP_PROC_SUCCESS) goto error;
+
+ /*
+ * Record which connection was used for this query
+ * - results processing often needs access to an LDAP handle
+ */
+ query->ldap_conn = ldap_conn;
+
+ /*
+ * Add the query to the tree of pending queries for this trunk
+ */
+ fr_rb_insert(query->ldap_conn->queries, query);
+
+ fr_trunk_request_signal_sent(treq);
+ }
+
+}
+
+/** Read LDAP responses
+ *
+ * Responses from the LDAP server will cause the fd to become readable and trigger this
+ * callback. Most LDAP search responses have multiple messages in their response - we
+ * only gather those which are complete before either following a referral or passing
+ * the head of the resulting chain of messages back.
+ *
+ * @param[in] tconn Trunk connection associated with these results.
+ * @param[in] conn Connection handle for these results.
+ * @param[in] uctx Thread specific trunk structure - contains tree of pending queries.
+ */
+static void ldap_trunk_request_demux(UNUSED fr_trunk_connection_t *tconn, fr_connection_t *conn, void *uctx)
+{
+ fr_ldap_connection_t *ldap_conn = talloc_get_type_abort(conn->h, fr_ldap_connection_t);
+ fr_ldap_thread_trunk_t *t = talloc_get_type_abort(uctx, fr_ldap_thread_trunk_t);
+
+ int ret = 0, msgtype;
+ struct timeval poll = { 0, 10 };
+ LDAPMessage *result = NULL;
+ fr_ldap_rcode_t rcode;
+ fr_ldap_query_t find = { .msgid = -1 }, *query = NULL;
+ request_t *request;
+
+ /*
+ * Reset the idle timeout event
+ */
+ fr_event_timer_in(t->t, t->t->el, &t->ev, t->t->config->idle_timeout, _ldap_trunk_idle_timeout, t);
+
+ do {
+ /*
+ * Look for any results for which we have the complete result message
+ * ldap_result will return a pointer to a chain of messages.
+ */
+ ret = ldap_result(ldap_conn->handle, LDAP_RES_ANY, LDAP_MSG_ALL, &poll, &result);
+
+ switch (ret) {
+ case 0:
+ return;
+
+ case -1:
+ rcode = fr_ldap_error_check(NULL, ldap_conn, NULL, NULL);
+ if (rcode == LDAP_PROC_BAD_CONN) ERROR("Bad LDAP connection");
+ return;
+
+ default:
+ break;
+ }
+
+ find.msgid = ldap_msgid(result);
+ query = fr_rb_find(ldap_conn->queries, &find);
+
+ if (!query) {
+ WARN("Ignoring msgid %i - doesn't match any outstanding queries (it may have been cancelled)",
+ find.msgid);
+ continue;
+ }
+
+ msgtype = ldap_msgtype(result);
+
+ /*
+ * Request to reference in debug output
+ */
+ request = query->request;
+
+ ROPTIONAL(RDEBUG2, DEBUG2, "Got LDAP response of type \"%s\" for message %d",
+ ldap_msg_types[msgtype], query->msgid);
+ rcode = fr_ldap_error_check(NULL, ldap_conn, result, query->dn);
+
+ switch (rcode) {
+ case LDAP_PROC_SUCCESS:
+ query->ret = ((!query->mods) && (ldap_count_entries(ldap_conn->handle, result) == 0)) ?
+ LDAP_RESULT_NO_RESULT : LDAP_RESULT_SUCCESS;
+ break;
+
+ case LDAP_PROC_REFERRAL:
+ if (!t->t->config->chase_referrals) {
+ ROPTIONAL(REDEBUG, ERROR,
+ "LDAP referral received but 'chase_referrals' is set to 'no'");
+ query->ret = LDAP_RESULT_EXCESS_REFERRALS;
+ break;
+ }
+
+ if (query->referral_depth >= t->t->config->referral_depth) {
+ ROPTIONAL(REDEBUG, ERROR, "Maximum LDAP referral depth (%d) exceeded",
+ t->t->config->referral_depth);
+ query->ret = LDAP_RESULT_EXCESS_REFERRALS;
+ break;
+ }
+
+ /*
+ * If we've come here as the result of an existing referral
+ * clear the previous list of URLs before getting the next list.
+ */
+ if (query->referral_urls) ldap_memvfree((void **)query->referral_urls);
+
+ ldap_get_option(ldap_conn->handle, LDAP_OPT_REFERRAL_URLS, &query->referral_urls);
+ if (!(query->referral_urls) || (!(query->referral_urls[0]))) {
+ ROPTIONAL(REDEBUG, ERROR, "LDAP referral missing referral URL");
+ query->ret = LDAP_RESULT_MISSING_REFERRAL;
+ break;
+ }
+
+ query->referral_depth ++;
+
+ if (fr_ldap_referral_follow(query) == 0) {
+ next_follow:
+ ldap_msgfree(result);
+ continue;
+ }
+
+ ROPTIONAL(REDEBUG, ERROR, "Unable to follow any LDAP referral URLs");
+ query->ret = LDAP_RESULT_REFERRAL_FAIL;
+ break;
+
+ case LDAP_PROC_BAD_DN:
+ ROPTIONAL(RDEBUG2, DEBUG2, "DN %s does not exist", query->ldap_url->lud_dn);
+ query->ret = LDAP_RESULT_BAD_DN;
+ break;
+
+ default:
+ ROPTIONAL(RPERROR, PERROR, "LDAP server returned an error");
+
+ if (query->referral_depth > 0) {
+ /*
+ * We're processing a referral - see if there are any more to try
+ */
+ fr_dlist_talloc_free_item(&query->referrals, query->referral);
+
+ if ((fr_dlist_num_elements(&query->referrals) > 0) &&
+ (fr_ldap_referral_next(query) == 0)) goto next_follow;
+ }
+
+ query->ret = LDAP_RESULT_REFERRAL_FAIL;
+ break;
+ }
+
+ /*
+ * Remove the timeout event
+ */
+ if (query->ev) fr_event_timer_delete(&query->ev);
+
+ query->result = result;
+
+ /*
+ * Remove the query from the outstanding list and tidy up
+ */
+ fr_rb_remove(ldap_conn->queries, query);
+ fr_trunk_request_signal_complete(query->treq);
+ if (query->request) unlang_interpret_mark_runnable(query->request);
+
+ } while (1);
+}
+
+/** Find a thread specific LDAP connection for a specific URI / bind DN
+ *
+ * If no existing connection exists for that combination then create a new one
+ *
+ * @param[in] thread to which the connection belongs
+ * @param[in] uri of the host to find / create a connection to
+ * @param[in] bind_dn to make the connection as
+ * @param[in] bind_password for making connection
+ * @param[in] request currently being processed (only for debug messages)
+ * @return
+ * - an existing or new connection matching the URI and bind DN
+ * - NULL on failure
+ */
+fr_ldap_thread_trunk_t *fr_thread_ldap_trunk_get(fr_ldap_thread_t *thread, char const *uri,
+ char const *bind_dn, char const *bind_password,
+ request_t *request, fr_ldap_config_t const *config)
+{
+ fr_ldap_thread_trunk_t *found, find = {.uri = uri, .bind_dn = bind_dn};
+
+ ROPTIONAL(RDEBUG2, DEBUG2, "Looking for LDAP connection to %s bound as %s", uri, bind_dn);
+ found = fr_rb_find(thread->trunks, &find);
+
+ if (found) return found;
+
+ /*
+ * No existing connection matching the requirement - create a new one
+ */
+ ROPTIONAL(RDEBUG2, DEBUG2, "No existing connection found - creating new one");
+ found = talloc_zero(thread, fr_ldap_thread_trunk_t);
+
+ /*
+ * Buld config for this connection - start with module settings and
+ * override server and bind details
+ */
+ memcpy(&found->config, config, sizeof(fr_ldap_config_t));
+ found->config.server = talloc_strdup(found, uri);
+ found->config.admin_identity = talloc_strdup(found, bind_dn);
+ found->config.admin_password = talloc_strdup(found, bind_password);
+
+ found->uri = found->config.server;
+ found->bind_dn = found->config.admin_identity;
+
+ found->trunk = fr_trunk_alloc(found, thread->el,
+ &(fr_trunk_io_funcs_t){
+ .connection_alloc = ldap_trunk_connection_alloc,
+ .connection_notify = ldap_trunk_connection_notify,
+ .request_mux = ldap_trunk_request_mux,
+ .request_demux = ldap_trunk_request_demux,
+ .request_cancel_mux = ldap_request_cancel_mux
+ },
+ thread->trunk_conf,
+ "rlm_ldap", found, false);
+
+ if (!found->trunk) {
+ ROPTIONAL(REDEBUG, ERROR, "Unable to create LDAP connection");
+ talloc_free(found);
+ return NULL;
+ }
+
+ found->t = thread;
+
+ /*
+ * Insert event to close trunk if it becomes idle
+ */
+ fr_event_timer_in(thread, thread->el, &found->ev, thread->config->idle_timeout,
+ _ldap_trunk_idle_timeout, found);
+
+ fr_rb_insert(thread->trunks, found);
+
+ return found;
+}
+
+/** Lookup the state of a thread specific LDAP connection trunk for a specific URI / bind DN
+ *
+ * @param[in] thread to which the connection belongs
+ * @param[in] uri of the host to find / create a connection to
+ * @param[in] bind_dn to make the connection as
+ * @return
+ * - State of a trunk matching the URI and bind DN
+ * - FR_TRUNK_STATE_MAX if no matching trunk
+ */
+fr_trunk_state_t fr_thread_ldap_trunk_state(fr_ldap_thread_t *thread, char const *uri, char const *bind_dn)
+{
+ fr_ldap_thread_trunk_t *found, find = {.uri = uri, .bind_dn = bind_dn};
+
+ found = fr_rb_find(thread->trunks, &find);
+
+ return (found) ? found->trunk->state : FR_TRUNK_STATE_MAX;
+}
--- /dev/null
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file lib/ldap/referral.c
+ * @brief Functions to handle ldap referrals
+ *
+ * @author Nick Porter <nick.porter@networkradius.com>
+ * @copyright 2021 The FreeRADIUS Server Project.
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/ldap/base.h>
+
+
+/** Clear up a fr_ldap_referral_t
+ *
+ * If there is a parsed referral_url, that must be freed using libldap's ldap_free_urldesc
+ */
+static int _fr_ldap_referral_free(fr_ldap_referral_t *referral)
+{
+ if (referral->referral_url) ldap_free_urldesc(referral->referral_url);
+ return 0;
+}
+
+/** Allocate a new structure to handle an LDAP referral, setting the destructor
+ *
+ * @param[in] ctx to allocate the referral in
+ * @return
+ * - a new referral structure on success
+ * - NULL on failure
+ */
+fr_ldap_referral_t *fr_ldap_referral_alloc(TALLOC_CTX *ctx)
+{
+ fr_ldap_referral_t *referral;
+
+ referral = talloc_zero(ctx, fr_ldap_referral_t);
+ if (!referral) {
+ PERROR("Failed to allocate LDAP referral container");
+ return NULL;
+ }
+ talloc_set_destructor(referral, _fr_ldap_referral_free);
+
+ return referral;
+}
+
+/** Callback to send LDAP referral queries when a trunk becomes active
+ *
+ */
+static void _ldap_referral_send (UNUSED fr_trunk_t *trunk, UNUSED fr_trunk_state_t prev,
+ UNUSED fr_trunk_state_t state, void *uctx)
+{
+ fr_ldap_referral_t *referral = talloc_get_type_abort(uctx, fr_ldap_referral_t);
+ fr_ldap_query_t *query = referral->query;
+ request_t *request = query->request;
+
+ /*
+ * If referral is set, then another LDAP trunk has gone active first and sent the referral
+ */
+ if (query->referral) return;
+
+ /*
+ * Enqueue referral query on active trunk connection
+ */
+ query->referral = referral;
+ query->ttrunk = referral->ttrunk;
+ query->treq = fr_trunk_request_alloc(query->ttrunk->trunk, query->request);
+ fr_trunk_request_enqueue(&query->treq, query->ttrunk->trunk, query->request, query, NULL);
+
+ RDEBUG4("Pending LDAP referral query queued on active trunk");
+}
+
+
+/** Follow an LDAP referral
+ *
+ * The returned list of LDAP referrals should already be in query->referrals.
+ * We check all the possible referrals and look for one where there already
+ * is an active trunk connection.
+ *
+ * @param query whose result was one or more referral URLs
+ * @return
+ * - 0 on success.
+ * - < 0 on failure.
+ */
+int fr_ldap_referral_follow(fr_ldap_query_t *query)
+{
+ request_t *request = query->request;
+ fr_ldap_config_t *handle_config = query->ttrunk->t->config;
+ fr_ldap_thread_t *thread = query->ttrunk->t;
+ fr_ldap_thread_trunk_t *ttrunk = NULL;
+ int referral_no = -1;
+ fr_ldap_referral_t *referral;
+
+ /*
+ * In following a referral, firstly remove the query from the
+ * tree of pending queries clear the message id.
+ */
+ fr_rb_remove(query->ldap_conn->queries, query);
+ query->msgid = 0;
+ fr_trunk_request_signal_complete(query->treq);
+ query->treq = NULL;
+
+ if (query->referral_depth > 1) {
+ /*
+ * If we've already parsed a referral, clear the existing list of followers.
+ */
+ fr_dlist_talloc_free(&query->referrals);
+ query->referral = NULL;
+ } else {
+ /*
+ * Otherwise initialise the list header for followers.
+ */
+ fr_dlist_talloc_init(&query->referrals, fr_ldap_referral_t, entry);
+ }
+
+ while (query->referral_urls[++referral_no]) {
+ if (!ldap_is_ldap_url(query->referral_urls[referral_no])) {
+ RERROR("Referral %s does not look like an LDAP URL", query->referral_urls[referral_no]);
+ continue;
+ }
+
+ referral = fr_ldap_referral_alloc(query);
+ if (!referral) continue;
+
+ referral->query = query;
+
+ if (ldap_url_parse(query->referral_urls[referral_no], &referral->referral_url)) {
+ RERROR("Failed parsing referral LDAP URL %s", query->referral_urls[referral_no]);
+ free_referral:
+ talloc_free(referral);
+ continue;
+ }
+
+ referral->host_uri = talloc_asprintf(referral, "%s://%s:%d", referral->referral_url->lud_scheme,
+ referral->referral_url->lud_host, referral->referral_url->lud_port);
+
+ if (handle_config->use_referral_credentials) {
+ char **ext;
+
+ /*
+ * If there are no extensions, OpenLDAP doesn't
+ * bother allocating an array.
+ */
+ for (ext = referral->referral_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:
+ RERROR("Failed parsing extension \"%s\": "
+ "No attribute/value delimiter '='", *ext);
+ goto free_referral;
+ }
+ referral->identity = p + 1;
+ break;
+
+ case LDAP_EXT_BINDPW:
+ p = strchr(p, '=');
+ if (!p) goto bad_ext;
+ referral->password = p + 1;
+ break;
+
+ default:
+ if (critical) {
+ RERROR("Failed parsing critical extension \"%s\": "
+ "Not supported by FreeRADIUS", *ext);
+ goto free_referral;
+ }
+ DEBUG2("Skipping unsupported extension \"%s\"", *ext);
+ continue;
+ }
+ }
+ } else {
+ if (handle_config->rebind) {
+ referral->identity = handle_config->admin_identity;
+ referral->password = handle_config->admin_password;
+ }
+ }
+
+ fr_dlist_insert_tail(&query->referrals, referral);
+ if (fr_thread_ldap_trunk_state(thread, referral->host_uri,
+ referral->identity) != FR_TRUNK_STATE_ACTIVE) {
+ RDEBUG3("No active LDAP trunk for URI %s, bind DN %s", referral->host_uri, referral->identity);
+ continue;
+ }
+
+ ttrunk = fr_thread_ldap_trunk_get(thread, referral->host_uri, referral->identity,
+ referral->password, request, handle_config);
+
+ if (!ttrunk) {
+ RERROR("Unable to connect to LDAP referral URL");
+ fr_dlist_talloc_free_item(&query->referrals, referral);
+ continue;
+ }
+
+ /*
+ * We have an active trunk enqueue the request
+ */
+ query->ttrunk = ttrunk;
+ query->referral = referral;
+ query->treq = fr_trunk_request_alloc(ttrunk->trunk, request);
+ fr_trunk_request_enqueue(&query->treq, ttrunk->trunk, request, query, NULL);
+ return 0;
+ }
+
+ /*
+ * None of the referrals parsed successfully
+ */
+ if (fr_dlist_num_elements(&query->referrals) == 0) {
+ RERROR("No valid LDAP referrals to follow");
+ return -1;
+ }
+
+ /*
+ * We have parsed referrals, but none of them matched an existing active connection.
+ * Launch new trunks with callbacks so the first to become active will run the query.
+ */
+ referral = NULL;
+ while ((referral = fr_dlist_next(&query->referrals, referral))) {
+ ttrunk = fr_thread_ldap_trunk_get(thread, referral->host_uri, referral->identity,
+ referral->password, request, handle_config);
+ if (!ttrunk) {
+ fr_dlist_talloc_free_item(&query->referrals, referral);
+ continue;
+ }
+ referral->ttrunk = ttrunk;
+ fr_trunk_add_watch(ttrunk->trunk, FR_TRUNK_STATE_ACTIVE, _ldap_referral_send, true, referral);
+ RDEBUG4("Watch inserted to send referral query on active trunk.");
+ }
+
+ return 0;
+}
+
+/** Follow an alternative LDAP referral
+ *
+ * If an initial chase of an LDAP referral results in an error being returned
+ * this function can be used to attempt one of the other referral URLs given
+ * in the initial query results.
+ *
+ * The initial use of fr_ldap_referral_follow may have launched trunks for
+ * any referral URLs which parsed successfully, so this starts by looking
+ * for the first which has an active state and sends the query that way.
+ *
+ * If no active trunks match the remaining servers listed in referrals then
+ * new trunks are launched with watchers to send the query on the first
+ * active trunk.
+ *
+ * @param query whose referrals are being chased
+ * @return
+ * - 0 on success.
+ * - < 0 on failure.
+ */
+int fr_ldap_referral_next(fr_ldap_query_t *query)
+{
+ request_t *request = query->request;
+ fr_ldap_config_t *handle_config = query->ttrunk->t->config;
+ fr_ldap_thread_t *thread = query->ttrunk->t;
+ fr_ldap_referral_t *referral = NULL;
+ fr_ldap_thread_trunk_t *ttrunk;
+
+ while ((referral = fr_dlist_next(&query->referrals, referral))) {
+ if (fr_thread_ldap_trunk_state(thread, referral->host_uri,
+ referral->identity) != FR_TRUNK_STATE_ACTIVE) {
+ RDEBUG3("No active LDAP trunk for URI %s, bind DN %s", referral->host_uri, referral->identity);
+ continue;
+ }
+
+ ttrunk = fr_thread_ldap_trunk_get(thread, referral->host_uri, referral->identity,
+ referral->password, request, handle_config);
+
+ if (!ttrunk) {
+ RERROR("Unable to connect to LDAP referral URL");
+ fr_dlist_talloc_free_item(&query->referrals, referral);
+ continue;
+ }
+
+ /*
+ * We have an active trunk enqueue the request
+ */
+ query->ttrunk = ttrunk;
+ query->referral = referral;
+ query->treq = fr_trunk_request_alloc(ttrunk->trunk, request);
+ fr_trunk_request_enqueue(&query->treq, ttrunk->trunk, request, query, NULL);
+ return 0;
+ }
+
+ /*
+ * None of the referrals parsed successfully
+ */
+ if (fr_dlist_num_elements(&query->referrals) == 0) {
+ RERROR("No valid LDAP referrals to follow");
+ return -1;
+ }
+
+ /*
+ * None of the remaining referrals have an active trunk.
+ * Launch new trunks with callbacks so the first to become active will run the query.
+ */
+ referral = NULL;
+ while ((referral = fr_dlist_next(&query->referrals, referral))) {
+ ttrunk = fr_thread_ldap_trunk_get(thread, referral->host_uri, referral->identity,
+ referral->password, request, handle_config);
+ if (!ttrunk) {
+ fr_dlist_talloc_free_item(&query->referrals, referral);
+ continue;
+ }
+ referral->ttrunk = ttrunk;
+ fr_trunk_add_watch(ttrunk->trunk, FR_TRUNK_STATE_ACTIVE, _ldap_referral_send, true, referral);
+ RDEBUG4("Watch inserted to send referral query on active trunk.");
+ }
+
+ return 0;
+}
{ FR_CONF_OFFSET("use_referral_credentials", FR_TYPE_BOOL, rlm_ldap_t, handle_config.use_referral_credentials), .dflt = "no" },
+ { FR_CONF_OFFSET("referral_depth", FR_TYPE_UINT16, rlm_ldap_t, handle_config.referral_depth), .dflt = "5" },
+
{ FR_CONF_OFFSET("rebind", FR_TYPE_BOOL, rlm_ldap_t, handle_config.rebind) },
{ FR_CONF_OFFSET("sasl_secprops", FR_TYPE_STRING, rlm_ldap_t, handle_config.sasl_secprops) },
/* timeout for search results */
{ FR_CONF_OFFSET("res_timeout", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.res_timeout), .dflt = "20" },
+ { FR_CONF_OFFSET("idle_timeout", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.idle_timeout), .dflt = "300" },
+
CONF_PARSER_TERMINATOR
};
return 0;
}
+/** Initialise thread specific data structure
+ *
+ */
+static int mod_thread_instatiate(UNUSED CONF_SECTION const *conf, void *instance,
+ fr_event_list_t *el, void *thread)
+{
+ rlm_ldap_t *inst = instance;
+ fr_ldap_thread_t *this_thread = thread;
+
+ /*
+ * Initialise tree for connection trunks used by this thread
+ */
+ MEM(this_thread->trunks = fr_rb_inline_talloc_alloc(this_thread, fr_ldap_thread_trunk_t, node, fr_ldap_trunk_cmp, NULL));
+
+ this_thread->inst = inst;
+ this_thread->config = &inst->handle_config;
+ this_thread->el = el;
+
+ return 0;
+}
+
+/** Clean up thread specific data structure
+ *
+ */
+static int mod_thread_detach(UNUSED fr_event_list_t *el, void *thread)
+{
+ fr_ldap_thread_t *this_thread = thread;
+ fr_ldap_thread_trunk_t *ttrunk;
+ fr_rb_iter_preorder_t iter;
+
+ for (ttrunk = fr_rb_iter_init_preorder(&iter, this_thread->trunks);
+ ttrunk;
+ ttrunk = fr_rb_iter_next_preorder(&iter)) {
+ talloc_free(ttrunk->trunk);
+ }
+ talloc_free(this_thread->trunks);
+ return 0;
+}
+
/** Bootstrap the module
*
* Define attributes.
.bootstrap = mod_bootstrap,
.instantiate = mod_instantiate,
.detach = mod_detach,
+ .thread_inst_size = sizeof(fr_ldap_thread_t),
+ .thread_inst_type = "fr_ldap_thread_t",
+ .thread_instantiate = mod_thread_instatiate,
+ .thread_detach = mod_thread_detach,
.methods = {
[MOD_AUTHENTICATE] = mod_authenticate,
[MOD_AUTHORIZE] = mod_authorize,