This is the second set of patches. These aren't quite ready for a
complete review. They are functional, but they need some discussion.

These patches attempt to implement
https://fedorahosted.org/sssd/ticket/1367.

For details on the "magic" filters, see
http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475%
28v=vs.85%29.aspx (http://goo.gl/Czjlf if that doesn't come through
email well).



Patch 0001: Add new option "ldap_use_matching_rule_in_chain". This patch
is very incomplete. It only adds the new option. It is missing
SSSDConfig API entries and manpages. It is included because it's
necessary to test the other two patches.



Patch 0002: Add support for the chain matching filter when performing
group lookups. This is the contentious patch. It works, but it does not
have the performance gain I might have expected. I need to do some
re-testing on beefier hardware, but this approach has two major
performance downsides compared to the existing approach.

1) The existing approach takes advantage of the ASQ lookup, which (by
accident or design on Microsoft's part) ignores the limits on page size
per lookup. As a result, we can do a single lookup for all of the users
at a particular nesting level. With the match filter, we have to use the
paging control and request additional pages (meaning additional
round-trips). Net result is that the existing approach is faster for the
common case (only one or two nestings).

2) This is the bigger issue, though it may be a flawed test (I'll
explain in a moment). In addition to multiple round-trips to the server,
each round-trip in my tests was taking between 2x and 3x the time to
return. I suspect that this may be due in part to the fact that my
target server was a fairly wimpy CPU-and-memory VM, but it does reveal
that lookups using this filter against very large groups causes a
significantly increased strain on the AD server.

With both of those downsides in mind, I'm considering scrapping this
portion of the solution. Additional input is welcome.



Patch 0003: Add support for the chain matching filter when performing
initgroups lookups. This patch is much more performant (due in large
part to the fact that in the average case, the set of groups a user
belongs to will almost always fall within a single paged search). With
this patch, I consistently see 'time id -G <username>' reduced by 50%.
That makes this patch a clear performance victory in my mind.
From be9fa3e05f0ded818e5818c765fb9447b0431828 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <[email protected]>
Date: Sun, 10 Jun 2012 13:06:57 -0400
Subject: [PATCH 6/8] WIP: Add ldap_use_matching_rule_in_chain option

---
 src/providers/ipa/ipa_opts.h   |    1 +
 src/providers/ldap/ldap_opts.h |    1 +
 2 files changed, 2 insertions(+)

diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h
index f688f765c988fa79d2ae97fcdfbeee0bd3fa523b..871394b10b6e89322ad5e52dafc4154262567ce9 100644
--- a/src/providers/ipa/ipa_opts.h
+++ b/src/providers/ipa/ipa_opts.h
@@ -120,6 +120,7 @@ struct dp_option ipa_def_ldap_opts[] = {
     { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_use_matching_rule_in_chain", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/ldap/ldap_opts.h b/src/providers/ldap/ldap_opts.h
index 62b0371339c86d0277e6842be389e696936f4ae3..ec03d9a55504b3f3e67ebfd4a7d293ddf7241ce4 100644
--- a/src/providers/ldap/ldap_opts.h
+++ b/src/providers/ldap/ldap_opts.h
@@ -102,6 +102,7 @@ struct dp_option default_basic_opts[] = {
     { "ldap_idmap_autorid_compat", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "ldap_idmap_default_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_idmap_default_domain_sid", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_use_matching_rule_in_chain", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     DP_OPTION_TERMINATOR
 };
 
-- 
1.7.10.2

From 0e4de14987ea8f9ae2622b96382efd67e2a6886d Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <[email protected]>
Date: Sun, 10 Jun 2012 13:07:15 -0400
Subject: [PATCH 7/8] LDAP: Add support for AD chain matching extension in
 group lookups

---
 Makefile.am                               |    1 +
 src/providers/ldap/sdap.h                 |    1 +
 src/providers/ldap/sdap_async.h           |   21 +++
 src/providers/ldap/sdap_async_groups.c    |  159 +++++++++++++++++-
 src/providers/ldap/sdap_async_groups_ad.c |  257 +++++++++++++++++++++++++++++
 5 files changed, 430 insertions(+), 9 deletions(-)
 create mode 100644 src/providers/ldap/sdap_async_groups_ad.c

diff --git a/Makefile.am b/Makefile.am
index a6c2f909846011e81270e3f99e771e3f1c11bedb..0e84da00a570c6f1d3f28c154c49d045ab734cef 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1091,6 +1091,7 @@ libsss_ldap_common_la_SOURCES = \
     src/providers/ldap/sdap_async.c \
     src/providers/ldap/sdap_async_users.c \
     src/providers/ldap/sdap_async_groups.c \
+    src/providers/ldap/sdap_async_groups_ad.c \
     src/providers/ldap/sdap_async_initgroups.c \
     src/providers/ldap/sdap_async_connection.c \
     src/providers/ldap/sdap_async_netgroups.c \
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 90558221a83c82a8166f807c6f55b9155ff322c5..ba807abf5bb5e344f3440ce7bb9495558eff701a 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -217,6 +217,7 @@ enum sdap_basic_opt {
     SDAP_IDMAP_AUTORID_COMPAT,
     SDAP_IDMAP_DEFAULT_DOMAIN,
     SDAP_IDMAP_DEFAULT_DOMAIN_SID,
+    SDAP_AD_MATCHING_RULE,
 
     SDAP_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index 34fb40daed5830aa9b1adae8ad7b62bb2c429bca..ebbf1e8bd5a14732ea24e7a86dfe10b07d0ffabe 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -243,4 +243,25 @@ enum_services_send(TALLOC_CTX *memctx,
 errno_t
 enum_services_recv(struct tevent_req *req);
 
+
+/* OID documented in
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475%28v=vs.85%29.aspx
+ */
+#define SDAP_MATCHING_RULE_IN_CHAIN "1.2.840.113556.1.4.1941"
+
+struct tevent_req *
+sdap_get_ad_match_rule_members_send(TALLOC_CTX *mem_ctx,
+                                    struct tevent_context *ev,
+                                    struct sdap_options *opts,
+                                    struct sdap_handle *sh,
+                                    struct sysdb_attrs *group,
+                                    const char **attrs,
+                                    int timeout);
+
+errno_t
+sdap_get_ad_match_rule_members_recv(struct tevent_req *req,
+                                    TALLOC_CTX *mem_ctx,
+                                    size_t *num_users,
+                                    struct sysdb_attrs ***users);
+
 #endif /* _SDAP_ASYNC_H_ */
diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c
index bf13b305ce15f4be36b05b7b58ed57083f84bd35..621e4c77e47a76b5ef2f65e69cb19980c2ef4e73 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -1352,6 +1352,8 @@ static struct tevent_req *sdap_nested_group_process_send(
         bool enable_deref, uint32_t nesting);
 static void sdap_nested_done(struct tevent_req *req);
 static errno_t sdap_nested_group_process_recv(struct tevent_req *req);
+static void sdap_ad_match_rule_members_process(struct tevent_req *subreq);
+
 static void sdap_get_groups_process(struct tevent_req *subreq)
 {
     struct tevent_req *req =
@@ -1437,10 +1439,15 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
      * for RFC2307bis/FreeIPA/ActiveDirectory
      * We don't need to do this for enumeration,
      * because all groups will be picked up anyway.
+     *
+     * We can also skip this if we're using the
+     * LDAP_MATCHING_RULE_IN_CHAIN available in
+     * AD 2008 and later
      */
     if (!state->enumeration) {
-        if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307) &&
-            (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0)) {
+        if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307)
+                && (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0)
+                && !dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE)) {
 
             /* Prepare hashes for nested user processing */
             ret = sss_hash_create(state, 32, &state->user_hash);
@@ -1505,6 +1512,25 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
     /* We have all of the groups. Save them to the sysdb */
     state->check_count = state->count;
 
+    /* If we're using LDAP_MATCHING_RULE_IN_CHAIN, start a subreq to
+     * retrieve the members so we can save them in a single step.
+     */
+    if (!state->enumeration
+            && (state->opts->schema_type != SDAP_SCHEMA_RFC2307)
+            && dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE)) {
+        subreq = sdap_get_ad_match_rule_members_send(
+                state, state->ev, state->opts, state->sh,
+                state->groups[0], NULL, state->timeout);
+        if (!subreq) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+        tevent_req_set_callback(subreq,
+                                sdap_ad_match_rule_members_process,
+                                req);
+        return;
+    }
+
     ret = sysdb_transaction_start(state->sysdb);
     if (ret != EOK) {
         DEBUG(0, ("Failed to start transaction\n"));
@@ -1588,6 +1614,128 @@ static void sdap_get_groups_done(struct tevent_req *subreq)
     }
 }
 
+static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx,
+                                                struct sysdb_ctx *sysdb,
+                                                struct sdap_options *opts,
+                                                struct sysdb_attrs **users,
+                                                int num_users,
+                                                hash_table_t **_ghosts);
+
+static void sdap_ad_match_rule_members_process(struct tevent_req *subreq)
+{
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_get_groups_state *state = tevent_req_data(req,
+                                            struct sdap_get_groups_state);
+    struct sysdb_attrs **users;
+    struct sysdb_attrs *group = state->groups[0];
+    struct ldb_message_element *member_el;
+    struct ldb_message_element *orig_dn_el;
+    size_t count;
+    size_t i;
+    hash_table_t *ghosts;
+
+    ret = sdap_get_ad_match_rule_members_recv(subreq, state,
+                                              &count, &users);
+    talloc_zfree(subreq);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not retrieve members using AD match rule. [%s]\n",
+               strerror(ret)));
+
+        goto done;
+    }
+
+    /* Save the group and users to the cache */
+
+    /* Truncate the member attribute of the group.
+     * It will be repopulated below, and it may currently
+     * be incomplete anyway, thanks to the range extension.
+     */
+
+    ret = sysdb_attrs_get_el(group, SYSDB_MEMBER, &member_el);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    member_el->num_values = 0;
+    talloc_zfree(member_el->values);
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Figure out which users are already cached in the sysdb and
+     * which ones need to be added as ghost users.
+     */
+    ret = sdap_nested_group_populate_users(tmp_ctx, state->sysdb,
+                                           state->opts, users, count,
+                                           &ghosts);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not determine which users are ghosts: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    /* Add any entries that aren't in the ghost hash table to the
+     * member element of the group. This will get converted to a
+     * native sysdb representation later in sdap_save_groups().
+     */
+
+    /* Add all of the users as members
+     */
+    member_el->values = talloc_zero_array(tmp_ctx, struct ldb_val, count);
+    if (!member_el->values) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Copy the origDN values of the users into the member element */
+    for (i = 0; i < count; i++) {
+        ret = sysdb_attrs_get_el(users[i], SYSDB_ORIG_DN,
+                                 &orig_dn_el);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Missing originalDN for user? Skipping.\n"));
+            continue;
+        }
+
+        /* These values will have the same lifespan, so instead
+         * of copying them, just point at the data.
+         */
+        member_el->values[i].data = orig_dn_el->values[0].data;
+        member_el->values[i].length = orig_dn_el->values[0].length;
+    }
+    member_el->num_values = count;
+
+    /* Now save the group, users and ghosts to the cache */
+    ret = sdap_save_groups(tmp_ctx, state->sysdb, state->dom,
+                           state->opts, state->groups, 1,
+                           false, ghosts, NULL);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not save group to the cache: [%s]\n",
+               strerror(ret)));
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+}
+
 int sdap_get_groups_recv(struct tevent_req *req,
                          TALLOC_CTX *mem_ctx, char **usn_value)
 {
@@ -1603,13 +1751,6 @@ int sdap_get_groups_recv(struct tevent_req *req,
     return EOK;
 }
 
-static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx,
-                                                struct sysdb_ctx *sysdb,
-                                                struct sdap_options *opts,
-                                                struct sysdb_attrs **users,
-                                                int num_users,
-                                                hash_table_t **_ghosts);
-
 static void sdap_nested_done(struct tevent_req *subreq)
 {
     errno_t ret, tret;
diff --git a/src/providers/ldap/sdap_async_groups_ad.c b/src/providers/ldap/sdap_async_groups_ad.c
new file mode 100644
index 0000000000000000000000000000000000000000..2d7addf73500a4018ea10694745934c54846d5c8
--- /dev/null
+++ b/src/providers/ldap/sdap_async_groups_ad.c
@@ -0,0 +1,257 @@
+/*
+    SSSD
+
+    Authors:
+        Stephen Gallagher <[email protected]>
+
+    Copyright (C) 2012 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ldap/ldap_common.h"
+
+struct sdap_ad_match_rule_state {
+    struct tevent_context *ev;
+    struct sdap_handle *sh;
+    const char **attrs;
+
+    struct sdap_options *opts;
+    const char *base_filter;
+    char *filter;
+    int timeout;
+
+    size_t base_iter;
+    struct sdap_search_base **search_bases;
+
+    size_t count;
+    struct sysdb_attrs **users;
+};
+
+static errno_t
+sdap_get_ad_match_rule_members_next_base(struct tevent_req *req);
+static void
+sdap_get_ad_match_rule_members_step(struct tevent_req *subreq);
+
+struct tevent_req *
+sdap_get_ad_match_rule_members_send(TALLOC_CTX *mem_ctx,
+                                    struct tevent_context *ev,
+                                    struct sdap_options *opts,
+                                    struct sdap_handle *sh,
+                                    struct sysdb_attrs *group,
+                                    const char **attrs,
+                                    int timeout)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct sdap_ad_match_rule_state *state;
+    const char *group_dn;
+    char *sanitized_group_dn;
+
+    req = tevent_req_create(mem_ctx, &state, struct sdap_ad_match_rule_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->opts = opts;
+    state->sh = sh;
+    state->timeout = timeout;
+    state->count = 0;
+    state->base_iter = 0;
+    state->search_bases = opts->user_search_bases;
+
+    if (attrs) {
+        state->attrs = attrs;
+    } else {
+        /* Default to requesting all of the user attributes
+         * that we know about.
+         */
+        ret = build_attrs_from_map(state, opts->user_map, SDAP_OPTS_USER,
+                                   NULL, &state->attrs, NULL);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Could not build attribute map: [%s]\n",
+                   strerror(ret)));
+            goto immediate;
+        }
+    }
+
+    /* Get the DN of the group */
+    ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &group_dn);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not retrieve originalDN for group: %s\n",
+               strerror(ret)));
+        goto immediate;
+    }
+
+    /* Sanitize it in case we have special characters in DN */
+    ret = sss_filter_sanitize(state, group_dn, &sanitized_group_dn);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not sanitize group DN: %s\n",
+               strerror(ret)));
+        goto immediate;
+    }
+
+    /* Craft a special filter according to
+     * http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475%28v=vs.85%29.aspx
+     */
+    state->base_filter =
+            talloc_asprintf(state,
+                            "(&(%s:%s:=%s)(objectClass=%s))",
+                            state->opts->user_map[SDAP_AT_USER_MEMBEROF].name,
+                            SDAP_MATCHING_RULE_IN_CHAIN,
+                            sanitized_group_dn,
+                            state->opts->user_map[SDAP_OC_USER].name);
+    talloc_zfree(sanitized_group_dn);
+    if (!state->base_filter) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    /* Start the loop through the search bases to get all of the users */
+    ret = sdap_get_ad_match_rule_members_next_base(req);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("sdap_get_ad_match_rule_members_next_base failed: [%s]\n",
+               strerror(ret)));
+        goto immediate;
+    }
+
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t
+sdap_get_ad_match_rule_members_next_base(struct tevent_req *req)
+{
+    struct tevent_req *subreq;
+    struct sdap_ad_match_rule_state *state;
+
+    state = tevent_req_data(req, struct sdap_ad_match_rule_state);
+
+    talloc_zfree(state->filter);
+    state->filter = sdap_get_id_specific_filter(state,
+                        state->base_filter,
+                        state->search_bases[state->base_iter]->filter);
+    if (!state->filter) {
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Searching for users with base [%s]\n",
+           state->search_bases[state->base_iter]->basedn));
+
+    subreq = sdap_get_generic_send(
+            state, state->ev, state->opts, state->sh,
+            state->search_bases[state->base_iter]->basedn,
+            state->search_bases[state->base_iter]->scope,
+            state->filter, state->attrs,
+            state->opts->user_map, SDAP_OPTS_USER,
+            state->timeout, true);
+    if (!subreq) {
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(subreq, sdap_get_ad_match_rule_members_step, req);
+
+    return EOK;
+}
+
+static void
+sdap_get_ad_match_rule_members_step(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_ad_match_rule_state *state =
+            tevent_req_data(req, struct sdap_ad_match_rule_state);
+    size_t count, i;
+    struct sysdb_attrs **users;
+
+    ret = sdap_get_generic_recv(subreq, state, &count, &users);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("LDAP search failed: [%s]\n", strerror(ret)));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS,
+          ("Search for users returned %d results\n", count));
+
+    /* Add this batch of users to the list */
+    if (count > 0) {
+        state->users = talloc_realloc(state, state->users,
+                                      struct sysdb_attrs *,
+                                      state->count + count + 1);
+        if (!state->users) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+
+        /* Copy the new users into the list */
+        for (i = 0; i < count; i++) {
+            state->users[state->count + i] =
+                    talloc_steal(state->users, users[i]);
+        }
+
+        state->count += count;
+        state->users[state->count] = NULL;
+    }
+
+    /* Continue checking other search bases */
+    state->base_iter++;
+    if (state->search_bases[state->base_iter]) {
+        /* There are more search bases to try */
+        ret = sdap_get_ad_match_rule_members_next_base(req);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+        }
+        return;
+    }
+
+    /* No more search bases. We're done here. */
+    if (state->count == 0) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              ("No users matched in any search base\n"));
+        tevent_req_error(req, ENOENT);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+errno_t
+sdap_get_ad_match_rule_members_recv(struct tevent_req *req,
+                                    TALLOC_CTX *mem_ctx,
+                                    size_t *num_users,
+                                    struct sysdb_attrs ***users)
+{
+    struct sdap_ad_match_rule_state *state =
+            tevent_req_data(req, struct sdap_ad_match_rule_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *num_users = state->count;
+    *users = talloc_steal(mem_ctx, state->users);
+    return EOK;
+}
-- 
1.7.10.2

From 216497fcff0c4966b982987d1142c282d9949546 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <[email protected]>
Date: Sun, 10 Jun 2012 14:50:43 -0400
Subject: [PATCH 8/8] LDAP: Add support for AD chain matching extension in
 initgroups

---
 Makefile.am                                   |    1 +
 src/providers/ldap/sdap_async.h               |   14 ++
 src/providers/ldap/sdap_async_initgroups.c    |   30 ++-
 src/providers/ldap/sdap_async_initgroups_ad.c |  296 +++++++++++++++++++++++++
 4 files changed, 332 insertions(+), 9 deletions(-)
 create mode 100644 src/providers/ldap/sdap_async_initgroups_ad.c

diff --git a/Makefile.am b/Makefile.am
index 0e84da00a570c6f1d3f28c154c49d045ab734cef..8af213e59aa707b5c981833a46e2b8d9916d9200 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1093,6 +1093,7 @@ libsss_ldap_common_la_SOURCES = \
     src/providers/ldap/sdap_async_groups.c \
     src/providers/ldap/sdap_async_groups_ad.c \
     src/providers/ldap/sdap_async_initgroups.c \
+    src/providers/ldap/sdap_async_initgroups_ad.c \
     src/providers/ldap/sdap_async_connection.c \
     src/providers/ldap/sdap_async_netgroups.c \
     src/providers/ldap/sdap_async_services.c \
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index ebbf1e8bd5a14732ea24e7a86dfe10b07d0ffabe..325ba513b0e17fa66700a440a9e33ade5654413b 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -264,4 +264,18 @@ sdap_get_ad_match_rule_members_recv(struct tevent_req *req,
                                     size_t *num_users,
                                     struct sysdb_attrs ***users);
 
+struct tevent_req *
+sdap_get_ad_match_rule_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,
+                                       const char **attrs,
+                                       int timeout);
+
+errno_t
+sdap_get_ad_match_rule_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 bae6d466848769cabf415921196c81b5044d6cf2..dde61d740713c4ac68751200b578ef30004c46e2 100644
--- a/src/providers/ldap/sdap_async_initgroups.c
+++ b/src/providers/ldap/sdap_async_initgroups.c
@@ -2657,10 +2657,6 @@ static void sdap_get_initgr_user(struct tevent_req *subreq)
 
     case SDAP_SCHEMA_RFC2307BIS:
     case SDAP_SCHEMA_AD:
