On 09/21/2012 05:41 AM, Jakub Hrozek wrote:
> On Thu, Sep 20, 2012 at 09:34:37AM -0400, Stephen Gallagher wrote:
>> -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
>> 
>> * [PATCH 1/2] AD: Detect domain controller compatibility version
>>  This patch allows us to read the domain controller version from
>>  the RootDSE, if it exists. This will be used by the next patch
>> to determine if the SSSD can use tokenGroups for lookups.
>> 
>> * [PATCH 2/2] AD: Optimize initgroups lookups with tokenGroups
>> If we are doing ID-mapped lookups, we can take advantage of the 
>> AD-specific option "tokenGroups", which allows us to retrieve a 
>> pre-flattened list of all the group SIDs for which this user is a
>> member. From this, we can convert them to GIDs and conclude the
>> initgroups() lookup with only two LDAP queries (the search for
>> the user and the base search for the user's tokengroups; the 
>> latter must be performed as a base search due to restrictions 
>> imposed on the tokenGroups attribute by AD).
>> 
>> When we save these groups to the sysdb, if the group is being 
>> seen for the first time it will be temporarily given the name of 
>> the SID representing it. Later, when we look it up by name or 
>> GID, this group will be replaced with the real one.
>> 
> 
> I only have one question:
> 
> +    /* 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));
> 
> Should we just return success here? Does a missing togenGroup
> imply a user that does not belong to any group?
> 
> +        } +        DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not read
>  tokenGroups attribute: [%s]\n", + strerror(ret))); +        goto 
> done; +    }
> 

You're right, I meant to do more with that IF block and forgot about
it (though in reality, that's a branch we should never take since all
users are members of at least the built-in "Users" group that we later
filter out).

Just returning success here isn't sufficient, though. We need to make
sure that the sysdb matches. The attached patch creates a new
ldb_message_element with zero members that will short-circuit the
group-processing loop below and advance on to where we correct the
group memberships.

Additionally, while fixing this I realized that I had forgotten to use
the _recv() function in sdap_async_initgroups.c. It had been working
by lucky coincidence because the _recv() function it was calling
instead happened to have the exact same implementation (checking only
for errors, not requiring a specific state variable). This is now also
corrected.

Thanks for the review!


>> Fixes https://fedorahosted.org/sssd/ticket/1355
> 
> I'm going to move the ticket to 1.9.0. This enhancement is not 
> really intrusive and provides performance gain so big that I don't 
> think we should wait. 
> _______________________________________________ sssd-devel mailing 
> list sssd-devel@lists.fedorahosted.org 
> https://lists.fedorahosted.org/mailman/listinfo/sssd-devel
> 



>From 73dc74725686292e54470313d082ef783f96ccc5 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgall...@redhat.com>
Date: Tue, 18 Sep 2012 14:24:38 -0400
Subject: [PATCH 1/2] AD: Detect domain controller compatibility version

---
 src/providers/ldap/sdap.c       | 30 ++++++++++++++++++++++++++++++
 src/providers/ldap/sdap.h       | 13 +++++++++++++
 src/providers/ldap/sdap_async.c |  1 +
 3 files changed, 44 insertions(+)

diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
index 65fbc8c1e79802bd5355a083434e5c88100b8c5a..65824fc11796fae2f4ffa73e0bb46634f5d634d7 100644
--- a/src/providers/ldap/sdap.c
+++ b/src/providers/ldap/sdap.c
@@ -897,6 +897,7 @@ int sdap_get_server_opts_from_rootdse(TALLOC_CTX *memctx,
     char *endptr = NULL;
     int ret;
     int i;
+    uint32_t dc_level;
 
     so = talloc_zero(memctx, struct sdap_server_opts);
     if (!so) {
@@ -968,6 +969,35 @@ int sdap_get_server_opts_from_rootdse(TALLOC_CTX *memctx,
                 }
             }
         }
+
+        /* Detect Active Directory version if available */
+        ret = sysdb_attrs_get_uint32_t(rootdse,
+                                       SDAP_ROOTDSE_ATTR_AD_VERSION,
+                                       &dc_level);
+        if (ret == EOK) {
+            /* Validate that the DC level matches an expected value */
+            switch(dc_level) {
+            case DS_BEHAVIOR_WIN2000:
+            case DS_BEHAVIOR_WIN2003:
+            case DS_BEHAVIOR_WIN2008:
+            case DS_BEHAVIOR_WIN2008R2:
+            case DS_BEHAVIOR_WIN2012:
+                opts->dc_functional_level = dc_level;
+                DEBUG(SSSDBG_CONF_SETTINGS,
+                      ("Setting AD compatibility level to [%d]\n",
+                       opts->dc_functional_level));
+                break;
+            default:
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      ("Received invalid value for AD compatibility level. "
+                       "Continuing without AD performance enhancements\n"));
+            }
+        } else if (ret != ENOENT) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Error detecting Active Directory compatibility level "
+                   "(%s). Continuing without AD performance enhancements\n",
+                   strerror(ret)));
+        }
     }
 
     if (!last_usn_name) {
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 01c33e42117281dbebebfdd8240d468d657ae522..d844ad6369dc19c82ea761afae746f9d4bd0ce82 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -129,6 +129,7 @@ struct sdap_ppolicy_data {
 
 #define SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS "namingContexts"
 #define SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT "defaultNamingContext"
+#define SDAP_ROOTDSE_ATTR_AD_VERSION "domainControllerFunctionality"
 
 #define SDAP_IPA_USN "entryUSN"
 #define SDAP_IPA_LAST_USN "lastUSN"
@@ -364,6 +365,17 @@ struct sdap_search_base {
     const char *filter;
 };
 
+/* Values from
+ * http://msdn.microsoft.com/en-us/library/cc223272%28v=prot.13%29.aspx
+ */
+enum dc_functional_level {
+    DS_BEHAVIOR_WIN2000 = 0,
+    DS_BEHAVIOR_WIN2003 = 2,
+    DS_BEHAVIOR_WIN2008 = 3,
+    DS_BEHAVIOR_WIN2008R2 = 4,
+    DS_BEHAVIOR_WIN2012 = 5
+};
+
 struct sdap_options {
     struct dp_option *basic;
     struct sdap_attr_map *gen_map;
@@ -397,6 +409,7 @@ struct sdap_options {
     struct sdap_search_base **autofs_search_bases;
 
     bool support_matching_rule;
+    enum dc_functional_level dc_functional_level;
 };
 
 struct sdap_server_opts {
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
index 42c8dd6805f4321b131ab799384bb388f2eadfb2..e0440625d279fbbaa1cc2e6343b73f5a247371f7 100644
--- a/src/providers/ldap/sdap_async.c
+++ b/src/providers/ldap/sdap_async.c
@@ -855,6 +855,7 @@ struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx,
             "supportedFeatures",
             "supportedLDAPVersion",
             "supportedSASLMechanisms",
+            SDAP_ROOTDSE_ATTR_AD_VERSION,
             SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT,
             SDAP_IPA_LAST_USN, SDAP_AD_LAST_USN,
             NULL
-- 
1.7.12



>From b2cf36d61b4cc01be32aa9204a787945a51eca7c Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgall...@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    |  24 ++-
 src/providers/ldap/sdap_async_initgroups_ad.c | 277 ++++++++++++++++++++++++++
 3 files changed, 313 insertions(+), 4 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..71b4536b359c4cc0183e2733ae8d8074ca30c0e3 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.
              */
@@ -2815,7 +2825,13 @@ static void sdap_get_initgr_done(struct tevent_req *subreq)
 
     case SDAP_SCHEMA_RFC2307BIS:
     case SDAP_SCHEMA_AD:
-        if (dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE_INITGROUPS)) {
+        if (use_id_mapping
+                && state->opts->dc_functional_level >= DS_BEHAVIOR_WIN2008) {
+            ret = sdap_get_ad_tokengroups_initgroups_recv(subreq);
+        }
+        else if (state->opts->support_matching_rule
+                && dp_opt_get_bool(state->opts->basic,
+                                   SDAP_AD_MATCHING_RULE_INITGROUPS)) {
             ret = sdap_get_ad_match_rule_initgroups_recv(subreq);
         } else {
             ret = sdap_initgr_rfc2307bis_recv(subreq);
diff --git a/src/providers/ldap/sdap_async_initgroups_ad.c b/src/providers/ldap/sdap_async_initgroups_ad.c
index f74729f47c5bf31f326aba1d91765ca4d4846f40..1333e3080949dc138330b12c8b991a9ea3561b2b 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,278 @@ 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));
+            /* No groups in LDAP. We need to ensure that the
+             * sysdb matches.
+             */
+            el = talloc_zero(tmp_ctx, struct ldb_message_element);
+            if (!el) {
+                ret = ENOMEM;
+                goto done;
+            }
+            el->num_values = 0;
+
+            /* This will skip the group-processing loop below
+             * and proceed to removing any sysdb groups.
+             */
+        } else {
+            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



_______________________________________________
sssd-devel mailing list
sssd-devel@lists.fedorahosted.org
https://lists.fedorahosted.org/mailman/listinfo/sssd-devel

Reply via email to