Skip to content

Commit

Permalink
Rework LDAP group filter expansion to remove xlat_eval()
Browse files Browse the repository at this point in the history
  • Loading branch information
ndptech committed Feb 27, 2024
1 parent 7359251 commit 7e3939f
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 83 deletions.
5 changes: 3 additions & 2 deletions src/lib/ldap/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
*/
Expand Down
76 changes: 31 additions & 45 deletions src/lib/ldap/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
83 changes: 47 additions & 36 deletions src/modules/rlm_ldap/groups.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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");
Expand All @@ -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;
}

Expand Down Expand Up @@ -785,58 +788,66 @@ 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) {
.inst = inst,
.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;
}

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;
}
Expand Down
33 changes: 33 additions & 0 deletions src/modules/rlm_ldap/rlm_ldap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/modules/rlm_ldap/rlm_ldap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 7e3939f

Please sign in to comment.