dpward / rpms / sssd

Forked from rpms/sssd 3 years ago
Clone
Blob Blame History Raw
From 8c714cbf1d0ce2cbddc4222ade51e1f93f36dbe8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 22 Jan 2014 15:21:24 +0100
Subject: [PATCH 71/71] AD: Establish cross-domain memberships after
 enumeration finishes

Because domain enumeration currently works for each domain separately,
the code has to establish cross-domain memberships after all domains are
enumerated. The code works as follows:

    1) check if any *sub*domains were enumerated. If not, do nothing
    2) if any of the groups saved had more original members than
       sysdb members, check if members of these groups can be linked now
       that all users and groups are saved using the orig_member
       attribute of the group matched against originalDN member of the
       user.

Related:
https://fedorahosted.org/sssd/ticket/2142
---
 src/providers/ad/ad_id.c         | 390 +++++++++++++++++++++++++++++++++++++--
 src/providers/ad/ad_subdomains.c |  11 --
 2 files changed, 379 insertions(+), 22 deletions(-)

diff --git a/src/providers/ad/ad_id.c b/src/providers/ad/ad_id.c
index a47aa4f75ab348b0f4597fea264d770b5abe3184..e3302c15774ab1c24b16cefc274313e447b31e5c 100644
--- a/src/providers/ad/ad_id.c
+++ b/src/providers/ad/ad_id.c
@@ -420,10 +420,13 @@ struct ad_enumeration_state {
     struct tevent_context *ev;
 
     struct sdap_domain *sdom;
+    struct sdap_domain *sditer;
 };
 
 static void ad_enumeration_conn_done(struct tevent_req *subreq);
 static void ad_enumeration_master_done(struct tevent_req *subreq);
+static errno_t ad_enum_sdom(struct tevent_req *req, struct sdap_domain *sd,
+                            struct ad_id_ctx *id_ctx);
 static void ad_enumeration_done(struct tevent_req *subreq);
 
 struct tevent_req *
@@ -452,6 +455,7 @@ ad_enumeration_send(TALLOC_CTX *mem_ctx,
     state->ectx = ectx;
     state->ev = ev;
     state->sdom = ectx->sdom;
+    state->sditer = state->sdom;
     state->id_ctx = talloc_get_type(ectx->pvt, struct ad_id_ctx);
 
     state->sdap_op = sdap_id_op_create(state,
@@ -526,7 +530,6 @@ ad_enumeration_master_done(struct tevent_req *subreq)
     char *flat_name;
     char *master_sid;
     char *forest;
-    struct sdap_id_conn_ctx *user_conn;
 
     ret = ad_master_domain_recv(subreq, state,
                                 &flat_name, &master_sid, &forest);
@@ -545,32 +548,57 @@ ad_enumeration_master_done(struct tevent_req *subreq)
         return;
     }
 
-    if (dp_opt_get_bool(state->id_ctx->ad_options->basic, AD_ENABLE_GC)) {
-        user_conn = state->id_ctx->gc_ctx;
+    ret = ad_enum_sdom(req, state->sdom, state->id_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+                ("Could not enumerate domain %s\n", state->sdom->dom->name));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    /* Execution will resume in ad_enumeration_done */
+}
+
+static errno_t
+ad_enum_sdom(struct tevent_req *req,
+             struct sdap_domain *sd,
+             struct ad_id_ctx *id_ctx)
+{
+    struct sdap_id_conn_ctx *user_conn;
+    struct tevent_req *subreq;
+    struct ad_enumeration_state *state = tevent_req_data(req,
+                                                struct ad_enumeration_state);
+
+    if (dp_opt_get_bool(id_ctx->ad_options->basic, AD_ENABLE_GC)) {
+        user_conn = id_ctx->gc_ctx;
     } else {
-        user_conn = state->id_ctx->ldap_ctx;
+        user_conn = id_ctx->ldap_ctx;
     }
 
     /* Groups are searched for in LDAP, users in GC. Services (if present,
      * which is unlikely in AD) from LDAP as well
      */
     subreq = sdap_dom_enum_ex_send(state, state->ev,
-                                   state->id_ctx->sdap_id_ctx,
-                                   state->sdom,
-                                   user_conn,                /* Users    */
-                                   state->id_ctx->ldap_ctx,  /* Groups   */
-                                   state->id_ctx->ldap_ctx); /* Services */
+                                   id_ctx->sdap_id_ctx,
+                                   sd,
+                                   user_conn,         /* Users    */
+                                   id_ctx->ldap_ctx,  /* Groups   */
+                                   id_ctx->ldap_ctx); /* Services */
     if (subreq == NULL) {
         /* The ptask API will reschedule the enumeration on its own on
          * failure */
         DEBUG(SSSDBG_OP_FAILURE,
               ("Failed to schedule enumeration, retrying later!\n"));
-        tevent_req_error(req, ENOMEM);
-        return;
+        return ENOMEM;
     }
     tevent_req_set_callback(subreq, ad_enumeration_done, req);
+
+    return EOK;
 }
 