-        /* TODO: AD uses a different member/memberof schema
-         *       We need an AD specific call that is able to unroll
-         *       nested groups by doing extensive recursive searches */
-
         ret = sysdb_attrs_get_string(state->orig_user,
                                      SYSDB_ORIG_DN,
                                      &orig_dn);
@@ -2669,17 +2665,29 @@ static void sdap_get_initgr_user(struct tevent_req *subreq)
             return;
         }
 
-        subreq = sdap_initgr_rfc2307bis_send(
-                state, state->ev, state->opts, state->sysdb,
-                state->dom, state->sh,
-                cname, orig_dn);
+        if (dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE)) {
+            /* Take advantage of AD's extensibleMatch filter to look up
+             * all parent groups in a single request.
+             */
+            subreq = sdap_get_ad_match_rule_initgroups_send(
+                    state, state->ev, state->opts, state->sysdb,
+                    state->sh, cname, orig_dn, NULL,
+                    state->timeout);
+        } else {
+            subreq = sdap_initgr_rfc2307bis_send(
+                    state, state->ev, state->opts, state->sysdb,
+                    state->dom, state->sh,
+                    cname, orig_dn);
+        }
         if (!subreq) {
             tevent_req_error(req, ENOMEM);
             return;
         }
+
         talloc_steal(subreq, orig_dn);
         tevent_req_set_callback(subreq, sdap_get_initgr_done, req);
         break;
