From 1076626641ce81f71a16a57d5359d60ce2aab5a3 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 29 Apr 2011 15:59:17 +0200
Subject: [PATCH 9/9] Use dereference when processing RFC2307bis nested groups

Instead of issuing N LDAP requests when processing a group with N users,
utilize the dereference functionality to pull down all the members in a
single LDAP request.

https://fedorahosted.org/sssd/ticket/799
---
 src/config/SSSDConfig.py                 |    1 +
 src/config/etc/sssd.api.d/sssd-ldap.conf |    1 +
 src/man/sssd-ldap.5.xml                  |   23 ++
 src/providers/ipa/ipa_common.c           |    3 +-
 src/providers/ipa/ipa_common.h           |    2 +-
 src/providers/ldap/ldap_common.c         |    3 +-
 src/providers/ldap/sdap.h                |    1 +
 src/providers/ldap/sdap_async_accounts.c |  468 +++++++++++++++++++++++++++++-
 8 files changed, 485 insertions(+), 17 deletions(-)

diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
index b613cfe4e435888906fac423df986be7b534faf1..5171d2eb120fcfee04727b13ea474b81c9170eff 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -141,6 +141,7 @@ option_strings = {
     'ldap_deref' : _('How to dereference aliases'),
     'ldap_dns_service_name' : _('Service name for DNS service lookups'),
     'ldap_page_size' : _('The number of records to retrieve in a single LDAP query'),
+    'ldap_deref_threshold' : _('The number of members that must be missing to trigger a full deref'),
 
     'ldap_entry_usn' : _('entryUSN attribute'),
     'ldap_rootdse_last_usn' : _('lastUSN attribute'),
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
index e568c74d3d1763ada387fa11fe39876c7879a7bc..ce9ec513df609aaaa07ce8ef872680d4ca1f564a 100644
--- a/src/config/etc/sssd.api.d/sssd-ldap.conf
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -28,6 +28,7 @@ ldap_krb5_ticket_lifetime = int, None, false
 ldap_dns_service_name = str, None, false
 ldap_deref = str, None, false
 ldap_page_size = int, None, false
+ldap_deref_threshold = int, None, false
 
 [provider/ldap/id]
 ldap_search_timeout = int, None, false
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index 49c9e49157f45449617028e75934e0f28a408278..42ea848a2d28808b1f8751ab2e6bed10ec75ce75 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -869,6 +869,29 @@
                 </varlistentry>
 
                 <varlistentry>
+                    <term>ldap_deref_threshold (integer)</term>
+                    <listitem>
+                        <para>
+                            Specify the number of group members that must be
+                            missing from the internal cache in order to trigger
+                            a dereference lookup. If less members are missing,
+                            they are looked up individually.
+                        </para>
+                        <para>
+                            A dereference lookup is a means of fetching all
+                            group members in a single LDAP call.
+                            Different LDAP servers may implement different
+                            dereference methods. The currently supported
+                            servers are 389/RHDS, OpenLDAP and Active
+                            Directory.
+                        </para>
+                        <para>
+                            Default: 10
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
                     <term>ldap_tls_reqcert (string)</term>
                     <listitem>
                         <para>
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index b0fe523cf3a9a86526710b0a2094f85e11e5a33a..da3fcd6f7fd9d83d077795305d9a9d0be617ebc5 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -94,7 +94,8 @@ struct dp_option ipa_def_ldap_opts[] = {
      * manpages or SSSDConfig API
      */
     { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
-    { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }
+    { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER },
+    { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }
 };
 
 struct sdap_attr_map ipa_attr_map[] = {
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
index 02c4276cab7382749ac113f75e1019636daca991..8f0f35a826aaf7063ac6bfa26f5b06d69019e9c4 100644
--- a/src/providers/ipa/ipa_common.h
+++ b/src/providers/ipa/ipa_common.h
@@ -35,7 +35,7 @@ struct ipa_service {
 /* the following defines are used to keep track of the options in the ldap
  * module, so that if they change and ipa is not updated correspondingly
  * this will trigger a runtime abort error */
-#define IPA_OPTS_BASIC_TEST 50
+#define IPA_OPTS_BASIC_TEST 51
 
 /* the following define is used to keep track of the options in the krb5
  * module, so that if they change and ipa is not updated correspondingly
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index 12028b0131f4f4cdaca2800bdf135ee98617d045..1b52190f9d143bbd8d86ebe1e3f35a115a9c43d9 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -87,7 +87,8 @@ struct dp_option default_basic_opts[] = {
      * manpages or SSSDConfig API
      */
     { "ldap_auth_disable_tls_never_use_in_production", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
-    { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER }
+    { "ldap_page_size", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER },
+    { "ldap_deref_threshold", DP_OPT_NUMBER, { .number = 10 }, NULL_NUMBER }
 };
 
 struct sdap_attr_map generic_attr_map[] = {
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index fa3d76e300073decdc90b3abdf4b325d6127ca06..fa08865e27dd22280f12a8e53b4c50adfed2d8fc 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -194,6 +194,7 @@ enum sdap_basic_opt {
     SDAP_ENUM_SEARCH_TIMEOUT,
     SDAP_DISABLE_AUTH_TLS,
     SDAP_PAGE_SIZE,
+    SDAP_DEREF_THRESHOLD,
 
     SDAP_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ldap/sdap_async_accounts.c b/src/providers/ldap/sdap_async_accounts.c
index a4f08bc4989a2e026f2abd43e2979b421d0c6d37..7422570261bf53e65f5816220601d99fc79a996b 100644
--- a/src/providers/ldap/sdap_async_accounts.c
+++ b/src/providers/ldap/sdap_async_accounts.c
@@ -3275,6 +3275,28 @@ int sdap_get_initgr_recv(struct tevent_req *req)
     return EOK;
 }
 
+struct sdap_deref_ctx {
+    const char *orig_dn;
+
+    size_t expired_users_num;
+    uint32_t expired_users_index;
+    char **expired_users;
+
+    size_t expired_groups_num;
+    uint32_t expired_groups_index;
+    char **expired_groups;
+
+    size_t missing_dns_num;
+    uint32_t missing_dns_index;
+    char **missing_dns;
+
+    struct sdap_deref_attrs **deref_result;
+    size_t num_results;
+    uint32_t result_index;
+
+    int deref_threshold;
+};
+
 struct sdap_nested_group_ctx {
     struct tevent_context *ev;
     struct sysdb_ctx *sysdb;
@@ -3291,9 +3313,13 @@ struct sdap_nested_group_ctx {
     struct ldb_message_element *members;
     uint32_t member_index;
     char *member_dn;
+
+    struct sdap_deref_ctx *derefctx;
 };
 
+static errno_t sdap_nested_group_process_deref_step(struct tevent_req *req);
 static errno_t sdap_nested_group_process_step(struct tevent_req *req);
+
 static struct tevent_req *sdap_nested_group_process_send(
         TALLOC_CTX *mem_ctx, struct tevent_context *ev,
         struct sss_domain_info *domain,
@@ -3387,8 +3413,20 @@ static struct tevent_req *sdap_nested_group_process_send(
 
     state->member_index = 0;
 
-    ret = sdap_nested_group_process_step(req);
-    if (ret != EAGAIN) goto immediate;
+    if (sdap_has_deref_support(state->sh)) {
+        state->derefctx = talloc_zero(state, struct sdap_deref_ctx);
+        if (!state->derefctx) goto immediate;
+
+        ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN,
+                                     &state->derefctx->orig_dn);
+        if (ret != EOK) goto immediate;
+
+        ret = sdap_nested_group_process_deref_step(req);
+        if (ret != EAGAIN) goto immediate;
+    } else {
+        ret = sdap_nested_group_process_step(req);
+        if (ret != EAGAIN) goto immediate;
+    }
 
     return req;
 
@@ -3415,6 +3453,136 @@ static void sdap_nested_group_process_user(struct tevent_req *subreq);
 static errno_t sdap_nested_group_lookup_user(struct tevent_req *req,
                                              tevent_req_fn fn);
 static errno_t sdap_nested_group_lookup_group(struct tevent_req *req);
+static errno_t sdap_nested_group_process_deref_call(struct tevent_req *req);
+static errno_t sdap_nested_group_process_noderef(struct tevent_req *req);
+
+static errno_t sdap_nested_group_process_deref_step(struct tevent_req *req)
+{
+    errno_t ret;
+    struct sdap_nested_group_ctx *state =
+            tevent_req_data(req, struct sdap_nested_group_ctx);
+    size_t missing = 0;
+    struct ldb_message **msgs = NULL;
+    enum sysdb_member_type mtype;
+    struct sdap_deref_ctx *dctx = state->derefctx;
+
+    dctx->deref_threshold = dp_opt_get_int(state->opts->basic,
+                                           SDAP_DEREF_THRESHOLD);
+
+    dctx->expired_users = talloc_array(dctx, char *,
+                                       state->members->num_values + 1);
+    dctx->expired_groups = talloc_array(dctx, char *,
+                                       state->members->num_values + 1);
+    dctx->missing_dns = talloc_array(dctx, char *,
+                                     state->members->num_values + 1);
+    if (!dctx->expired_users ||
+        !dctx->expired_groups ||
+        !dctx->missing_dns) return ENOMEM;
+
+    while (true) {
+        if (state->member_index >= state->members->num_values) {
+            /* No more entries to check. Return success */
+            talloc_zfree(state->member_dn);
+            ret = EOK;
+            break;
+        }
+
+        /* Continue to loop through until all entries have been
+         * processed.
+         */
+        ret = sdap_nested_group_check_hash(state);
+        if (ret == EOK) {
+            talloc_zfree(state->member_dn);
+            break; /* All remaining members in hash, check missing */
+        } else if (ret != ENOENT) {
+            goto done; /* Unexpected error */
+        }
+
+        ret = sdap_nested_group_check_cache(state, state->sysdb,
+                                            state->domain, state->opts,
+                                            state->member_dn,
+                                            &msgs, &mtype);
+        if (ret == EOK) {
+            /* The entry is cached and valid */
+            state->member_index++;
+            talloc_zfree(state->member_dn);
+            continue;
+        } else if (ret == EAGAIN) {
+            /* The entry is cached but needs refresh */
+            switch(mtype) {
+            case SYSDB_MEMBER_GROUP:
+                DEBUG(8, ("Cached LDAP group [%s] needs refresh\n",
+                           state->member_dn));
+
+                missing++;
+
+                dctx->expired_groups[dctx->expired_groups_num] =
+                    talloc_move(dctx, &state->member_dn);
+                dctx->expired_groups_num++;
+
+                state->member_index++;
+                continue;
+            case SYSDB_MEMBER_USER:
+                DEBUG(8, ("Cached LDAP user [%s] needs refresh\n",
+                           state->member_dn));
+                missing++;
+
+                dctx->expired_users[dctx->expired_users_num] =
+                    talloc_move(dctx, &state->member_dn);
+                dctx->expired_users_num++;
+
+                state->member_index++;
+                continue;
+            default:
+                DEBUG(2, ("Unknown member value\n"));
+                ret = EINVAL;
+                goto done;
+            }
+        } else if (ret == ENOENT) {
+            /* The entry is missing. It is unclear whether it
+             * is a user or a group so we'll need to try looking
+             * it up */
+            missing++;
+
+            dctx->missing_dns[dctx->missing_dns_num] =
+                talloc_move(dctx, &state->member_dn);
+            dctx->missing_dns_num++;
+
+            state->member_index++;
+            continue;
+        }
+
+        /* Unexpected error, skip this entry */
+        state->member_index++;
+        continue;
+    } /* while (true) */
+
+
+    dctx->expired_users[dctx->expired_users_num] = NULL;
+    dctx->expired_groups[dctx->expired_groups_num] = NULL;
+    dctx->missing_dns[dctx->missing_dns_num] = NULL;
+
+    if (missing == 0) {
+        ret = EOK;
+        goto done;
+    }
+
+    if (missing > dctx->deref_threshold) {
+        DEBUG(6, ("Missing data past threshold, doing a full deref\n"));
+        ret = sdap_nested_group_process_deref_call(req);
+    } else {
+        DEBUG(6, ("Falling back to individual lookups\n"));
+        ret = sdap_nested_group_process_noderef(req);
+    }
+
+    if (ret != EOK && ret != EAGAIN) goto done;
+    return EAGAIN;
+
+done:
+    talloc_zfree(state->member_dn);
+    return ret;
+}
+
 
 static errno_t sdap_nested_group_process_step(struct tevent_req *req)
 {
@@ -3655,6 +3823,110 @@ done:
     return ret;
 }
 
+static void sdap_nested_group_process_deref(struct tevent_req *subreq);
+
+static errno_t
+sdap_nested_group_process_deref_call(struct tevent_req *req)
+{
+    struct tevent_req *subreq;
+    struct sdap_attr_map_info *maps;
+    const char **sdap_attrs;
+    int ret;
+    int timeout;
+    const int num_maps = 2;
+    struct sdap_nested_group_ctx *state =
+            tevent_req_data(req, struct sdap_nested_group_ctx);
+
+    maps = talloc_array(state, struct sdap_attr_map_info, num_maps+1);
+    if (!maps) return ENOMEM;
+
+    maps[0].map = state->opts->user_map;
+    maps[0].num_attrs = SDAP_OPTS_USER;
+    maps[1].map = state->opts->group_map;
+    maps[1].num_attrs = SDAP_OPTS_GROUP;
+    maps[2].map = NULL;
+
+    /* Pull down the whole group map, but only pull down username
+     * and originalDN for users. */
+    ret = build_attrs_from_map(state, state->opts->group_map,
+                               SDAP_OPTS_GROUP, &sdap_attrs);
+    if (ret != EOK) goto fail;
+
+    sdap_attrs = talloc_realloc(NULL, sdap_attrs, const char *,
+                                SDAP_OPTS_GROUP + 2);
+    if (!sdap_attrs) {
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    sdap_attrs[SDAP_OPTS_GROUP] = \
+                        state->opts->user_map[SDAP_AT_USER_NAME].name;
+    sdap_attrs[SDAP_OPTS_GROUP + 1] = NULL;
+
+    timeout = dp_opt_get_int(state->opts->basic, SDAP_ENTRY_CACHE_TIMEOUT);
+
+    subreq = sdap_deref_search_send(state, state->ev, state->opts,
+                    state->sh, state->derefctx->orig_dn,
+                    state->opts->group_map[SDAP_AT_GROUP_MEMBER].name,
+                    sdap_attrs, num_maps, maps, timeout);
+    if (!subreq) {
+        ret = EIO;
+        goto fail;
+    }
+    talloc_steal(subreq, sdap_attrs);
+    talloc_steal(subreq, maps);
+
+    tevent_req_set_callback(subreq, sdap_nested_group_process_deref, req);
+    return EOK;
+
+fail:
+    talloc_free(sdap_attrs);
+    talloc_free(maps);
+    return ret;
+}
+
+static errno_t sdap_nested_group_process_noderef(struct tevent_req *req)
+{
+    struct sdap_nested_group_ctx *state =
+            tevent_req_data(req, struct sdap_nested_group_ctx);
+    struct sdap_deref_ctx *dctx = state->derefctx;
+    errno_t ret;
+
+    if (dctx->expired_users_index < dctx->expired_users_num) {
+        state->member_dn = dctx->expired_users[dctx->expired_users_index];
+        DEBUG(8, ("Refreshing expired user [%s]\n", state->member_dn));
+
+        ret = sdap_nested_group_lookup_user(
+                req, sdap_nested_group_process_user);
+        if (ret != EOK) goto done;
+        return EAGAIN;
+    }
+
+    if (dctx->expired_groups_index < dctx->expired_groups_num) {
+        state->member_dn = dctx->expired_groups[dctx->expired_groups_index];
+        DEBUG(8, ("Refreshing expired group [%s]\n", state->member_dn));
+
+        ret = sdap_nested_group_lookup_group(req);
+        if (ret != EOK) goto done;
+        return EAGAIN;
+    }
+
+    if (dctx->missing_dns_index < dctx->missing_dns_num) {
+        state->member_dn = dctx->missing_dns[dctx->missing_dns_index];
+        DEBUG(8, ("Looking up missing DN [%s]\n", state->member_dn));
+
+        /* Try users first for generic missing DNs */
+        ret = sdap_nested_group_lookup_user(
+                req, sdap_nested_group_process_ldap_user);
+        if (ret != EOK) goto done;
+        return EAGAIN;
+    }
+
+    ret = EOK;
+done:
+    return ret;
+}
+
 static errno_t sdap_nested_group_lookup_user(struct tevent_req *req,
                                              tevent_req_fn fn)
 {
@@ -3800,9 +4072,15 @@ static void sdap_nested_group_process_user(struct tevent_req *subreq)
     talloc_steal(state->users, replies[0]);
 
 skip:
-    state->member_index++;
-    talloc_zfree(state->member_dn);
-    ret = sdap_nested_group_process_step(req);
+    if (state->derefctx) {
+        state->derefctx->expired_users_index++;
+        ret = sdap_nested_group_process_noderef(req);
+    } else {
+        state->member_index++;
+        talloc_zfree(state->member_dn);
+        ret = sdap_nested_group_process_step(req);
+    }
+
     if (ret == EOK) {
         /* EOK means it's complete */
         tevent_req_done(req);
@@ -3872,9 +4150,15 @@ static void sdap_nested_group_process_group(struct tevent_req *subreq)
     return;
 
 skip:
-    state->member_index++;
-    talloc_zfree(state->member_dn);
-    ret = sdap_nested_group_process_step(req);
+    if (state->derefctx) {
+        state->derefctx->expired_groups_index++;
+        ret = sdap_nested_group_process_noderef(req);
+    } else {
+        state->member_index++;
+        talloc_zfree(state->member_dn);
+        ret = sdap_nested_group_process_step(req);
+    }
+
     if (ret == EOK) {
         /* EOK means it's complete */
         tevent_req_done(req);
@@ -3905,9 +4189,22 @@ static void sdap_group_internal_nesting_done(struct tevent_req *subreq)
         return;
     }
 
-    state->member_index++;
-    talloc_zfree(state->member_dn);
-    ret = sdap_nested_group_process_step(req);
+    if (state->derefctx) {
+        if (state->derefctx->expired_groups_index <
+            state->derefctx->expired_groups_num) {
+            state->derefctx->expired_groups_index++;
+        } else {
+            state->derefctx->missing_dns_index++;
+        }
+
+        state->derefctx->expired_users_index++;
+        ret = sdap_nested_group_process_noderef(req);
+    } else {
+        state->member_index++;
+        talloc_zfree(state->member_dn);
+        ret = sdap_nested_group_process_step(req);
+    }
+
     if (ret == EOK) {
         /* EOK means it's complete */
         tevent_req_done(req);
@@ -3981,9 +4278,15 @@ static void sdap_nested_group_process_ldap_user(struct tevent_req *subreq)
     talloc_steal(state->users, replies[0]);
 
     /* Move on to the next member */
-    state->member_index++;
-    talloc_zfree(state->member_dn);
-    ret = sdap_nested_group_process_step(req);
+    if (state->derefctx) {
+        state->derefctx->missing_dns_index++;
+        ret = sdap_nested_group_process_noderef(req);
+    } else {
+        state->member_index++;
+        talloc_zfree(state->member_dn);
+        ret = sdap_nested_group_process_step(req);
+    }
+
     if (ret == EOK) {
         /* EOK means it's complete */
         tevent_req_done(req);
@@ -3999,6 +4302,143 @@ done:
     talloc_free(tmp_ctx);
 }
 
+static errno_t
+sdap_nested_group_process_deref_result(struct tevent_req *req);
+
+static void sdap_nested_group_process_deref(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_nested_group_ctx *state =
+            tevent_req_data(req, struct sdap_nested_group_ctx);
+
+    ret = sdap_deref_search_recv(subreq, state->derefctx,
+                                 &state->derefctx->num_results,
+                                 &state->derefctx->deref_result);
+    talloc_zfree(subreq);
+    if (ret != EOK && ret != ENOENT) {
+        tevent_req_error(req, ret);
+        return;
+    } else if (ret == ENOENT || state->derefctx->deref_result == 0) {
+        /* Nothing could be dereferenced. Done. */
+        tevent_req_done(req);
+        return;
+    }
+
+    state->derefctx->result_index = 0;
+
+    DEBUG(8, ("Received %d dereference results, about to process them\n",
+              state->derefctx->num_results));
+    ret = sdap_nested_group_process_deref_result(req);
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else if (ret != EAGAIN) {
+        tevent_req_error(req, ret);
+    }
+
+    /* EAGAIN means a recursive search is in progress */
+}
+
+static void
+sdap_nested_group_process_deref_recurse_done(struct tevent_req *subreq);
+
+static errno_t
+sdap_nested_group_process_deref_result(struct tevent_req *req)
+{
+    struct sdap_nested_group_ctx *state =
+            tevent_req_data(req, struct sdap_nested_group_ctx);
+    struct tevent_req *subreq;
+    hash_key_t key;
+    hash_value_t value;
+    int hret;
+    const char *orig_dn;
+    errno_t ret;
+    struct sdap_deref_ctx *dctx = state->derefctx;
+
+    while (dctx->result_index < dctx->num_results) {
+        if (dctx->deref_result[dctx->result_index]->map == \
+            state->opts->user_map) {
+            /* Add to appropriate hash table */
+            ret = sysdb_attrs_get_string(
+                    dctx->deref_result[dctx->result_index]->attrs,
+                    SYSDB_ORIG_DN, &orig_dn);
+            if (ret != EOK) {
+                DEBUG(2, ("The entry has no originalDN\n"));
+                return ret;
+            }
+
+            DEBUG(9, ("Found member user [%s]\n", orig_dn));
+
+            key.type = HASH_KEY_STRING;
+            key.str = talloc_strdup(state, orig_dn);
+
+            value.type = HASH_VALUE_PTR;
+            value.ptr = dctx->deref_result[dctx->result_index]->attrs;
+
+            hret = hash_enter(state->users, &key, &value);
+            if (hret != HASH_SUCCESS) return EIO;
+
+            talloc_steal(state->users,
+                         dctx->deref_result[dctx->result_index]->attrs);
+            dctx->result_index++;
+        } else if (dctx->deref_result[dctx->result_index]->map == \
+                   state->opts->group_map) {
+            DEBUG(6, ("Recursing down a nested group\n"));
+            subreq = sdap_nested_group_process_send(state, state->ev,
+                                state->domain, state->sysdb,
+                                dctx->deref_result[dctx->result_index]->attrs,
+                                state->users, state->groups,
+                                state->opts, state->sh,
+                                state->nesting_level + 1);
+            if (!subreq) return EIO;
+
+            tevent_req_set_callback(subreq,
+                    sdap_nested_group_process_deref_recurse_done,
+                    req);
+            return EAGAIN;
+        } else {
+            /* This should never happen, but if it does,
+             * do not loop forever */
+            DEBUG(2, ("Entry does not match any known map, skipping\n"));
+            dctx->result_index++;
+            continue;
+        }
+    }
+
+    /* All deref results processed */
+    DEBUG(8, ("All dereference results processed\n"));
+    return EOK;
+}
+
+static void
+sdap_nested_group_process_deref_recurse_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_nested_group_ctx *state =
+            tevent_req_data(req, struct sdap_nested_group_ctx);
+
+    ret = sdap_nested_group_process_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->derefctx->result_index++;
+
+    ret = sdap_nested_group_process_deref_result(req);
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else if (ret != EAGAIN) {
+        tevent_req_error(req, ret);
+    }
+
+    /* EAGAIN means a recursive search is in progress */
+}
+
 static errno_t sdap_nested_group_process_recv(struct tevent_req *req)
 {
     TEVENT_REQ_RETURN_ON_ERROR(req);
-- 
1.7.4.4

