>From 3fe766222a735bb2d62f86378cbba67ded2e07d5 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Tue, 18 Sep 2012 16:51:15 -0400
Subject: [PATCH 2/2] AD: Optimize initgroups lookups with tokenGroups

https://fedorahosted.org/sssd/ticket/1355
---
 src/providers/ldap/sdap_async.h               |  16 ++
 src/providers/ldap/sdap_async_initgroups.c    |  16 +-
 src/providers/ldap/sdap_async_initgroups_ad.c | 263 ++++++++++++++++++++++++++
 3 files changed, 292 insertions(+), 3 deletions(-)

diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index d024e30a92893a84d9e8280c1469bb40ecaff045..8c16d94e6486336e92b6112cd8b5a2dff4c97957 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -31,6 +31,8 @@
 #include "providers/ldap/sdap_id_op.h"
 #include "providers/fail_over.h"
 
+#define AD_TOKENGROUPS_ATTR "tokenGroups"
+
 struct tevent_req *sdap_connect_send(TALLOC_CTX *memctx,
                                      struct tevent_context *ev,
                                      struct sdap_options *opts,
@@ -275,4 +277,18 @@ sdap_get_ad_match_rule_initgroups_send(TALLOC_CTX *mem_ctx,
 errno_t
 sdap_get_ad_match_rule_initgroups_recv(struct tevent_req *req);
 
+
+struct tevent_req *
+sdap_get_ad_tokengroups_initgroups_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct sdap_options *opts,
+                                        struct sysdb_ctx *sysdb,
+                                        struct sdap_handle *sh,
+                                        const char *name,
+                                        const char *orig_dn,
+                                        int timeout);
+
+errno_t
+sdap_get_ad_tokengroups_initgroups_recv(struct tevent_req *req);
+
 #endif /* _SDAP_ASYNC_H_ */
diff --git a/src/providers/ldap/sdap_async_initgroups.c b/src/providers/ldap/sdap_async_initgroups.c
index d55f661ff4d83de82fc9c920f17adc68b9594fea..7a25cc810a224a6c3eb1331edd4c8660b0b0e9c3 100644
--- a/src/providers/ldap/sdap_async_initgroups.c
+++ b/src/providers/ldap/sdap_async_initgroups.c
@@ -2642,6 +2642,8 @@ static void sdap_get_initgr_user(struct tevent_req *subreq)
     const char *orig_dn;
     const char *cname;
     bool in_transaction = false;
+    bool use_id_mapping =
+            dp_opt_get_bool(state->opts->basic, SDAP_ID_MAPPING);
 
     DEBUG(9, ("Receiving info for the user\n"));
 
@@ -2731,9 +2733,17 @@ static void sdap_get_initgr_user(struct tevent_req *subreq)
             return;
         }
 
