summaryrefslogtreecommitdiffstats
path: root/src/providers/ad/ad_access.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ad/ad_access.c')
-rw-r--r--src/providers/ad/ad_access.c213
1 files changed, 212 insertions, 1 deletions
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);