+static errno_t ad_enum_cross_dom_members(struct sdap_options *opts,
+                                         struct sss_domain_info *dom);
+
 static void
 ad_enumeration_done(struct tevent_req *subreq)
 {
@@ -579,6 +607,7 @@ ad_enumeration_done(struct tevent_req *subreq)
                                                       struct tevent_req);
     struct ad_enumeration_state *state = tevent_req_data(req,
                                                 struct ad_enumeration_state);
+    struct ad_id_ctx *subdom_id_ctx;
 
     ret = sdap_dom_enum_ex_recv(subreq);
     talloc_zfree(subreq);
@@ -589,9 +618,348 @@ ad_enumeration_done(struct tevent_req *subreq)
         return;
     }
 
+    state->sditer = state->sditer->next;
+    if (state->sditer != NULL) {
+        subdom_id_ctx = talloc_get_type(state->sdom->pvt, struct ad_id_ctx);
+        if (subdom_id_ctx == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot retrieve subdomain ad_id_ctx!\n"));
+            tevent_req_error(req, EFAULT);
+            return;
+        }
+
+        ret = ad_enum_sdom(req, state->sditer, state->sditer->pvt);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, ("Could not enumerate domain %s\n",
+                  state->sditer->dom->name));
+            tevent_req_error(req, ret);
+            return;
+        }
+
+        /* Execution will resume in ad_enumeration_done */
+        return;
+    }
+
+    /* No more subdomains to enumerate. Check if we need to fixup
+     * cross-domain membership
+     */
+    if (state->sditer != state->sdom) {
+        /* We did enumerate at least one subdomain. Walk the subdomains
+         * and fixup members for each of them
+         */
+        for (state->sditer = state->sdom;
+             state->sditer;
+             state->sditer = state->sditer->next) {
+            ret = ad_enum_cross_dom_members(state->id_ctx->ad_options->id,
+                                            state->sditer->dom);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_MINOR_FAILURE, ("Could not check cross-domain "
+                      "memberships for %s, group memberships might be "
+                      "incomplete!\n", state->sdom->dom->name));
+                continue;
+            }
+        }
+    }
+
     tevent_req_done(req);
 }
 