-        if (state->opts->support_matching_rule
-                && dp_opt_get_bool(state->opts->basic,
-                                   SDAP_AD_MATCHING_RULE_INITGROUPS)) {
+        if (use_id_mapping
+                && state->opts->dc_functional_level >= DS_BEHAVIOR_WIN2008) {
+            /* Take advantage of AD's tokenGroups mechanism to look up all
+             * parent groups in a single request.
+             */
+            subreq = sdap_get_ad_tokengroups_initgroups_send(
+                    state, state->ev, state->opts, state->sysdb,
+                    state->sh, cname, orig_dn, state->timeout);
+        } else if (state->opts->support_matching_rule
+                    && dp_opt_get_bool(state->opts->basic,
+                                       SDAP_AD_MATCHING_RULE_INITGROUPS)) {
             /* Take advantage of AD's extensibleMatch filter to look up
              * all parent groups in a single request.
              */
diff --git a/src/providers/ldap/sdap_async_initgroups_ad.c b/src/providers/ldap/sdap_async_initgroups_ad.c
index f74729f47c5bf31f326aba1d91765ca4d4846f40..afa25b7da6fff7ec92a094abd06ef97b6bb9519f 100644
--- a/src/providers/ldap/sdap_async_initgroups_ad.c
+++ b/src/providers/ldap/sdap_async_initgroups_ad.c
@@ -24,6 +24,8 @@
 #include "providers/ldap/sdap_async.h"
 #include "providers/ldap/ldap_common.h"
 #include "providers/ldap/sdap_async_private.h"
+#include "providers/ldap/sdap_idmap.h"
+#include "lib/idmap/sss_idmap.h"
 
 struct sdap_ad_match_rule_initgr_state {
     struct tevent_context *ev;
@@ -290,3 +292,264 @@ sdap_get_ad_match_rule_initgroups_recv(struct tevent_req *req)
     TEVENT_REQ_RETURN_ON_ERROR(req);
     return EOK;
 }
+
+struct sdap_ad_tokengroups_initgr_state {
+    struct tevent_context *ev;
+    struct sdap_options *opts;
+    struct sysdb_ctx *sysdb;
+    struct sdap_handle *sh;
+    const char *username;
+};
+
+static void
+sdap_get_ad_tokengroups_initgroups_lookup_done(struct tevent_req *req);
+
+struct tevent_req *
+sdap_get_ad_tokengroups_initgroups_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        struct sdap_options *opts,
+                                        struct sysdb_ctx *sysdb,
+                                        struct sdap_handle *sh,
+                                        const char *name,
+                                        const char *orig_dn,
+                                        int timeout)
+{
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct sdap_ad_tokengroups_initgr_state *state;
+    const char *attrs[] = {AD_TOKENGROUPS_ATTR, NULL};
+
+    req = tevent_req_create(mem_ctx, &state,
+                            struct sdap_ad_tokengroups_initgr_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->opts = opts;
+    state->sysdb = sysdb;
+    state->sh = sh;
+    state->username = name;
+
+    subreq = sdap_get_generic_send(
+            state, state->ev, state->opts, state->sh,
+            orig_dn, LDAP_SCOPE_BASE, NULL, attrs,
+            NULL, 0, timeout, false);
+    if (!subreq) {
+        tevent_req_error(req, ENOMEM);
+        tevent_req_post(req, ev);
+        return req;
+    }
+
+    tevent_req_set_callback(subreq,
+                            sdap_get_ad_tokengroups_initgroups_lookup_done,
+                            req);
+    return req;
+}
+
+static void
+sdap_get_ad_tokengroups_initgroups_lookup_done(struct tevent_req *subreq)
+{
+    errno_t ret, sret;
+    enum idmap_error_code err;
+    size_t user_count, group_count, i;
+    TALLOC_CTX *tmp_ctx;
+    bool in_transaction = false;
+    char *sid_str;
+    gid_t gid;
+    time_t now;
+    struct sysdb_attrs **users;
+    struct ldb_message_element *el;
+    struct ldb_message *msg;
+    char **ldap_grouplist;
+    char **sysdb_grouplist;
+    char **add_groups;
+    char **del_groups;
+    const char *attrs[] = { SYSDB_NAME, NULL };
+    const char *group_name;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_ad_tokengroups_initgr_state *state =
+            tevent_req_data(req, struct sdap_ad_tokengroups_initgr_state);
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sdap_get_generic_recv(subreq, tmp_ctx, &user_count, &users);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("LDAP search failed: [%s]\n", strerror(ret)));
+        goto done;
+    }
+
+    if (user_count != 1) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("More than one result on a base search!\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    /* Get the list of group SIDs */
+    ret = sysdb_attrs_get_el_ext(users[0], AD_TOKENGROUPS_ATTR,
+                                 false, &el);
+    if (ret != EOK) {
+        if (ret == ENOENT) {
+            DEBUG(SSSDBG_TRACE_LIBS,
+                  ("No tokenGroups entries for [%s]\n",
+                   state->username));
+        }
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not read tokenGroups attribute: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    /* Process the groups */
+    now = time(NULL);
+
+    ret = sysdb_transaction_start(state->sysdb);
+    if (ret != EOK) goto done;
+    in_transaction = true;
+
+    ldap_grouplist = talloc_array(tmp_ctx, char *, el->num_values + 1);
+    if (!ldap_grouplist) {
+        ret = ENOMEM;
+        goto done;
+    }
+    group_count = 0;
+
+    for (i = 0; i < el->num_values; i++) {
+        /* Get the SID and convert it to a GID */
+
+        err = sss_idmap_bin_sid_to_sid(state->opts->idmap_ctx->map,
+                                        el->values[i].data,
+                                        el->values[i].length,
+                                        &sid_str);
+        if (err != IDMAP_SUCCESS) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Could not convert binary SID to string: [%s]. Skipping\n",
+                   idmap_error_string(err)));
+            continue;
+        }
+        DEBUG(SSSDBG_TRACE_LIBS,
+              ("Processing membership SID [%s]\n",
+               sid_str));
+        ret = sdap_idmap_sid_to_unix(state->opts->idmap_ctx, sid_str,
+                                     &gid);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Could not convert SID to GID: [%s]. Skipping\n",
+                   strerror(ret)));
+            continue;
+        }
+
+        DEBUG(SSSDBG_TRACE_LIBS,
+              ("Processing membership GID [%lu]\n",
+               gid));
+
+        /* Check whether this GID already exists in the sysdb */
+        ret = sysdb_search_group_by_gid(tmp_ctx, state->sysdb,
+                                        gid, attrs, &msg);
+        if (ret == EOK) {
+            group_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+            if (!group_name) {
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      ("Could not retrieve group name from sysdb\n"));
+                ret = EINVAL;
+                goto done;
+            }
+        } else if (ret == ENOENT) {
+            /* This is a new group. For now, we will store it
+             * under the name of its SID. When a direct lookup of
+             * the group or its GID occurs, it will replace this
+             * temporary entry.
+             */
+            group_name = sid_str;
+            ret = sysdb_add_incomplete_group(state->sysdb, group_name,
+                                             gid, NULL, false, now);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      ("Could not create incomplete group: [%s]\n",
+                       strerror(ret)));
+                goto done;
+            }
+        }
+
+        ldap_grouplist[group_count] =
+                talloc_strdup(ldap_grouplist, group_name);
+        if (!ldap_grouplist[group_count]) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        group_count++;
+    }
+    ldap_grouplist[group_count] = NULL;
+
+    /* Get the current sysdb group list for this user
+     * so we can update it.
+     */
+    ret = get_sysdb_grouplist(state, state->sysdb, state->username,
+                              &sysdb_grouplist);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not get the list of groups for [%s] in the sysdb: "
+               "[%s]\n",
+               state->username, strerror(ret)));
+        goto done;
+    }
+
+    /* Find the differences between the sysdb and LDAP lists
+     * Groups in the sysdb only must be removed.
+     */
+    ret = diff_string_lists(tmp_ctx, ldap_grouplist, sysdb_grouplist,
+                            &add_groups, &del_groups, NULL);
+    if (ret != EOK) goto done;
+
+    DEBUG(SSSDBG_TRACE_LIBS,
+          ("Updating memberships for [%s]\n", state->username));
+    ret = sysdb_update_members(state->sysdb, state->username,
+                               SYSDB_MEMBER_USER,
+                               (const char *const *) add_groups,
+                               (const char *const *) del_groups);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Membership update failed [%d]: %s\n",
+               ret, strerror(ret)));
+        goto done;
+    }
+
+    ret = sysdb_transaction_commit(state->sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Could not commit transaction! [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+    in_transaction = false;
+
+done:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(state->sysdb);
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("Could not cancel transaction! [%s]\n",
+               sret));
+    }
+
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    talloc_free(tmp_ctx);
+    return;
+}
+
+errno_t
+sdap_get_ad_tokengroups_initgroups_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
-- 
1.7.12

