From: Nick Porter Date: Fri, 23 Feb 2024 20:58:27 +0000 (+0000) Subject: Rework LDAP group filter expansion to remove xlat_eval() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7e3939f420c3725c49e67ec085a3f33cfa50ee2e;p=thirdparty%2Ffreeradius-server.git Rework LDAP group filter expansion to remove xlat_eval() --- diff --git a/src/lib/ldap/base.h b/src/lib/ldap/base.h index 841501a0ba5..172376fc8dd 100644 --- a/src/lib/ldap/base.h +++ b/src/lib/ldap/base.h @@ -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 */ diff --git a/src/lib/ldap/util.c b/src/lib/ldap/util.c index b5df86f4f5b..7165a8d89a7 100644 --- a/src/lib/ldap/util.c +++ b/src/lib/ldap/util.c @@ -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 diff --git a/src/modules/rlm_ldap/groups.c b/src/modules/rlm_ldap/groups.c index e7dbcb9dc36..b7a83b79aeb 100644 --- a/src/modules/rlm_ldap/groups.c +++ b/src/modules/rlm_ldap/groups.c @@ -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; } diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index 7d0f4524793..8c14992fe7d 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -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. diff --git a/src/modules/rlm_ldap/rlm_ldap.h b/src/modules/rlm_ldap/rlm_ldap.h index f47a53e7787..5a40a8bec51 100644 --- a/src/modules/rlm_ldap/rlm_ldap.h +++ b/src/modules/rlm_ldap/rlm_ldap.h @@ -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