]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
v4: Move LDAP xlat to use trunks and to the new API (#4262)
authorNick Porter <nick@portercomputing.co.uk>
Fri, 8 Oct 2021 17:09:07 +0000 (18:09 +0100)
committerGitHub <noreply@github.com>
Fri, 8 Oct 2021 17:09:07 +0000 (12:09 -0500)
* When escaping vbs in a URI, remove them from the list and re-insert afterwards

Escaping functions may re-initialise the vb which destroys the list
pointers.  Removing and re-inserting is much safer.

* Update rlm_rest's uri_part_escape to match new behaviour or fr_uri_escape()

* Define ldap_xlat_thread_inst_t to hold thread specific xlat data

* Define mod_xlat_thread_instantiate for LDAP xlats

* Define uri_part_escape for LDAP URIs

* Define xlat resume function for ldap xlat

* Define ldap_xlat_signal for signalling async ldap xlat

* Define ldap_query_timeout() callback for query timeouts

* Define parts of LDAP uri

* Convert %{ldap: } to new xlat api

* Add trunk_conf to ldap_inst_s

* Give thread instance access to trunk config

src/lib/util/uri.c
src/modules/rlm_ldap/rlm_ldap.c
src/modules/rlm_ldap/rlm_ldap.h
src/modules/rlm_rest/rlm_rest.c

index 48df1ffaa3c40f061e3490a086453217ff86430f..12868a76f88bb805d678d0bc4ef5c08642dc3129 100644 (file)
@@ -59,9 +59,18 @@ int fr_uri_escape(fr_value_box_list_t *uri, fr_uri_part_t const *uri_parts, void
                 *      Tainted boxes can only belong to a single part of the URI
                 */
                if (uri_vb->tainted) {
-                       if ((uri_part->func) && (uri_part->func(uri_vb, uctx) < 0)) {
-                               fr_strerror_printf_push("Unable to escape tainted input %pV", uri_vb);
-                               return -1;
+                       if (uri_part->func) {
+                               /*
+                                *      Escaping often ends up breaking the vb's list pointers
+                                *      so remove it from the list and re-insert after the escaping
+                                *      has been done
+                                */
+                               fr_value_box_t  *prev = fr_dlist_remove(uri, uri_vb);
+                               if (uri_part->func(uri_vb, uctx) < 0) {
+                                       fr_strerror_printf_push("Unable to escape tainted input %pV", uri_vb);
+                                       return -1;
+                               }
+                               fr_dlist_insert_after(uri, prev, uri_vb);
                        }
                        continue;
                }
index 737b776289934ff5c050b9a2d61669235b77b110..8fa2a29c711ebb48013e717dd93c209b0f762bae 100644 (file)
@@ -32,11 +32,20 @@ RCSID("$Id$")
 USES_APPLE_DEPRECATED_API
 
 #include <freeradius-devel/util/debug.h>
+#include <freeradius-devel/util/uri.h>
 
 #include "rlm_ldap.h"
 
 #include <freeradius-devel/server/map_proc.h>
 
+
+/*
+ *     Xlat pointer to thread handling the query
+ */
+typedef struct {
+       fr_ldap_thread_t        *t;
+} ldap_xlat_thread_inst_t;
+
 static CONF_PARSER sasl_mech_dynamic[] = {
        { FR_CONF_OFFSET("mech", FR_TYPE_TMPL | FR_TYPE_NOT_EMPTY, fr_ldap_sasl_t_dynamic_t, mech) },
        { FR_CONF_OFFSET("proxy", FR_TYPE_TMPL, fr_ldap_sasl_t_dynamic_t, proxy) },
@@ -349,110 +358,214 @@ static xlat_action_t ldap_unescape_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out, requ
        return XLAT_ACTION_DONE;
 }
 
-/** Expand an LDAP URL into a query, and return a string result from that query.
+/** Escape function for a part of an LDAP URI
  *
- * @ingroup xlat_functions
  */
-static ssize_t ldap_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen,
-                        void const *mod_inst, UNUSED void const *xlat_inst,
-                        request_t *request, char const *fmt)
+static int uri_part_escape(fr_value_box_t *vb, UNUSED void *uctx)
 {
-       fr_ldap_rcode_t         status;
-       size_t                  len = 0;
-       rlm_ldap_t const        *inst = mod_inst;
-
-       LDAPURLDesc             *ldap_url;
-       LDAPMessage             *result = NULL;
-       LDAPMessage             *entry = NULL;
+       fr_sbuff_t              sbuff;
+       fr_sbuff_uctx_talloc_t  sbuff_ctx;
+       size_t                  len;
 
-       struct berval           **values;
+       /*
+        *      Maximum space needed for output would be 3 times the input if every
+        *      char needed escaping
+        */
+       if (!fr_sbuff_init_talloc(vb, &sbuff, &sbuff_ctx, vb->length * 3, vb->length * 3)) {
+               fr_strerror_printf_push("Failed to allocate buffer for escaped argument");
+               return -1;
+       }
 
-       fr_ldap_connection_t    *conn;
-       int                     ldap_errno;
+       /*
+        *      Call the escape function, including the space for the trailing NULL
+        */
+       len = fr_ldap_escape_func(NULL, fr_sbuff_buff(&sbuff), vb->length * 3 + 1, vb->vb_strvalue, NULL);
 
-       char const              *url;
-       char const              **attrs;
+       fr_sbuff_trim_talloc(&sbuff, len);
+       fr_value_box_clear_value(vb);
+       fr_value_box_strdup_shallow(vb, NULL, fr_sbuff_buff(&sbuff), vb->tainted);
 
-       LDAPControl             *server_ctrls[] = { NULL, NULL };
+       return 0;
+}
 
-       url = fmt;
+/** Callback when LDAP query times out
+ *
+ */
+static void ldap_query_timeout(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
+{
+       fr_ldap_query_t         *query = talloc_get_type_abort(uctx, fr_ldap_query_t);
+       request_t               *request = query->request;
 
-       if (!ldap_is_ldap_url(url)) {
-               REDEBUG("String passed does not look like an LDAP URL");
-               return -1;
+       RERROR("Timeout waiting for LDAP query");
+       if (query->msgid) {
+               fr_trunk_request_signal_cancel(query->treq);
        }
 
-       if (ldap_url_parse(url, &ldap_url)){
-               REDEBUG("Parsing LDAP URL failed");
-               return -1;
-       }
+       query->ret = LDAP_RESULT_TIMEOUT;
+       unlang_interpret_mark_runnable(request);
+}
+
+/** Callback when resuming after async ldap query is completed
+ *
+ */
+static xlat_action_t ldap_xlat_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, request_t *request,
+                                     UNUSED void const *xlat_inst, UNUSED void *xlat_thread_inst,
+                                     UNUSED fr_value_box_list_t *in, void *rctx)
+{
+       fr_ldap_query_t         *query = talloc_get_type_abort(rctx, fr_ldap_query_t);
+       fr_ldap_connection_t    *ldap_conn = query->ldap_conn;
+       fr_value_box_t          *vb = NULL;
+       LDAPMessage             *msg;
+       struct berval           **values;
+       int                     count, i;
+
+       if (query->ret != LDAP_RESULT_SUCCESS) return XLAT_ACTION_FAIL;
 
        /*
-        *      Nothing, empty string, "*" string, or got 2 things, die.
+        *      We only parse "entries"
         */
-       if (!ldap_url->lud_attrs || !ldap_url->lud_attrs[0] ||
-           !*ldap_url->lud_attrs[0] ||
-           (strcmp(ldap_url->lud_attrs[0], "*") == 0) ||
-           ldap_url->lud_attrs[1]) {
-               REDEBUG("Bad attributes list in LDAP URL. URL must specify exactly one attribute to retrieve");
+       for (msg = ldap_first_entry(ldap_conn->handle, query->result); msg; msg = ldap_next_entry(ldap_conn->handle, msg)) {
 
-               goto free_urldesc;
+               values = ldap_get_values_len(ldap_conn->handle, msg, query->ldap_url->lud_attrs[0]);
+
+               if (!values) {
+                       RDEBUG2("No \"%s\" attributes found in specified object", query->ldap_url->lud_attrs[0]);
+                       continue;
+               }
+
+               count = ldap_count_values_len(values);
+               for (i = 0; i < count; i++) {
+                       MEM(vb = fr_value_box_alloc_null(ctx));
+                       if (fr_value_box_bstrndup(ctx, vb, NULL, values[i]->bv_val, values[i]->bv_len, true) < 0) {
+                               talloc_free(vb);
+                               RPERROR("Failed creating value from LDAP response");
+                               break;
+                       }
+                       fr_dcursor_append(out, vb);
+               }
+
+               ldap_value_free_len(values);
        }
 
-       conn = mod_conn_get(inst, request);
-       if (!conn) goto free_urldesc;
+       talloc_free(query);
 
-       memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs));
+       return XLAT_ACTION_DONE;
+}
 
-       if (fr_ldap_parse_url_extensions(&server_ctrls[0], request, conn, ldap_url->lud_exts) < 0) goto free_socket;
+/** Callback for signalling async ldap query
+ *
+ */
+static void ldap_xlat_signal(request_t *request, UNUSED void *instance, UNUSED void *thread, void *rctx, fr_state_signal_t action)
+{
+       fr_ldap_query_t         *query = talloc_get_type_abort(rctx, fr_ldap_query_t);
 
-       status = fr_ldap_search(&result, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope,
-                               ldap_url->lud_filter, attrs, server_ctrls, NULL);
+       if (action != FR_SIGNAL_CANCEL) return;
 
-#ifdef HAVE_LDAP_CREATE_SORT_CONTROL
-       if (server_ctrls[0]) ldap_control_free(server_ctrls[0]);
-#endif
+       RDEBUG2("Forcefully cancelling pending LDAP query");
 
-       switch (status) {
-       case LDAP_PROC_SUCCESS:
-               break;
+       fr_trunk_request_signal_cancel(query->treq);
+}
 
-       default:
-               goto free_socket;
+
+static fr_uri_part_t const ldap_uri_parts[] = {
+       { .name = "scheme", .terminals = &FR_SBUFF_TERMS(L(":")), .part_adv = { [':'] = 1 },
+         .tainted_allowed = false, .extra_skip = 2 },
+       { .name = "host", .terminals = &FR_SBUFF_TERMS(L(":"), L("/")), .part_adv = { [':'] = 1, ['/'] = 2 },
+         .tainted_allowed = false },
+       { .name = "port", .terminals = &FR_SBUFF_TERMS(L("/")), .part_adv = { ['/'] = 1 },
+         .tainted_allowed = false },
+       { .name = "dn", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 },
+         .tainted_allowed = true, .func = uri_part_escape },
+       { .name = "attrs", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 },
+         .tainted_allowed = false },
+       { .name = "scope", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1 },
+         .tainted_allowed = true, .func = uri_part_escape },
+       { .name = "filter", .terminals = &FR_SBUFF_TERMS(L("?")), .part_adv = { ['?'] = 1},
+         .tainted_allowed = true, .func = uri_part_escape },
+       { .name = "exts", .tainted_allowed = true, .func = uri_part_escape },
+       XLAT_URI_PART_TERMINATOR
+};
+
+static xlat_arg_parser_t const ldap_xlat_arg = { .required = true, .type = FR_TYPE_STRING };
+
+/** Expand an LDAP URL into a query, and return a string result from that query.
+ *
+ * @ingroup xlat_functions
+ */
+static xlat_action_t ldap_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out, request_t *request,
+                              UNUSED void const *xlat_inst, void *xlat_thread_inst,
+                              fr_value_box_list_t *in)
+{
+       fr_value_box_t          *in_vb = NULL;
+       ldap_xlat_thread_inst_t *xt = talloc_get_type_abort(xlat_thread_inst, ldap_xlat_thread_inst_t);
+       char                    *host_url;
+       fr_ldap_config_t const  *handle_config = &xt->t->inst->handle_config;
+
+       fr_ldap_query_t         *query = NULL;
+
+       LDAPURLDesc             *ldap_url;
+
+       if (fr_uri_escape(in, ldap_uri_parts, NULL) < 0) return XLAT_ACTION_FAIL;
+
+       in_vb = fr_dlist_head(in);
+       if(fr_value_box_list_concat_in_place(in_vb, in_vb, in, FR_TYPE_STRING, FR_VALUE_BOX_LIST_FREE,
+                                            true, SIZE_MAX) < 0) {
+               REDEBUG("Failed concattenating input");
+               return XLAT_ACTION_FAIL;
        }
 
-       fr_assert(conn);
-       fr_assert(result);
+       if (!ldap_is_ldap_url(in_vb->vb_strvalue)) {
+               REDEBUG("String passed does not look like an LDAP URL");
+               return XLAT_ACTION_FAIL;
+       }
 
-       entry = ldap_first_entry(conn->handle, result);
-       if (!entry) {
-               ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-               REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
-               len = -1;
-               goto free_result;
+       query = fr_ldap_query_alloc(unlang_interpret_frame_talloc_ctx(request));
+
+       if (ldap_url_parse(in_vb->vb_strvalue, &query->ldap_url)){
+               REDEBUG("Parsing LDAP URL failed");
+       free_query:
+               talloc_free(query);
+               return XLAT_ACTION_FAIL;
        }
 
-       values = ldap_get_values_len(conn->handle, entry, ldap_url->lud_attrs[0]);
-       if (!values) {
-               RDEBUG2("No \"%s\" attributes found in specified object", ldap_url->lud_attrs[0]);
-               goto free_result;
+       ldap_url = query->ldap_url;
+
+       /*
+        *      Nothing, empty string, "*" string, or got 2 things, die.
+        */
+       if (!ldap_url->lud_attrs || !ldap_url->lud_attrs[0] || !*ldap_url->lud_attrs[0] ||
+           (strcmp(ldap_url->lud_attrs[0], "*") == 0) || ldap_url->lud_attrs[1]) {
+               REDEBUG("Bad attributes list in LDAP URL. URL must specify exactly one attribute to retrieve");
+
+               goto free_query;
+       }
+
+       /*
+        *      If the URL is <scheme>:/// the parsed host will be NULL - use config default
+        */
+       if (!ldap_url->lud_host) {
+               host_url = handle_config->server;
+       } else {
+               host_url = talloc_asprintf(query, "%s://%s:%d", ldap_url->lud_scheme,
+                                          ldap_url->lud_host, ldap_url->lud_port);
        }
 
-       if (values[0]->bv_len >= outlen) goto free_values;
+       query->ttrunk = fr_thread_ldap_trunk_get(xt->t, host_url, handle_config->admin_identity,
+                                                handle_config->admin_password, request, handle_config);
+       if (!query->ttrunk) {
+               REDEBUG("Unable to get LDAP query for xlat");
+               goto free_query;
+       }
 
-       memcpy(*out, values[0]->bv_val, values[0]->bv_len + 1); /* +1 as strlcpy expects buffer size */
-       len = values[0]->bv_len;
+       query->type = LDAP_REQUEST_SEARCH;
+       query->request = request;
 
-free_values:
-       ldap_value_free_len(values);
-free_result:
-       ldap_msgfree(result);
-free_socket:
-       ldap_mod_conn_release(inst, request, conn);
-free_urldesc:
-       ldap_free_urldesc(ldap_url);
+       fr_trunk_request_enqueue(&query->treq, query->ttrunk->trunk, request, query, NULL);
+
+       fr_event_timer_in(query, unlang_interpret_event_list(request), &query->ev, handle_config->res_timeout,
+                         ldap_query_timeout, query);
 
-       return len;
+       return unlang_xlat_yield(request, ldap_xlat_resume, ldap_xlat_signal, query);
 }
 
 /*
@@ -1563,6 +1676,17 @@ static int parse_sub_section(rlm_ldap_t *inst, CONF_SECTION *parent, ldap_acct_s
        return 0;
 }
 
+static int mod_xlat_thread_instantiate(UNUSED void *xlat_inst, void *xlat_thread_inst,
+                                      UNUSED xlat_exp_t const *exp, void *uctx)
+{
+       rlm_ldap_t              *inst = talloc_get_type_abort(uctx, rlm_ldap_t);
+       ldap_xlat_thread_inst_t *xt = xlat_thread_inst;
+
+       xt->t = talloc_get_type_abort(module_thread_by_data(inst)->data, fr_ldap_thread_t);
+
+       return 0;
+}
+
 /** Initialise thread specific data structure
  *
  */
@@ -1579,6 +1703,7 @@ static int mod_thread_instatiate(UNUSED CONF_SECTION const *conf, void *instance
 
        this_thread->inst = inst;
        this_thread->config = &inst->handle_config;
+       this_thread->trunk_conf = &inst->trunk_conf;
        this_thread->el = el;
 
        return 0;
@@ -1659,7 +1784,9 @@ static int mod_bootstrap(void *instance, CONF_SECTION *conf)
                inst->cache_da = inst->group_da;        /* Default to the group_da */
        }
 
-       xlat_register_legacy(inst, inst->name, ldap_xlat, fr_ldap_escape_func, NULL, 0, XLAT_DEFAULT_BUF_LEN);
+       xlat = xlat_register(NULL, inst->name, ldap_xlat, false);
+       xlat_func_mono(xlat, &ldap_xlat_arg);
+       xlat_async_thread_instantiate_set(xlat, mod_xlat_thread_instantiate, ldap_xlat_thread_inst_t, NULL, inst);
        xlat = xlat_register(NULL, "ldap_escape", ldap_escape_xlat, false);
        xlat_func_mono(xlat, &ldap_escape_xlat_arg);
        xlat = xlat_register(NULL, "ldap_unescape", ldap_unescape_xlat, false);
index bec6f545a9ef2f27ab491a7af865f13cbaa72cdc..0bc704067c8332e742a3de018b864e5fe89d053e 100644 (file)
@@ -141,6 +141,7 @@ struct ldap_inst_s {
 
        fr_pool_t       *pool;                          //!< Connection pool instance.
        fr_ldap_config_t handle_config;                 //!< Connection configuration instance.
+       fr_trunk_conf_t trunk_conf;                     //!< Trunk configuration
 
        /*
         *      Global config
index 21b76fffc633a8e556bc8b495d1b8ddea68e3bef..268c8a3a224963bab7d10ae426257ebe5d9e7c94 100644 (file)
@@ -330,7 +330,6 @@ static int uri_part_escape(fr_value_box_t *vb, void *uctx)
 {
        char                    *escaped;
        fr_curl_io_request_t    *randle = talloc_get_type_abort(uctx, fr_curl_io_request_t);
-       fr_dlist_t              entry;
        request_t               *request = randle->request;
 
        escaped = curl_easy_escape(randle->candle, vb->vb_strvalue, vb->length);
@@ -346,16 +345,9 @@ static int uri_part_escape(fr_value_box_t *vb, void *uctx)
        }
 
        RDEBUG4("Tainted value %pV escaped to %s", vb, escaped);
-       /*
-        *      Store list pointers to restore later - fr_value_box_clear() clears them
-        */
-       entry = vb->entry;
-
-       fr_value_box_clear(vb);
 
+       fr_value_box_clear_value(vb);
        fr_value_box_strdup(vb, vb, NULL, escaped, vb->tainted);
-       vb->entry.next = entry.next;
-       vb->entry.prev = entry.prev;
 
        curl_free(escaped);