+static errno_t ad_group_extra_members(TALLOC_CTX *mem_ctx,
+                                      const struct ldb_message *group,
+                                      struct sss_domain_info *dom,
+                                      char ***_group_only);
+static errno_t ad_group_add_member(struct sdap_options *opts,
+                                   struct sss_domain_info *group_domain,
+                                   struct ldb_dn *group_dn,
+                                   const char *member);
+
+static errno_t
+ad_enum_cross_dom_members(struct sdap_options *opts,
+                          struct sss_domain_info *dom)
+{
+    errno_t ret;
+    errno_t sret;
+    char *filter;
+    TALLOC_CTX *tmp_ctx;
+    const char *attrs[] = {
+            SYSDB_NAME,
+            SYSDB_MEMBER,
+            SYSDB_ORIG_MEMBER,
+            NULL
+    };
+    size_t count, i, mi;
+    struct ldb_message **msgs;
+    bool in_transaction = false;
+    char **group_only;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    ret = sysdb_transaction_start(dom->sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to start transaction\n"));
+        goto done;
+    }
+    in_transaction = true;
+
+    filter = talloc_asprintf(tmp_ctx, "(%s=*)", SYSDB_NAME);
+    if (filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sysdb_search_groups(tmp_ctx, dom->sysdb, dom,
+                              filter, attrs, &count, &msgs);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    for (i = 0; i < count; i++) {
+        ret = ad_group_extra_members(tmp_ctx, msgs[i], dom, &group_only);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, ("Failed to check extra members\n"));
+        } else if (group_only == NULL) {
+            DEBUG(SSSDBG_TRACE_INTERNAL, ("No extra members\n"));
+            continue;
+        }
+
+        /* Group has extra members */
+        for (mi = 0; group_only[mi]; mi++) {
+            ret = ad_group_add_member(opts, dom, msgs[i]->dn, group_only[mi]);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_MINOR_FAILURE, ("Failed to add [%s]: %s\n",
+                      group_only[mi], strerror(ret)));
+                continue;
+            }
+        }
+
+        talloc_zfree(group_only);
+    }
+
+    ret = sysdb_transaction_commit(dom->sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to commit transaction\n"));
+        goto done;
+    }
+    in_transaction = false;
+
+    ret = EOK;
+done:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(dom->sysdb);
+        if (sret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Could not cancel transaction\n"));
+        }
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom,
+                             struct ldb_dn *dn, char ***_odn_list);
+
+static errno_t
+ad_group_extra_members(TALLOC_CTX *mem_ctx, const struct ldb_message *group,
+                       struct sss_domain_info *dom, char ***_group_only)
+{
+    TALLOC_CTX *tmp_ctx;
+    struct ldb_message_element *m, *om;
+    const char *name;
+    errno_t ret;
+    char **sysdb_odn_list;
+    const char **group_odn_list;
+    char **group_only = NULL;
+
+    if (_group_only == NULL) return EINVAL;
+    *_group_only = NULL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    om = ldb_msg_find_element(group, SYSDB_ORIG_MEMBER);
+    m = ldb_msg_find_element(group, SYSDB_MEMBER);
+    name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL);
+    if (name == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, ("A group with no name!\n"));
+        ret = EFAULT;
+        goto done;
+    }
+
+    if (om == NULL || om->num_values == 0) {
+        DEBUG(SSSDBG_TRACE_FUNC, ("Group %s has no original members\n", name));
+        ret = EOK;
+        goto done;
+    }
+
+    if (m == NULL || (m->num_values < om->num_values)) {
+        DEBUG(SSSDBG_TRACE_FUNC,
+              ("Group %s has %d members but %d original members\n",
+               name, m ? m->num_values : 0, om->num_values));
+
+        /* Get the list of originalDN attributes that are already
+         * linked to the group
+         */
+        ret = ad_group_stored_orig_members(tmp_ctx, dom, group->dn,
+                                           &sysdb_odn_list);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  ("Could not retrieve list of original members for %s\n",
+                  name));
+            goto done;
+        }
+
+        /* Get the list of original DN attributes the group had in AD */
+        group_odn_list = sss_ldb_el_to_string_list(tmp_ctx, om);
+        if (group_odn_list == NULL) {
+            ret = EFAULT;
+            goto done;
+        }
+
+        /* Compare the two lists */
+        ret = diff_string_lists(tmp_ctx, discard_const(group_odn_list),
+                                sysdb_odn_list, &group_only, NULL, NULL);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  ("Could not compare lists of members for %s\n", name));
+            goto done;
+        }
+    }
+
+    ret = EOK;
+    *_group_only = talloc_steal(mem_ctx, group_only);
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+ad_group_stored_orig_members(TALLOC_CTX *mem_ctx, struct sss_domain_info *dom,
+                             struct ldb_dn *dn, char ***_odn_list)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    size_t m_count, i;
+    struct ldb_message **members;
+    const char *attrs[] = {
+            SYSDB_NAME,
+            SYSDB_ORIG_DN,
+            NULL
+    };
+    char **odn_list;
+    const char *odn;
+    size_t oi;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    /* Get all entries member element points to */
+    ret = sysdb_asq_search(tmp_ctx, dom->sysdb, dn, NULL, SYSDB_MEMBER,
+                           attrs, &m_count, &members);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    odn_list = talloc_zero_array(tmp_ctx, char *, m_count + 1);
+    if (odn_list == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Get a list of their original DNs */
+    oi = 0;
+    for (i = 0; i < m_count; i++) {
+        odn = ldb_msg_find_attr_as_string(members[i], SYSDB_ORIG_DN, NULL);
+        if (odn == NULL) {
+            continue;
+        }
+
+        odn_list[oi] = talloc_strdup(odn_list, odn);
+        if (odn_list[oi] == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+        oi++;
+        DEBUG(SSSDBG_TRACE_INTERNAL, ("Member %s already in sysdb\n", odn));
+    }
+
+    ret = EOK;
+    *_odn_list = talloc_steal(mem_ctx, odn_list);
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+ad_group_add_member(struct sdap_options *opts,
+                    struct sss_domain_info *group_domain,
+                    struct ldb_dn *group_dn,
+                    const char *member)
+{
+    struct sdap_domain *sd;
+    struct ldb_dn *base_dn;
+    TALLOC_CTX *tmp_ctx;
+    errno_t ret;
+    const char *mem_filter;
+    size_t msgs_count;
+    struct ldb_message **msgs;
+
+    /* This member would be from a different domain */
+    sd = sdap_domain_get_by_dn(opts, member);
+    if (sd == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE, ("No matching domain for %s\n", member));
+        return ENOENT;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) return ENOMEM;
+
+    mem_filter = talloc_asprintf(tmp_ctx, "(%s=%s)",
+                                 SYSDB_ORIG_DN, member);
+    if (mem_filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    base_dn = sysdb_domain_dn(sd->dom->sysdb, tmp_ctx, sd->dom);
+    if (base_dn == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sysdb_search_entry(tmp_ctx, sd->dom->sysdb, base_dn,
+                             LDB_SCOPE_SUBTREE, mem_filter, NULL,
+                             &msgs_count, &msgs);
+    if (ret == ENOENT) {
+        DEBUG(SSSDBG_TRACE_FUNC, ("No member [%s] in sysdb\n", member));
+        ret = EOK;
+        goto done;
+    } else if (ret != EOK) {
+        goto done;
+    }
+    DEBUG(SSSDBG_TRACE_INTERNAL, ("[%s] found in sysdb\n", member));
+
+    if (msgs_count != 1) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+               ("Search by orig DN returned %zd results!\n", msgs_count));
+        ret = EFAULT;
+        goto done;
+    }
+
+    ret = sysdb_mod_group_member(group_domain->sysdb, msgs[0]->dn,
+                                 group_dn, SYSDB_MOD_ADD);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, ("Could not add [%s] as a member of [%s]\n",
+              ldb_dn_get_linearized(msgs[0]->dn),
+              ldb_dn_get_linearized(group_dn)));
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
 errno_t
 ad_enumeration_recv(struct tevent_req *req)
 {
diff --git a/src/providers/ad/ad_subdomains.c b/src/providers/ad/ad_subdomains.c
index e7871cc32407893948fe1b2803258d68c70889c1..0d9652b5c615add47958cfdc61eba862a332ae4d 100644
--- a/src/providers/ad/ad_subdomains.c
+++ b/src/providers/ad/ad_subdomains.c
@@ -177,17 +177,6 @@ ad_subdom_ad_ctx_new(struct be_ctx *be_ctx,
         return EFAULT;
     }
 
-    ret = sdap_id_setup_tasks(be_ctx,
-                              ad_id_ctx->sdap_id_ctx,
-                              sdom,
-                              ldap_enumeration_send,
-                              ldap_enumeration_recv,
-                              ad_id_ctx->sdap_id_ctx);
-    if (ret != EOK) {
-        talloc_free(ad_options);
-        return ret;
-    }
-
     /* Set up the ID mapping object */
     ad_id_ctx->sdap_id_ctx->opts->idmap_ctx =
         id_ctx->sdap_id_ctx->opts->idmap_ctx;
-- 
1.8.4.2