+
     case SDAP_SCHEMA_IPA_V1:
         subreq = sdap_initgr_nested_send(state, state->ev, state->opts,
                                          state->sysdb, state->dom, state->sh,
@@ -2730,7 +2738,11 @@ static void sdap_get_initgr_done(struct tevent_req *subreq)
 
     case SDAP_SCHEMA_RFC2307BIS:
     case SDAP_SCHEMA_AD:
-        ret = sdap_initgr_rfc2307bis_recv(subreq);
+        if (dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE)) {
+            ret = sdap_get_ad_match_rule_initgroups_recv(subreq);
+        } else {
+            ret = sdap_initgr_rfc2307bis_recv(subreq);
+        }
         break;
 
     case SDAP_SCHEMA_IPA_V1:
diff --git a/src/providers/ldap/sdap_async_initgroups_ad.c b/src/providers/ldap/sdap_async_initgroups_ad.c
new file mode 100644
index 0000000000000000000000000000000000000000..d9679b61fa71e211c7d744c8563f9f42fb6ddf4d
--- /dev/null
+++ b/src/providers/ldap/sdap_async_initgroups_ad.c
@@ -0,0 +1,296 @@
+/*
+    SSSD
+
+    Authors:
+        Stephen Gallagher <[email protected]>
+
+    Copyright (C) 2012 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async_private.h"
+
+struct sdap_ad_match_rule_initgr_state {
+    struct tevent_context *ev;
+    struct sdap_options *opts;
+    struct sysdb_ctx *sysdb;
+    struct sdap_handle *sh;
+    const char *name;
+    const char *orig_dn;
+    const char **attrs;
+    int timeout;
+    const char *base_filter;
+    char *filter;
+
+    size_t count;
+    struct sysdb_attrs **groups;
+
+    size_t base_iter;
+    struct sdap_search_base **search_bases;
+};
+
+static errno_t
+sdap_get_ad_match_rule_initgroups_next_base(struct tevent_req *req);
+
+static void
+sdap_get_ad_match_rule_initgroups_step(struct tevent_req *subreq);
+
+struct tevent_req *
+sdap_get_ad_match_rule_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,
+                                       const char **attrs,
+                                       int timeout)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct sdap_ad_match_rule_initgr_state *state;
+    const char **filter_members;
+    char *sanitized_user_dn;
+
+    req = tevent_req_create(mem_ctx, &state,
+                            struct sdap_ad_match_rule_initgr_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->opts = opts;
+    state->sysdb = sysdb;
+    state->sh = sh;
+    state->name = name;
+    state->orig_dn = orig_dn;
+    state->base_iter = 0;
+    state->search_bases = opts->group_search_bases;
+
+    if (attrs) {
+        state->attrs = attrs;
+    } else {
+        /* Default to requesting all of the group attributes
+         * that we know about, except for 'member' because that
+         * wastes a lot of bandwidth here and we only really
+         * care about a single member (the one we already have).
+         */
+        filter_members = talloc_array(state, const char *, 2);
+        if (!filter_members) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+        filter_members[0] = opts->group_map[SDAP_AT_GROUP_MEMBER].name;
+        filter_members[1] = NULL;
+
+        ret = build_attrs_from_map(state, opts->group_map,
+                                   SDAP_OPTS_GROUP,
+                                   filter_members,
+                                   &state->attrs, NULL);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Could not build attribute map: [%s]\n",
+                   strerror(ret)));
+            goto immediate;
+        }
+    }
+
+    /* Sanitize the user DN in case we have special characters in DN */
+    ret = sss_filter_sanitize(state, state->orig_dn, &sanitized_user_dn);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not sanitize user DN: %s\n",
+               strerror(ret)));
+        goto immediate;
+    }
+
+    /* Craft a special filter according to
+     * http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475%28v=vs.85%29.aspx
+     */
+    state->base_filter =
+            talloc_asprintf(state,
+                            "(&(%s:%s:=%s)(objectClass=%s))",
+                            state->opts->group_map[SDAP_AT_GROUP_MEMBER].name,
+                            SDAP_MATCHING_RULE_IN_CHAIN,
+                            sanitized_user_dn,
+                            state->opts->group_map[SDAP_OC_GROUP].name);
+    talloc_zfree(sanitized_user_dn);
+    if (!state->base_filter) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    /* Start the loop through the search bases to get all of the
+     * groups to which this user belongs.
+     */
+    ret = sdap_get_ad_match_rule_initgroups_next_base(req);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("sdap_get_ad_match_rule_members_next_base failed: [%s]\n",
+               strerror(ret)));
+        goto immediate;
+    }
+
+    return req;
+
+immediate:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t
+sdap_get_ad_match_rule_initgroups_next_base(struct tevent_req *req)
+{
+    struct tevent_req *subreq;
+    struct sdap_ad_match_rule_initgr_state *state;
+
+    state = tevent_req_data(req, struct sdap_ad_match_rule_initgr_state);
+
+    talloc_zfree(state->filter);
+    state->filter = sdap_get_id_specific_filter(state,
+                        state->base_filter,
+                        state->search_bases[state->base_iter]->filter);
+    if (!state->filter) {
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Searching for groups with base [%s]\n",
+           state->search_bases[state->base_iter]->basedn));
+
+    subreq = sdap_get_generic_send(
+            state, state->ev, state->opts, state->sh,
+            state->search_bases[state->base_iter]->basedn,
+            state->search_bases[state->base_iter]->scope,
+            state->filter, state->attrs,
+            state->opts->group_map, SDAP_OPTS_GROUP,
+            state->timeout, true);
+    if (!subreq) {
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(subreq,
+                            sdap_get_ad_match_rule_initgroups_step,
+                            req);
+
+    return EOK;
+}
+
+static void
+sdap_get_ad_match_rule_initgroups_step(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_ad_match_rule_initgr_state *state =
+            tevent_req_data(req, struct sdap_ad_match_rule_initgr_state);
+    size_t count, i;
+    struct sysdb_attrs **groups;
+    char **sysdb_grouplist;
+
+    ret = sdap_get_generic_recv(subreq, state, &count, &groups);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("LDAP search failed: [%s]\n", strerror(ret)));
+        goto error;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS,
+          ("Search for users returned %d results\n", count));
+
+    /* Add this batch of groups to the list */
+    if (count > 0) {
+        state->groups = talloc_realloc(state, state->groups,
+                                      struct sysdb_attrs *,
+                                      state->count + count + 1);
+        if (!state->groups) {
+            tevent_req_error(req, ENOMEM);
+            return;
+        }
+
+        /* Copy the new groups into the list */
+        for (i = 0; i < count; i++) {
+            state->groups[state->count + i] =
+                    talloc_steal(state->groups, groups[i]);
+        }
+
+        state->count += count;
+        state->groups[state->count] = NULL;
+    }
+
+    /* Continue checking other search bases */
+    state->base_iter++;
+    if (state->search_bases[state->base_iter]) {
+        /* There are more search bases to try */
+        ret = sdap_get_ad_match_rule_initgroups_next_base(req);
+        if (ret != EOK) {
+            goto error;
+        }
+        return;
+    }
+
+    /* No more search bases. Save the groups. */
+
+    if (state->count == 0) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              ("User is not a member of any group in the search bases\n"));
+    }
+
+    /* Get the current sysdb group list for this user
+     * so we can update it.
+     */
+    ret = get_sysdb_grouplist(state, state->sysdb, state->name,
+                              &sysdb_grouplist);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not get the list of groups for [%s] in the sysdb: "
+               "[%s]\n",
+               state->name, strerror(ret)));
+        goto error;
+    }
+
+    /* The extensibleMatch search rule eliminates the need for
+     * nested group searches, so we can just update the
+     * memberships now.
+     */
+    ret = sdap_initgr_common_store(state->sysdb, state->opts,
+                                   state->name,
+                                   SYSDB_MEMBER_USER,
+                                   sysdb_grouplist,
+                                   state->groups,
+                                   state->count);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not store groups for user [%s]: [%s]\n",
+               state->name, strerror(ret)));
+        goto error;
+    }
+
+    tevent_req_done(req);
+    return;
+
+error:
+    tevent_req_error(req, ret);
+}
+
+errno_t
+sdap_get_ad_match_rule_initgroups_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
-- 
1.7.10.2

Attachment: signature.asc
Description: This is a digitally signed message part

_______________________________________________
sssd-devel mailing list
[email protected]
https://fedorahosted.org/mailman/listinfo/sssd-devel

Reply via email to