From 5e9833aa7b1a89cbb2c0cf667a99cb0bc4ad7963 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 8 Oct 2013 20:59:22 +0200 Subject: AD: Add extended access filter https://fedorahosted.org/sssd/ticket/2082 Adds a new option that allows the admin to specify a LDAP access filter that can be applied globally, per-domain or per-forest. --- src/providers/ad/ad_access.c | 213 ++++++++++++++++++++++++++++++++++++++++++- src/providers/ad/ad_init.c | 5 +- 2 files changed, 216 insertions(+), 2 deletions(-) (limited to 'src/providers') diff --git a/src/providers/ad/ad_access.c b/src/providers/ad/ad_access.c index 51c872c3b..c16f04942 100644 --- a/src/providers/ad/ad_access.c +++ b/src/providers/ad/ad_access.c @@ -33,6 +33,196 @@ ad_access_done(struct tevent_req *req); static errno_t ad_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn); +/* + * More advanced format can be used to restrict the filter to a specific + * domain or a specific forest. This format is KEYWORD:NAME:FILTER + * + * KEYWORD can be one of DOM or FOREST + * KEYWORD can be missing + * NAME is a label. + * - if KEYWORD equals DOM or missing completely, the filter is applied + * for users from domain named NAME only + * - if KEYWORD equals FOREST, the filter is applied on users from + * forest named NAME only + * examples of valid filters are: + * apply filter on domain called dom1 only: + * dom1:(memberOf=cn=admins,ou=groups,dc=dom1,dc=com) + * apply filter on domain called dom2 only: + * DOM:dom2:(memberOf=cn=admins,ou=groups,dc=dom2,dc=com) + * apply filter on forest called EXAMPLE.COM only: + * FOREST:EXAMPLE.COM:(memberOf=cn=admins,ou=groups,dc=example,dc=com) + * + * If any of the extended formats are used, the filter MUST be enclosed + * already. + */ + +/* From least specific */ +#define AD_FILTER_GENERIC 0x01 +#define AD_FILTER_FOREST 0x02 +#define AD_FILTER_DOMAIN 0x04 + +#define KW_FOREST "FOREST" +#define KW_DOMAIN "DOM" + +/* parse filter in the format domain_name:filter */ +static errno_t +parse_sub_filter(TALLOC_CTX *mem_ctx, const char *full_filter, + char **filter, char **sub_name, int *flags, + const int flagconst) +{ + char *specdelim; + + specdelim = strchr(full_filter, ':'); + if (specdelim == NULL) return EINVAL; + + /* Make sure the filter is already enclosed in brackets */ + if (*(specdelim+1) != '(') return EINVAL; + + *sub_name = talloc_strndup(mem_ctx, full_filter, specdelim - full_filter); + *filter = talloc_strdup(mem_ctx, specdelim+1); + if (*sub_name == NULL || *filter == NULL) return ENOMEM; + + *flags = flagconst; + return EOK; +} + +static inline errno_t +parse_dom_filter(TALLOC_CTX *mem_ctx, const char *dom_filter, + char **filter, char **domname, int *flags) +{ + return parse_sub_filter(mem_ctx, dom_filter, filter, domname, + flags, AD_FILTER_DOMAIN); +} + +static inline errno_t +parse_forest_filter(TALLOC_CTX *mem_ctx, const char *forest_filter, + char **filter, char **forest_name, int *flags) +{ + return parse_sub_filter(mem_ctx, forest_filter, filter, forest_name, + flags, AD_FILTER_FOREST); +} + + +static errno_t +parse_filter(TALLOC_CTX *mem_ctx, const char *full_filter, + char **filter, char **spec, int *flags) +{ + char *kwdelim, *specdelim; + + if (filter == NULL || spec == NULL || flags == NULL) return EINVAL; + + kwdelim = strchr(full_filter, ':'); + if (kwdelim != NULL) { + specdelim = strchr(kwdelim+1, ':'); + + if (specdelim == NULL) { + /* There is a single keyword. Treat it as a domain name */ + return parse_dom_filter(mem_ctx, full_filter, filter, spec, flags); + } else if (strncmp(full_filter, "DOM", kwdelim-full_filter) == 0) { + /* The format must be DOM:domain_name:filter */ + if (specdelim && specdelim-kwdelim <= 1) { + /* Check if there is some domain_name */ + return EINVAL; + } + + return parse_dom_filter(mem_ctx, kwdelim + 1, filter, spec, flags); + } else if (strncmp(full_filter, "FOREST", kwdelim-full_filter) == 0) { + /* The format must be FOREST:forest_name:filter */ + if (specdelim && specdelim-kwdelim <= 1) { + /* Check if there is some domain_name */ + return EINVAL; + } + + return parse_forest_filter(mem_ctx, kwdelim + 1, + filter, spec, flags); + } + + /* Malformed option */ + DEBUG(SSSDBG_CRIT_FAILURE, + ("Keyword in filter [%s] did not match expected format\n", + full_filter)); + return EINVAL; + } + + /* No keyword. Easy. */ + *filter = talloc_strdup(mem_ctx, full_filter); + if (*filter == NULL) return ENOMEM; + + *spec = NULL; + *flags = AD_FILTER_GENERIC; + return EOK; +} + +static errno_t +ad_parse_access_filter(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *filter_list, + char **_filter) +{ + char **filters; + int nfilters; + errno_t ret; + char *best_match; + int best_flags; + char *filter; + char *spec; + int flags; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = split_on_separator(tmp_ctx, filter_list, '?', true, true, + &filters, &nfilters); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("Cannot parse the list of ad_access_filters\n")); + goto done; + } + + best_match = NULL; + best_flags = 0; + for (int i=0; i < nfilters; i++) { + ret = parse_filter(tmp_ctx, filters[i], &filter, &spec, &flags); + if (ret != EOK) { + /* Skip the faulty filter. At worst, the user won't be + * allowed access */ + DEBUG(SSSDBG_MINOR_FAILURE, ("Access filter [%s] could not be " + "parsed, skipping\n", filters[i])); + continue; + } + + if (flags & AD_FILTER_DOMAIN && strcasecmp(spec, dom->name) != 0) { + /* If the filter specifies a domain, it must match the + * domain the user comes from + */ + continue; + } + + if (flags & AD_FILTER_FOREST && strcasecmp(spec, dom->forest) != 0) { + /* If the filter specifies a forest, it must match the + * forest the user comes from + */ + continue; + } + + if (flags > best_flags) { + best_flags = flags; + best_match = filter; + } + } + + ret = EOK; + /* Make sure the result is enclosed in brackets */ + *_filter = sdap_get_access_filter(mem_ctx, best_match); +done: + talloc_free(tmp_ctx); + return ret; +} + struct ad_access_state { struct tevent_context *ev; struct ad_access_ctx *ctx; @@ -40,6 +230,7 @@ struct ad_access_state { struct be_ctx *be_ctx; struct sss_domain_info *domain; + char *filter; struct sdap_id_conn_ctx **clist; int cindex; }; @@ -67,6 +258,14 @@ ad_access_send(TALLOC_CTX *mem_ctx, state->be_ctx = be_ctx; state->domain = domain; + ret = ad_parse_access_filter(state, domain, ctx->sdap_access_ctx->filter, + &state->filter); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Could not determine the best filter\n")); + ret = ERR_ACCESS_DENIED; + goto done; + } + state->clist = talloc_zero_array(state, struct sdap_id_conn_ctx *, 3); if (state->clist == NULL) { ret = ENOMEM; @@ -106,13 +305,25 @@ ad_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn) { struct tevent_req *subreq; struct ad_access_state *state; + struct sdap_access_ctx *req_ctx; state = tevent_req_data(req, struct ad_access_state); + req_ctx = talloc(state, struct sdap_access_ctx); + if (req_ctx == NULL) { + return ENOMEM; + } + req_ctx->id_ctx = state->ctx->sdap_access_ctx->id_ctx; + req_ctx->filter = state->filter; + memcpy(&req_ctx->access_rule, + state->ctx->sdap_access_ctx->access_rule, + sizeof(int) * LDAP_ACCESS_LAST); + subreq = sdap_access_send(req, state->ev, state->be_ctx, - state->domain, state->ctx->sdap_access_ctx, + state->domain, req_ctx, conn, state->pd); if (req == NULL) { + talloc_free(req_ctx); return ENOMEM; } tevent_req_set_callback(subreq, ad_access_done, req); diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c index d7f41a563..d06efbd08 100644 --- a/src/providers/ad/ad_init.c +++ b/src/providers/ad/ad_init.c @@ -402,7 +402,10 @@ sssm_ad_access_init(struct be_ctx *bectx, access_ctx->sdap_access_ctx->access_rule[0] = LDAP_ACCESS_EXPIRE; filter = dp_opt_get_cstring(access_ctx->ad_options, AD_ACCESS_FILTER); if (filter != NULL) { - access_ctx->sdap_access_ctx->filter = sdap_get_access_filter( + /* The processing of the extended filter is performed during the access + * check itself + */ + access_ctx->sdap_access_ctx->filter = talloc_strdup( access_ctx->sdap_access_ctx, filter); if (access_ctx->sdap_access_ctx->filter == NULL) { -- cgit