]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Rework LDAP group filter expansion to remove xlat_eval()
authorNick Porter <nick@portercomputing.co.uk>
Fri, 23 Feb 2024 20:58:27 +0000 (20:58 +0000)
committerNick Porter <nick@portercomputing.co.uk>
Tue, 27 Feb 2024 13:29:51 +0000 (13:29 +0000)
src/lib/ldap/base.h
src/lib/ldap/util.c
src/modules/rlm_ldap/groups.c
src/modules/rlm_ldap/rlm_ldap.c
src/modules/rlm_ldap/rlm_ldap.h

index 841501a0ba5dbf2853c12f1f0dff52caa5d052f5..172376fc8ddf3b582ac0a48639f629f42b9ae523 100644 (file)
@@ -760,8 +760,6 @@ size_t              fr_ldap_uri_escape_func(UNUSED request_t *request, char *out, size_t out
 size_t         fr_ldap_uri_unescape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
                CC_HINT(nonnull(2,4));
 
-ssize_t                fr_ldap_xlat_filter(request_t *request, char const **sub, size_t sublen, char *out, size_t outlen);
-
 char const     *fr_ldap_error_str(fr_ldap_connection_t const *conn);
 
 fr_ldap_rcode_t        fr_ldap_search_async(int *msgid, request_t *request,
@@ -946,6 +944,9 @@ char const  *fr_ldap_url_err_to_str(int ldap_url_err);
 
 int            fr_ldap_box_escape(fr_value_box_t *vb, UNUSED void *uctx);
 
+int            fr_ldap_filter_to_tmpl(TALLOC_CTX *ctx, tmpl_rules_t const *t_rules, char const **sub, size_t sublen,
+                                      tmpl_t **out) CC_HINT(nonnull());
+
 /*
  *     referral.c - Handle LDAP referrals
  */
index b5df86f4f5b7d996c77b0943b47c8e0d25eea228..7165a8d89a7cfc8214119e13921566140012e3b8 100644 (file)
@@ -505,25 +505,25 @@ size_t fr_ldap_common_dn(char const *full, char const *part)
        return f_len - p_len;
 }
 
-/** Combine and expand filters
+/** Combine filters and tokenize to a tmpl
  *
- * @param request Current request.
- * @param out Where to write the expanded string.
- * @param outlen Length of output buffer.
- * @param sub Array of subfilters (may contain NULLs).
- * @param sublen Number of potential subfilters in array.
- * @return length of expanded data.
+ * @param ctx          To allocate combined filter in
+ * @param request      Current request.
+ * @param sub          Array of subfilters (may contain NULLs).
+ * @param sublen       Number of potential subfilters in array.
+ * @param out          Where to write a pointer to the resulting tmpl.
+ * @return length of combined data.
  */
-ssize_t fr_ldap_xlat_filter(request_t *request, char const **sub, size_t sublen, char *out, size_t outlen)
+int fr_ldap_filter_to_tmpl(TALLOC_CTX *ctx, tmpl_rules_t const *t_rules, char const **sub, size_t sublen, tmpl_t **out)
 {
-       char buffer[LDAP_MAX_FILTER_STR_LEN + 1];
-       char const *in = NULL;
-       char *p = buffer;
+       char            *buffer = NULL;
+       char const      *in = NULL;
+       ssize_t         len = 0;
+       size_t          i;
+       int             cnt = 0;
+       tmpl_t          *parsed;
 
-       ssize_t len = 0;
-
-       unsigned int i;
-       int cnt = 0;
+       *out = NULL;
 
        /*
         *      Figure out how many filter elements we need to integrate
@@ -532,53 +532,39 @@ ssize_t fr_ldap_xlat_filter(request_t *request, char const **sub, size_t sublen,
                if (sub[i] && *sub[i]) {
                        in = sub[i];
                        cnt++;
+                       len += strlen(sub[i]);
                }
        }
 
-       if (!cnt) {
-               out[0] = '\0';
-               return 0;
-       }
+       if (!cnt) return 0;
 
        if (cnt > 1) {
-               if (outlen < 3) {
-                       goto oob;
-               }
-
-               p[len++] = '(';
-               p[len++] = '&';
+               /*
+                *      Allocate a buffer large enough, allowing for (& ... ) plus trailing '\0'
+                */
+               buffer = talloc_array(ctx, char, len + 4);
 
+               strcpy(buffer, "(&");
                for (i = 0; i < sublen; i++) {
                        if (sub[i] && (*sub[i] != '\0')) {
-                               len += strlcpy(p + len, sub[i], outlen - len);
-
-                               if ((size_t) len >= outlen) {
-                                       oob:
-                                       REDEBUG("Out of buffer space creating filter");
-
-                                       return -1;
-                               }
+                               strcat(buffer, sub[i]);
                        }
                }
-
-               if ((outlen - len) < 2) {
-                       goto oob;
-               }
-
-               p[len++] = ')';
-               p[len] = '\0';
-
+               strcat(buffer, ")");
                in = buffer;
        }
 
-       len = xlat_eval(out, outlen, request, in, fr_ldap_uri_escape_func, NULL);
-       if (len < 0) {
-               REDEBUG("Failed creating filter");
+       len = tmpl_afrom_substr(ctx, &parsed, &FR_SBUFF_IN(in, strlen(in)), T_DOUBLE_QUOTED_STRING, NULL, t_rules);
+
+       talloc_free(buffer);
 
+       if (len < 0) {
+               EMARKER(in, -len, fr_strerror());
                return -1;
        }
 
-       return len;
+       *out = parsed;
+       return 0;
 }
 
 /** Check that a particular attribute is included in an attribute list
index e7dbcb9dc36f07b1546d94e6941e662bc4a1236b..b7a83b79aeb2bf1d4c92e4c3ad2ce971ee59f56f 100644 (file)
@@ -60,7 +60,8 @@ typedef struct {
        rlm_ldap_t const        *inst;                                  //!< Module instance.
        fr_value_box_t          *base_dn;                               //!< The base DN to search for groups in.
        fr_ldap_thread_trunk_t  *ttrunk;                                //!< Trunk on which to perform additional queries.
-       char                    filter[LDAP_MAX_FILTER_STR_LEN + 1];    //!< Filter used to search for groups.
+       tmpl_t                  *filter_tmpl;                           //!< Tmpl to expand into LDAP filter.
+       fr_value_box_list_t     expanded_filter;                        //!< Values produced by expanding filter xlat.
        char const              *attrs[2];                              //!< For retrieving the group name.
        fr_ldap_query_t         *query;                                 //!< Current query performing group lookup.
        void                    *uctx;                                  //!< Optional context for use in results parsing.
@@ -570,16 +571,21 @@ unlang_action_t rlm_ldap_cacheable_userobj(rlm_rcode_t *p_result, request_t *req
  * @param[in] uctx             Group lookup context.
  * @return One of the RLM_MODULE_* values.
  */
-static unlang_action_t ldap_cacheable_groupobj_start(UNUSED rlm_rcode_t *p_result, UNUSED int *priority, request_t *request,
+static unlang_action_t ldap_cacheable_groupobj_start(rlm_rcode_t *p_result, UNUSED int *priority, request_t *request,
                                                     void *uctx)
 {
        ldap_group_groupobj_ctx_t       *group_ctx = talloc_get_type_abort(uctx, ldap_group_groupobj_ctx_t);
        rlm_ldap_t const                *inst = group_ctx->inst;
+       fr_value_box_t                  *filter;
+
+       filter = fr_value_box_list_head(&group_ctx->expanded_filter);
+
+       if (filter->type != FR_TYPE_STRING) RETURN_MODULE_FAIL;
 
        group_ctx->attrs[0] = inst->group.obj_name_attr;
        return fr_ldap_trunk_search(group_ctx, &group_ctx->query, request, group_ctx->ttrunk,
                                    group_ctx->base_dn->vb_strvalue, inst->group.obj_scope,
-                                   group_ctx->filter, group_ctx->attrs, NULL, NULL);
+                                   filter->vb_strvalue, group_ctx->attrs, NULL, NULL);
 }
 
 /** Cancel a pending group object lookup.
@@ -695,7 +701,6 @@ unlang_action_t rlm_ldap_cacheable_groupobj(rlm_rcode_t *p_result, request_t *re
 {
        rlm_ldap_t const                *inst = autz_ctx->inst;
        ldap_group_groupobj_ctx_t       *group_ctx;
-       char const                      *filters[] = { inst->group.obj_filter, inst->group.obj_membership_filter };
 
        if (!inst->group.obj_membership_filter) {
                RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set");
@@ -711,19 +716,17 @@ unlang_action_t rlm_ldap_cacheable_groupobj(rlm_rcode_t *p_result, request_t *re
        group_ctx->inst = inst;
        group_ctx->ttrunk = autz_ctx->ttrunk;
        group_ctx->base_dn = &autz_ctx->call_env->group_base;
-
-       if (fr_ldap_xlat_filter(request, filters, NUM_ELEMENTS(filters),
-                               group_ctx->filter, sizeof(group_ctx->filter)) < 0) {
-               talloc_free(group_ctx);
-               RETURN_MODULE_INVALID;
-       }
+       fr_value_box_list_init(&group_ctx->expanded_filter);
 
        if (unlang_function_push(request, ldap_cacheable_groupobj_start, ldap_cacheable_groupobj_resume,
                                 ldap_group_groupobj_cancel, ~FR_SIGNAL_CANCEL, UNLANG_SUB_FRAME, group_ctx) < 0) {
+       error:
                talloc_free(group_ctx);
                RETURN_MODULE_FAIL;
        }
 
+       if (unlang_tmpl_push(group_ctx, &group_ctx->expanded_filter, request, autz_ctx->call_env->group_filter, NULL) < 0) goto error;
+
        return UNLANG_ACTION_PUSHED_CHILD;
 }
 
@@ -785,7 +788,6 @@ unlang_action_t rlm_ldap_check_groupobj_dynamic(rlm_rcode_t *p_result, request_t
 {
        rlm_ldap_t const                *inst = xlat_ctx->inst;
        ldap_group_groupobj_ctx_t       *group_ctx;
-       int                             ret;
 
        MEM(group_ctx = talloc(unlang_interpret_frame_talloc_ctx(request), ldap_group_groupobj_ctx_t));
        *group_ctx = (ldap_group_groupobj_ctx_t) {
@@ -793,42 +795,45 @@ unlang_action_t rlm_ldap_check_groupobj_dynamic(rlm_rcode_t *p_result, request_t
                .ttrunk = xlat_ctx->ttrunk,
                .uctx = xlat_ctx
        };
+       fr_value_box_list_init(&group_ctx->expanded_filter);
 
        if (fr_ldap_util_is_dn(xlat_ctx->group->vb_strvalue, xlat_ctx->group->vb_length)) {
-               char const *filters[] = { inst->group.obj_filter, inst->group.obj_membership_filter };
-
-               RINDENT();
-               ret = fr_ldap_xlat_filter(request,
-                                         filters, NUM_ELEMENTS(filters),
-                                         group_ctx->filter, sizeof(group_ctx->filter));
-               REXDENT();
-
-               if (ret < 0) {
-               invalid:
-                       talloc_free(group_ctx);
-                       RETURN_MODULE_INVALID;
-               }
-
+               group_ctx->filter_tmpl = xlat_ctx->env_data->group_filter;
                group_ctx->base_dn = xlat_ctx->group;
        } else {
-               char name_filter[LDAP_MAX_FILTER_STR_LEN];
-               char const *filters[] = { name_filter, inst->group.obj_filter, inst->group.obj_membership_filter };
+               char            name_filter[LDAP_MAX_FILTER_STR_LEN];
+               char const      *filters[] = { name_filter, inst->group.obj_filter, inst->group.obj_membership_filter };
+               tmpl_rules_t    t_rules;
 
                if (!inst->group.obj_name_attr) {
                        REDEBUG("Told to search for group by name, but missing 'group.name_attribute' "
                                "directive");
-
-                       goto invalid;
+               invalid:
+                       talloc_free(group_ctx);
+                       RETURN_MODULE_INVALID;
                }
 
+               t_rules = (tmpl_rules_t){
+                       .attr = {
+                               .dict_def = request->dict,
+                               .list_def = request_attr_request,
+                       },
+                       .xlat = {
+                               .runtime_el = unlang_interpret_event_list(request),
+                       },
+                       .at_runtime = true,
+                       .escape.func = fr_ldap_box_escape,
+                       .escape.safe_for = (fr_value_box_safe_for_t)fr_ldap_box_escape,
+                       .escape.mode = TMPL_ESCAPE_PRE_CONCAT,
+                       .literals_safe_for = (fr_value_box_safe_for_t)fr_ldap_box_escape,
+                       .cast = FR_TYPE_STRING,
+               };
+
                snprintf(name_filter, sizeof(name_filter), "(%s=%s)",
                         inst->group.obj_name_attr, xlat_ctx->group->vb_strvalue);
-               RINDENT();
-               ret = fr_ldap_xlat_filter(request,
-                                         filters, NUM_ELEMENTS(filters),
-                                         group_ctx->filter, sizeof(group_ctx->filter));
-               REXDENT();
-               if (ret < 0) goto invalid;
+
+               if (fr_ldap_filter_to_tmpl(group_ctx, &t_rules, filters, NUM_ELEMENTS(filters),
+                                          &group_ctx->filter_tmpl) < 0) goto invalid;
 
                fr_assert(xlat_ctx->env_data);
                group_ctx->base_dn = &xlat_ctx->env_data->group_base;
@@ -836,7 +841,13 @@ unlang_action_t rlm_ldap_check_groupobj_dynamic(rlm_rcode_t *p_result, request_t
 
        if (unlang_function_push(request, ldap_cacheable_groupobj_start, ldap_check_groupobj_resume,
                                 ldap_group_groupobj_cancel, ~FR_SIGNAL_CANCEL,
-                                UNLANG_SUB_FRAME, group_ctx) < 0) goto invalid;
+                                UNLANG_SUB_FRAME, group_ctx) < 0) {
+       error:
+               talloc_free(group_ctx);
+               RETURN_MODULE_FAIL;
+       }
+
+       if (unlang_tmpl_push(group_ctx, &group_ctx->expanded_filter, request, group_ctx->filter_tmpl, NULL) < 0) goto error;
 
        return UNLANG_ACTION_PUSHED_CHILD;
 }
index 7d0f452479353a41b1d3e6720c099f2e9012a429..8c14992fe7d8678e5dcc5a2a75b4ee2c54398e82 100644 (file)
@@ -74,6 +74,8 @@ typedef struct {
 
 static int ldap_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, UNUSED call_env_parser_t const *rule);
 
+static int ldap_group_filter_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, void const *data, UNUSED call_env_parser_t const *rule);
+
 static const call_env_parser_t sasl_call_env[] = {
        { FR_CALL_ENV_OFFSET("mech", FR_TYPE_STRING, CALL_ENV_FLAG_NONE, ldap_auth_call_env_t, user_sasl_mech) },
        { FR_CALL_ENV_OFFSET("authname", FR_TYPE_STRING, CALL_ENV_FLAG_NONE, ldap_auth_call_env_t, user_sasl_authname) },
@@ -218,6 +220,15 @@ static const call_env_method_t authorize_method_env = {
                { FR_CALL_ENV_SUBSECTION("group", NULL, CALL_ENV_FLAG_NONE,
                                         ((call_env_parser_t[]) {
                                                { FR_CALL_ENV_OFFSET("base_dn", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, ldap_autz_call_env_t, group_base) },
+                                               { FR_CALL_ENV_PARSE_ONLY_OFFSET("membership_filter", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, ldap_autz_call_env_t, group_filter),
+                                                 .pair.func = ldap_group_filter_parse,
+                                                 .pair.escape = {
+                                                       .func = fr_ldap_box_escape,
+                                                       .safe_for = (fr_value_box_safe_for_t)fr_ldap_box_escape,
+                                                       .mode = TMPL_ESCAPE_PRE_CONCAT
+                                                 },
+                                                 .pair.literals_safe_for = (fr_value_box_safe_for_t)fr_ldap_box_escape,
+                                               },
                                                CALL_ENV_TERMINATOR
                                         })) },
                { FR_CALL_ENV_SUBSECTION("profile", NULL, CALL_ENV_FLAG_NONE,
@@ -255,6 +266,15 @@ static const call_env_method_t xlat_memberof_method_env = {
                { FR_CALL_ENV_SUBSECTION("group", NULL, CALL_ENV_FLAG_NONE,
                                         ((call_env_parser_t[]) {
                                                { FR_CALL_ENV_OFFSET("base_dn", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, ldap_xlat_memberof_call_env_t, group_base) },
+                                               { FR_CALL_ENV_PARSE_ONLY_OFFSET("membership_filter", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT, ldap_xlat_memberof_call_env_t, group_filter),
+                                                 .pair.func = ldap_group_filter_parse,
+                                                 .pair.escape = {
+                                                       .func = fr_ldap_box_escape,
+                                                       .safe_for = (fr_value_box_safe_for_t)fr_ldap_box_escape,
+                                                       .mode = TMPL_ESCAPE_PRE_CONCAT
+                                                 },
+                                                 .pair.literals_safe_for = (fr_value_box_safe_for_t)fr_ldap_box_escape,
+                                               },
                                                CALL_ENV_TERMINATOR
                                         })) },
                CALL_ENV_TERMINATOR
@@ -2417,6 +2437,19 @@ static int ldap_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *ou
        return 0;
 }
 
+static int ldap_group_filter_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, UNUSED CONF_ITEM *ci,
+                                  void const *data, UNUSED call_env_parser_t const *rule)
+{
+       rlm_ldap_t const        *inst = talloc_get_type_abort_const(data, rlm_ldap_t);
+       char const              *filters[] = { inst->group.obj_filter, inst->group.obj_membership_filter };
+       tmpl_t                  *parsed;
+
+       if (fr_ldap_filter_to_tmpl(ctx, t_rules, filters, NUM_ELEMENTS(filters), &parsed) < 0) return -1;
+
+       *(void **)out = parsed;
+       return 0;
+}
+
 /** Instantiate the module
  *
  * Creates a new instance of the module reading parameters from a configuration section.
index f47a53e7787db881f9feec4cf266415c8384817a..5a40a8bec514ec59c731463fffc3d4075d2a9f70 100644 (file)
@@ -133,6 +133,7 @@ typedef struct {
        fr_value_box_t  user_base;                      //!< Base DN in which to search for users.
        fr_value_box_t  user_filter;                    //!< Filter to use when searching for users.
        fr_value_box_t  group_base;                     //!< Base DN in which to search for groups.
+       tmpl_t          *group_filter;                  //!< tmpl to expand as group membership filter.
        fr_value_box_t  default_profile;                //!< If this is set, we will search for a profile object
                                                        //!< with this name, and map any attributes it contains.
                                                        //!< No value should be set if profiles are not being used
@@ -152,6 +153,7 @@ typedef struct {
        fr_value_box_t  user_base;                      //!< Base DN in which to search for users.
        fr_value_box_t  user_filter;                    //!< Filter to use when searching for users.
        fr_value_box_t  group_base;                     //!< Base DN in which to search for groups.
+       tmpl_t          *group_filter;                  //!< tmpl to expand as group membership filter.
 } ldap_xlat_memberof_call_env_t;
 
 /** State list for resumption of authorization