From 51e741785e1c5e9d8b0c85dc362d6ed643ee9bbe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Mon, 18 Feb 2013 10:28:07 +0100
Subject: [PATCH 2/2] refactor nested group processing: replace old code

https://fedorahosted.org/sssd/ticket/1784
---
 Makefile.am                             |    1 +
 src/providers/ldap/sdap_async_groups.c  | 1716 +------------------------------
 src/providers/ldap/sdap_async_private.h |   17 +
 3 files changed, 23 insertions(+), 1711 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 64708216103e48facf780f246de5668c8e0b26a7..116f1ec390836e7218b35770c436c3f68fc98626 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1264,6 +1264,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_nested_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 \
diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c
index 76c077d83be4a0ca0a5da3cf35889dbcf0c7e87d..7c757a573280036526327c0fc1ac07252a43f6b6 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -1540,15 +1540,7 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
     return EOK;
 }
 
-static struct tevent_req *sdap_nested_group_process_send(
-        TALLOC_CTX *mem_ctx, struct tevent_context *ev,
-        struct sss_domain_info *domain,
-        struct sysdb_ctx *sysdb, struct sysdb_attrs *group,
-        hash_table_t *users, hash_table_t *groups,
-        struct sdap_options *opts, struct sdap_handle *sh,
-        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)
@@ -1562,7 +1554,6 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
     bool next_base = false;
     size_t count;
     struct sysdb_attrs **groups;
-    bool enable_deref = true;
 
     ret = sdap_get_generic_recv(subreq, state,
                                 &count, &groups);
@@ -1641,57 +1632,9 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
         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_GROUPS)) {
-
-            /* Prepare hashes for nested user processing */
-            ret = sss_hash_create(state, 32, &state->user_hash);
-            if (ret != EOK) {
-                tevent_req_error(req, ret);
-                return;
-            }
-
-            ret = sss_hash_create(state, 32, &state->group_hash);
-            if (ret != EOK) {
-                tevent_req_error(req, ret);
-                return;
-            }
-
-            /*
-             * If any search base contains filter, disable dereference.
-             */
-            enable_deref = true;
-            for (i = 0; state->opts->user_search_bases[i] != NULL; i++) {
-                if (state->opts->user_search_bases[i]->filter != NULL) {
-                    DEBUG(SSSDBG_TRACE_FUNC,
-                          ("User search base contains filter, "
-                           "dereference will be disabled\n"));
-                    enable_deref = false;
-                    break;
-                }
-            }
-
-            if (enable_deref) {
-                for (i = 0; state->opts->group_search_bases[i] != NULL; i++) {
-                    if (state->opts->group_search_bases[i]->filter != NULL) {
-                        DEBUG(SSSDBG_TRACE_FUNC,
-                              ("Group search base contains filter, "
-                               "dereference will be disabled\n"));
-                        enable_deref = false;
-                        break;
-                    }
-                }
-            }
-
-            subreq = sdap_nested_group_process_send(state,
-                                                    state->ev,
-                                                    state->dom,
-                                                    state->sysdb,
-                                                    state->groups[0],
-                                                    state->user_hash,
-                                                    state->group_hash,
-                                                    state->opts,
-                                                    state->sh,
-                                                    enable_deref,
-                                                    0);
+            subreq = sdap_nested_group_send(state, state->ev, state->dom,
+                                            state->opts, state->sh,
+                                            state->groups[0]);
             if (!subreq) {
                 tevent_req_error(req, EIO);
                 return;
@@ -1956,11 +1899,8 @@ int sdap_get_groups_recv(struct tevent_req *req,
 static void sdap_nested_done(struct tevent_req *subreq)
 {
     errno_t ret, tret;
-    int hret;
-    unsigned long i;
     unsigned long user_count;
     unsigned long group_count;
-    hash_value_t *values;
     bool in_transaction = false;
     struct sysdb_attrs **users = NULL;
     struct sysdb_attrs **groups = NULL;
@@ -1970,7 +1910,8 @@ static void sdap_nested_done(struct tevent_req *subreq)
     struct sdap_get_groups_state *state = tevent_req_data(req,
                                             struct sdap_get_groups_state);
 
-    ret = sdap_nested_group_process_recv(subreq);
+    ret = sdap_nested_group_recv(state, subreq, &user_count, &users,
+                                 &group_count, &groups);
     talloc_zfree(subreq);
     if (ret != EOK) {
         DEBUG(1, ("Nested group processing failed: [%d][%s]\n",
@@ -1978,45 +1919,6 @@ static void sdap_nested_done(struct tevent_req *subreq)
         goto fail;
     }
 
-    hret = hash_values(state->user_hash, &user_count, &values);
-    if (hret != HASH_SUCCESS) {
-        ret = EIO;
-        goto fail;
-    }
-
-    if (user_count) {
-        users = talloc_array(state, struct sysdb_attrs *, user_count);
-        if (!users) {
-            talloc_free(values);
-            ret = ENOMEM;
-            goto fail;
-        }
-
-        for (i = 0; i < user_count; i++) {
-            users[i] = talloc_get_type(values[i].ptr, struct sysdb_attrs);
-        }
-        talloc_zfree(values);
-    }
-
-    /* Users are all retrieved. Now retrieve groups */
-    hret = hash_values(state->group_hash, &group_count, &values);
-    if (hret != HASH_SUCCESS) {
-        ret = EIO;
-        goto fail;
-    }
-
-    groups = talloc_array(state, struct sysdb_attrs *, group_count);
-    if (!groups) {
-        talloc_free(values);
-        ret = ENOMEM;
-        goto fail;
-    }
-
-    for (i = 0; i < group_count; i++) {
-        groups[i] = talloc_get_type(values[i].ptr, struct sysdb_attrs);
-    }
-    talloc_zfree(values);
-
     /* Save all of the users first so that they are in
      * place for the groups to add them.
      */
@@ -2218,1611 +2120,3 @@ done:
     talloc_zfree(tmp_ctx);
     return ret;
 }
-
-/*
- * Get user based on a user DN. Optimized for environments where the containers
- * are strictly defined, such as IPA.
- */
-static void
-sdap_nested_get_user_done(struct tevent_req *subreq);
-static errno_t
-sdap_nested_get_ipa_user(TALLOC_CTX *mem_ctx, const char *user_dn,
-                         struct sysdb_ctx *sysdb, struct sysdb_attrs ***_reply);
-
-struct sdap_nested_get_user_state {
-    size_t count;
-    struct sysdb_attrs **replies;
-};
-
-static struct tevent_req *
-sdap_nested_get_user_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
-                          struct sss_domain_info *domain,
-                          struct sysdb_ctx *sysdb,
-                          struct sdap_options *opts,
-                          struct sdap_handle *sh,
-                          const char *user_dn,
-                          const char *search_bases_filter)
-{
-    errno_t ret;
-    struct tevent_req *req;
-    struct tevent_req *subreq;
-    const char **sdap_attrs;
-    const char *filter;
-    struct sdap_nested_get_user_state *state;
-
-    req = tevent_req_create(mem_ctx, &state,
-                            struct sdap_nested_get_user_state);
-    if (!req) {
-        return NULL;
-    }
-
-    if (opts->schema_type == SDAP_SCHEMA_IPA_V1) {
-        /* If the schema is IPA, then just shortcut and guess the name */
-        ret = sdap_nested_get_ipa_user(state, user_dn, sysdb, &state->replies);
-        if (ret == EOK) {
-            state->count = 1;
-            goto immediate;
-        } else {
-            DEBUG(SSSDBG_MINOR_FAILURE, ("Couldn't parse out user information "
-                  "based on DN %s, falling back to an LDAP lookup\n"));
-        }
-    }
-
-    /* Only pull down username and originalDN */
-    sdap_attrs = talloc_array(state, const char *, 3);
-    if (!sdap_attrs) {
-        ret = ENOMEM;
-        goto immediate;
-    }
-    sdap_attrs[0] = "objectClass";
-    sdap_attrs[1] = opts->user_map[SDAP_AT_USER_NAME].name;
-    sdap_attrs[2] = NULL;
-
-    if (search_bases_filter != NULL) {
-        filter = talloc_asprintf(sdap_attrs, "(&%s(objectclass=%s))",
-                                 search_bases_filter,
-                                 opts->user_map[SDAP_OC_USER].name);
-    } else {
-        filter = talloc_asprintf(sdap_attrs, "(objectclass=%s)",
-                                 opts->user_map[SDAP_OC_USER].name);
-    }
-    if (!filter) {
-        ret = ENOMEM;
-        goto immediate;
-    }
-
-    subreq = sdap_get_generic_send(state, ev, opts, sh, user_dn,
-                                   LDAP_SCOPE_BASE, filter, sdap_attrs,
-                                   opts->user_map, SDAP_OPTS_USER,
-                                   dp_opt_get_int(opts->basic,
-                                                  SDAP_SEARCH_TIMEOUT),
-                                   false);
-    if (!subreq) {
-        ret = EIO;
-        goto immediate;
-    }
-    talloc_steal(subreq, sdap_attrs);
-
-    tevent_req_set_callback(subreq, sdap_nested_get_user_done, req);
-    return req;
-
-immediate:
-    if (ret) {
-        tevent_req_error(req, ret);
-    } else {
-        tevent_req_done(req);
-    }
-    tevent_req_post(req, ev);
-    return req;
-}
-
-/* This should be a function pointer set from the IPA provider */
-static errno_t
-sdap_nested_get_ipa_user(TALLOC_CTX *mem_ctx, const char *user_dn,
-                         struct sysdb_ctx *sysdb, struct sysdb_attrs ***_reply)
-{
-    errno_t ret;
-    struct sysdb_attrs **reply = NULL;
-    struct sysdb_attrs *user = NULL;
-    char *name;
-    struct ldb_dn *dn = NULL;
-    const char *rdn_name;
-    const char *users_comp_name;
-    const char *acct_comp_name;
-    const struct ldb_val *rdn_val;
-    const struct ldb_val *users_comp_val;
-    const struct ldb_val *acct_comp_val;
-    TALLOC_CTX *tmp_ctx;
-
-    tmp_ctx = talloc_new(NULL);
-    if (!tmp_ctx) return ENOMEM;
-
-    dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(sysdb), user_dn);
-    if (dn == NULL) {
-        ret = ENOMEM;
-        goto done;
-    }
-
-    if (ldb_dn_get_comp_num(dn) < 4) {
-        /* RDN, users, accounts, and at least one DC=
-         * For example:
-         * uid=admin,cn=users,cn=accounts,dc=example,dc=com */
-        /* If it's fewer, it's not a user DN */
-        ret = ENOENT;
-        goto done;
-    }
-
-    /* If the RDN attribute name is 'uid' */
-    rdn_name = ldb_dn_get_rdn_name(dn);
-    if (rdn_name == NULL) {
-        /* Shouldn't happen if ldb_dn_validate()
-         * passed, but we'll be careful.
-         */
-        ret = EINVAL;
-        goto done;
-    }
-
-    if (strcasecmp("uid", rdn_name) != 0) {
-        /* RDN has the wrong attribute name.
-         * It's not a service.
-         */
-        ret = ENOENT;
-        goto done;
-    }
-
-    /* and the second component is "cn=users" */
-    users_comp_name = ldb_dn_get_component_name(dn, 1);
-    if (strcasecmp("cn", users_comp_name) != 0) {
-        /* The second component name is not "cn" */
-        ret = ENOENT;
-        goto done;
-    }
-
-    users_comp_val = ldb_dn_get_component_val(dn, 1);
-    if (strncasecmp("users",
-                    (const char *) users_comp_val->data,
-                    users_comp_val->length) != 0) {
-        /* The second component value is not "users" */
-        ret = ENOENT;
-        goto done;
-    }
-
-    /* and the third component is "cn=accounts" */
-    acct_comp_name = ldb_dn_get_component_name(dn, 2);
-    if (strcasecmp("cn", acct_comp_name) != 0) {
-        /* The third component name is not "cn" */
-        ret = ENOENT;
-        goto done;
-    }
-
-    acct_comp_val = ldb_dn_get_component_val(dn, 2);
-    if (strncasecmp("accounts",
-                    (const char *) acct_comp_val->data,
-                    acct_comp_val->length) != 0) {
-        /* The third component value is not "accounts" */
-        ret = ENOENT;
-        goto done;
-    }
-
-    /* Then the value of the RDN is the group name */
-    reply = talloc_zero_array(tmp_ctx, struct sysdb_attrs *, 2);
-    if (!reply) {
-        ret = ENOMEM;
-        goto done;
-    }
-
-    reply[0] = sysdb_new_attrs(reply);
-    if (!reply[0]) {
-        ret = ENOMEM;
-        goto done;
-    }
-    user = reply[0];
-
-    rdn_val = ldb_dn_get_rdn_val(dn);
-    name = talloc_strndup(user, (const char *)rdn_val->data,
-                          rdn_val->length);
-    if (name == NULL) {
-        ret = ENOMEM;
-        goto done;
-    }
-
-    ret = sysdb_attrs_add_string(user, SYSDB_NAME, name);
-    if (ret != EOK) {
-        goto done;
-    }
-
-    ret = sysdb_attrs_add_string(user, SYSDB_ORIG_DN, user_dn);
-    if (ret != EOK) {
-        goto done;
-    }
-
-    ret = sysdb_attrs_add_string(user, SYSDB_OBJECTCLASS, SYSDB_USER_CLASS);
-    if (ret != EOK) {
-        goto done;
-    }
-
-    ret = EOK;
-    *_reply = talloc_steal(mem_ctx, reply);
-done:
-    talloc_free(tmp_ctx);
-    return ret;
-}
-
-static void
-sdap_nested_get_user_done(struct tevent_req *subreq)
-{
-    errno_t ret;
-    struct tevent_req *req =
-            tevent_req_callback_data(subreq, struct tevent_req);
-    struct sdap_nested_get_user_state *state = tevent_req_data(req,
-                                           struct sdap_nested_get_user_state);
-
-    ret = sdap_get_generic_recv(subreq, state, &state->count, &state->replies);
-    talloc_zfree(subreq);
-    if (ret != EOK && ret != ENOENT) {
-        tevent_req_error(req, ret);
-        return;
-    }
-
-    tevent_req_done(req);
-}
-
-static errno_t
-sdap_nested_get_user_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
-                          size_t *_count, struct sysdb_attrs ***_replies)
-{
-    struct sdap_nested_get_user_state *state = tevent_req_data(req,
-                                            struct sdap_nested_get_user_state);
-
-    TEVENT_REQ_RETURN_ON_ERROR(req);
-
-    if (_count) {
-        *_count = state->count;
-    }
-
-    if (_replies) {
-        *_replies = talloc_steal(mem_ctx, state->replies);
-    }
-
-    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;
-    struct sss_domain_info *domain;
-
-    hash_table_t *users;
-    hash_table_t *groups;
-
-    struct sdap_options *opts;
-    struct sdap_handle *sh;
-
-    uint32_t nesting_level;
-
-    struct ldb_message_element *members;
-    uint32_t member_index;
-    char *member_dn;
-
-    bool enable_deref;
-    struct sdap_deref_ctx *derefctx;
-
-    /**
-     * FIXME: Remove me!
-     */
-    bool send_finished;
-};
-
-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,
-        struct sysdb_ctx *sysdb, struct sysdb_attrs *group,
-        hash_table_t *users, hash_table_t *groups,
-        struct sdap_options *opts, struct sdap_handle *sh,
-        bool enable_deref, uint32_t nesting)
-{
-    errno_t ret;
-    int hret;
-    struct tevent_req *req;
-    struct sdap_nested_group_ctx *state;
-    const char *groupname;
-    hash_key_t key;
-    hash_value_t value;
-    gid_t gid;
-
-    req = tevent_req_create(mem_ctx, &state, struct sdap_nested_group_ctx);
-    if (!req) {
-        return NULL;
-    }
-
-    state->ev = ev;
-    state->sysdb = sysdb;
-    state->domain = domain;
-    state->users = users;
-    state->groups = groups;
-    state->opts = opts;
-    state->sh = sh;
-    state->enable_deref = enable_deref;
-    state->nesting_level = nesting;
-    state->send_finished = false;
-
-    /* If this is too many levels deep, just return success */
-    if (nesting > dp_opt_get_int(opts->basic, SDAP_NESTING_LEVEL)) {
-        ret = EOK;
-        goto immediate;
-    }
-
-    /* Add the current group to the groups hash so we don't
-     * look it up more than once
-     */
-    key.type = HASH_KEY_STRING;
-
-    ret = sysdb_attrs_primary_name(sysdb, group,
-                                   opts->group_map[SDAP_AT_GROUP_NAME].sys_name,
-                                   &groupname);
-    if (ret != EOK) {
-        goto immediate;
-    }
-
-    key.str = talloc_strdup(state, groupname);
-    if (!key.str) {
-        ret = ENOMEM;
-        goto immediate;
-    }
-
-    if (hash_has_key(groups, &key)) {
-        /* This group has already been processed
-         * (or is in progress)
-         * Skip it and just return success
-         */
-        ret = EOK;
-        goto immediate;
-    }
-
-    ret = sysdb_attrs_get_uint32_t(group,
-                                   opts->group_map[SDAP_AT_GROUP_GID].sys_name,
-                                   &gid);
-    if (ret == ENOENT || (ret == EOK && gid == 0)) {
-        DEBUG(9, ("The group's gid was %s\n", ret == ENOENT ? "missing" : "zero"));
-        DEBUG(8, ("Marking group as non-posix and setting GID=0!\n"));
-
-        if (ret == ENOENT) {
-            ret = sysdb_attrs_add_uint32(group,
-                                      opts->group_map[SDAP_AT_GROUP_GID].sys_name,
-                                      0);
-            if (ret != EOK) {
-                DEBUG(1, ("Failed to add a GID to non-posix group!\n"));
-                goto immediate;
-            }
-        }
-
-        ret = sysdb_attrs_add_bool(group, SYSDB_POSIX, false);
-        if (ret != EOK) {
-            DEBUG(2, ("Error: Failed to mark group as non-posix!\n"));
-            goto immediate;
-        }
-    } else if (ret) {
-        goto immediate;
-    }
-
-    value.type = HASH_VALUE_PTR;
-    value.ptr = talloc_steal(groups, group);
-
-    hret = hash_enter(groups, &key, &value);
-    if (hret != HASH_SUCCESS) {
-        ret = EIO;
-        goto immediate;
-    }
-    talloc_free(key.str);
-
-    /* Process group memberships */
-
-    /* TODO: future enhancement, check for memberuid as well
-     * See https://fedorahosted.org/sssd/ticket/445
-     */
-
-    ret = sysdb_attrs_get_el(
-            group,
-            opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
-            &state->members);
-    if (ret != EOK) {
-        if (ret == ENOENT) {
-            /* No members to process */
-            ret = EOK;
-        }
-        goto immediate;
-    }
-
-    state->member_index = 0;
-
-    if (enable_deref && sdap_has_deref_support(state->sh, state->opts)) {
-        state->derefctx = talloc_zero(state, struct sdap_deref_ctx);
-        if (!state->derefctx) {
-            ret = ENOMEM;
-            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;
-    }
-
-    state->send_finished = true;
-    return req;
-
-immediate:
-    if (ret == EOK) {
-        tevent_req_done(req);
-    } else {
-        tevent_req_error(req, ret);
-    }
-    tevent_req_post(req, ev);
-    state->send_finished = true;
-    return req;
-}
-
-static errno_t sdap_nested_group_check_hash(struct sdap_nested_group_ctx *);
-static errno_t sdap_nested_group_check_cache(TALLOC_CTX *mem_ctx,
-                                    struct sysdb_ctx *sysdb,
-                                    struct sss_domain_info *dom,
-                                    struct sdap_options *opts,
-                                    char *member_dn,
-                                    struct ldb_message ***_msgs,
-                                    enum sysdb_member_type *_mtype);
-
-static void sdap_nested_group_process_ldap_user(struct tevent_req *subreq);
-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)
-{
-    errno_t ret;
-    struct sdap_nested_group_ctx *state =
-            tevent_req_data(req, struct sdap_nested_group_ctx);
-    struct ldb_message **msgs = NULL;
-    enum sysdb_member_type mtype;
-
-    while (true) {
-        /* 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);
-            return EOK; /* All members in hash */
-        } else if (ret != ENOENT) {
-            goto error; /* 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(6, ("Refreshing cached group from LDAP\n"));
-                ret = sdap_nested_group_lookup_group(req);
-                if (ret != EOK) goto error;
-                break;
-            case SYSDB_MEMBER_USER:
-                DEBUG(6, ("Refreshing cached user from LDAP\n"));
-                ret = sdap_nested_group_lookup_user(
-                        req, sdap_nested_group_process_user);
-                if (ret != EOK) goto error;
-                break;
-            default:
-                DEBUG(2, ("Unknown member value\n"));
-                ret = EINVAL;
-                goto error;
-            }
-
-            return EAGAIN;
-        } else if (ret == ENOENT) {
-            /* It wasn't found in the cache either
-             * We'll have to do a blind lookup in LDAP
-             */
-
-            /* Try users first */
-            ret = sdap_nested_group_lookup_user(
-                    req, sdap_nested_group_process_ldap_user);
-            if (ret != EOK) {
-                goto error;
-            }
-            return EAGAIN;
-        }
-
-        /* Unexpected error, skip this entry */
-        state->member_index++;
-        talloc_zfree(state->member_dn);
-        continue;
-    } /* while (true) */
-
-error:
-    talloc_zfree(state->member_dn);
-    return ret;
-}
-
-static errno_t
-sdap_nested_group_check_hash(struct sdap_nested_group_ctx *state)
-{
-    hash_key_t key;
-    bool has_key = false;
-    uint8_t *data;
-
-    do {
-        if (state->member_index >= state->members->num_values) {
-            /* No more entries to check. Return success */
-            return EOK;
-        }
-
-        data = state->members->values[state->member_index].data;
-        state->member_dn = talloc_strdup(state, (const char *)data);
-        if (!state->member_dn) {
-            return ENOMEM;
-        }
-
-        /* Check the user hash
-         * If it's there, we can save ourselves a trip to the
-         * sysdb and possibly LDAP as well
-         */
-        key.type = HASH_KEY_STRING;
-        key.str = state->member_dn;
-        has_key = hash_has_key(state->users, &key);
-        if (has_key) {
-            talloc_zfree(state->member_dn);
-            state->member_index++;
-            continue;
-        }
-    } while (has_key);
-
-    return ENOENT;
-}
-
-static errno_t
-sdap_nested_group_check_cache(TALLOC_CTX *mem_ctx,
-                              struct sysdb_ctx *sysdb,
-                              struct sss_domain_info *dom,
-                              struct sdap_options *opts,
-                              char *dn,
-                              struct ldb_message ***_msgs,
-                              enum sysdb_member_type *_mtype)
-{
-    TALLOC_CTX *tmp_ctx;
-    errno_t ret;
-    struct ldb_message **msgs = NULL;
-    char *member_dn;
-    uint64_t expiration;
-    uid_t user_uid;
-    time_t now = time(NULL);
-    static const char *attrs[] = { SYSDB_CACHE_EXPIRE, SYSDB_UIDNUM,
-                                   SYSDB_CREATE_TIME, SYSDB_NAME,
-                                   NULL };
-    char *filter;
-    enum sysdb_member_type mtype;
-    size_t count;
-
-    tmp_ctx = talloc_new(NULL);
-    if (!tmp_ctx) return ENOMEM;
-
-    ret = sss_filter_sanitize(tmp_ctx, dn, &member_dn);
-    if (ret != EOK) {
-        goto fail;
-    }
-
-    /* Check for the specified origDN in the sysdb */
-    filter = talloc_asprintf(tmp_ctx, "(%s=%s)",
-                             SYSDB_ORIG_DN,
-                             member_dn);
-    if (!filter) {
-        ret = ENOMEM;
-        goto fail;
-    }
-
-    /* Try users first */
-    ret = sysdb_search_users(tmp_ctx, sysdb, dom, filter, attrs, &count, &msgs);
-    if (ret != EOK && ret != ENOENT) {
-        ret = EIO;
-        goto fail;
-    } else if (ret == EOK && count > 0) {
-        /* We found a user with this origDN in the sysdb. Check if it is valid
-         */
-        mtype = SYSDB_MEMBER_USER;
-
-        /* Check whether the entry is valid */
-        if (count != 1) {
-            DEBUG(1, ("More than one entry with this origDN? Skipping\n"));
-            ret = EIO;
-            goto fail;
-        }
-
-        user_uid = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_UIDNUM, 0);
-        if (!user_uid) {
-            DEBUG(SSSDBG_OP_FAILURE, ("User with no UID? Skipping\n"));
-            ret = EIO;
-            goto fail;
-        } else {
-            /* Regular user, check if we need a refresh */
-            expiration = ldb_msg_find_attr_as_uint64(msgs[0],
-                                                    SYSDB_CACHE_EXPIRE,
-                                                    0);
-        }
-
-        if (expiration && expiration > now) {
-            DEBUG(6, ("Cached values are still valid. Skipping\n"));
-            ret = EOK;
-            goto done;
-        }
-
-        /* Refresh the user from LDAP */
-        ret = EAGAIN;
-        goto done;
-    }
-
-    /* It wasn't a user. Check whether it's a group */
-    if (ret == EOK) talloc_zfree(msgs);
-
-    ret = sysdb_search_groups(tmp_ctx, sysdb, dom,
-                              filter, attrs, &count, &msgs);
-    if (ret != EOK && ret != ENOENT) {
-        ret = EIO;
-        goto fail;
-    } else if (ret == EOK && count > 0) {
-        /* We found a group with this origDN in the sysdb */
-        mtype = SYSDB_MEMBER_GROUP;
-
-        /* Check whether the entry is valid */
-        if (count != 1) {
-            DEBUG(1, ("More than one entry with this origDN? Skipping\n"));
-            ret = EIO;
-            goto fail;
-        }
-
-        expiration = ldb_msg_find_attr_as_uint64(msgs[0],
-                                                 SYSDB_CACHE_EXPIRE,
-                                                 0);
-        if (expiration && expiration > now) {
-            DEBUG(6, ("Cached values are still valid.\n"));
-            ret = EOK;
-            goto done;
-        }
-
-        /* Refresh the group from LDAP */
-        ret = EAGAIN;
-        goto done;
-    }
-
-    /* It wasn't found in the groups either */
-    ret = ENOENT;
-done:
-    if (ret == EOK || ret == EAGAIN) {
-        *_msgs = talloc_steal(mem_ctx, msgs);
-        *_mtype = mtype;
-    }
-    talloc_zfree(tmp_ctx);
-    return ret;
-
-fail:
-    talloc_zfree(tmp_ctx);
-    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;
-    size_t attr_count;
-    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,
-                               NULL, &sdap_attrs, &attr_count);
-    if (ret != EOK) goto fail;
-
-    sdap_attrs = talloc_realloc(NULL, sdap_attrs, const char *,
-                                attr_count + 2);
-    if (!sdap_attrs) {
-        ret = ENOMEM;
-        goto fail;
-    }
-
-    sdap_attrs[attr_count] = \
-                        state->opts->user_map[SDAP_AT_USER_NAME].name;
-    sdap_attrs[attr_count + 1] = NULL;
-
-    timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_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)
-{
-    char *search_bases_filter = NULL;
-    struct tevent_req *subreq;
-    struct sdap_nested_group_ctx *state =
-            tevent_req_data(req, struct sdap_nested_group_ctx);
-    errno_t ret;
-
-    /*
-     * If dn is not in user search base and object may be group
-     * continue with group lookup. If it can't be group, skip it.
-     */
-    if (!sss_ldap_dn_in_search_bases(state, state->member_dn,
-                                     state->opts->user_search_bases,
-                                     &search_bases_filter)) {
-        if (fn == sdap_nested_group_process_ldap_user) {
-            return sdap_nested_group_lookup_group(req);
-        } else if (fn == sdap_nested_group_process_user) {
-            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 && ret != EAGAIN) {
-                DEBUG(SSSDBG_OP_FAILURE, ("Nested group processing failed\n"));
-                return ret;
-            } else if (ret == EOK) {
-                DEBUG(SSSDBG_TRACE_FUNC, ("All done.\n"));
-                tevent_req_done(req);
-
-                /**
-                 * FIXME: Rewrite nested group processing so we call
-                 *        tevent_req_post() only in _send().
-                 */
-                if (state->send_finished == false) {
-                    tevent_req_post(req, state->ev);
-                }
-            }
-            return EOK;
-        }
-        /*
-         * Something else? Continue.
-         */
-    }
-
-    subreq = sdap_nested_get_user_send(state, state->ev, state->domain,
-                                       state->sysdb, state->opts, state->sh,
-                                       state->member_dn, search_bases_filter);
-    if (!subreq) {
-        return EIO;
-    }
-    talloc_steal(subreq, search_bases_filter);
-    tevent_req_set_callback(subreq, fn, req);
-    return EOK;
-}
-
-static void sdap_nested_group_process_group(struct tevent_req *subreq);
-static errno_t sdap_nested_group_lookup_group(struct tevent_req *req)
-{
-    errno_t ret;
-    const char **sdap_attrs;
-    char *filter;
-    char *search_bases_filter = NULL;
-    struct tevent_req *subreq;
-    struct sdap_nested_group_ctx *state =
-            tevent_req_data(req, struct sdap_nested_group_ctx);
-
-    /*
-     * If dn is not in group search base, skip it.
-     */
-    if (!sss_ldap_dn_in_search_bases(state, state->member_dn,
-                                     state->opts->group_search_bases,
-                                     &search_bases_filter)) {
-        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++;
-            }
-            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 && ret != EAGAIN) {
-            DEBUG(SSSDBG_OP_FAILURE, ("Nested group processing failed\n"));
-            return ret;
-        } else if (ret == EOK) {
-            DEBUG(SSSDBG_TRACE_FUNC, ("All done.\n"));
-            tevent_req_done(req);
-
-            /**
-             * FIXME: Rewrite nested group processing so we call
-             *        tevent_req_post() only in _send().
-             */
-            if (state->send_finished == false) {
-                tevent_req_post(req, state->ev);
-            }
-        }
-        return EOK;
-    }
-
-    ret = build_attrs_from_map(state, state->opts->group_map, SDAP_OPTS_GROUP,
-                               NULL, &sdap_attrs, NULL);
-    if (ret != EOK) {
-        return ret;
-    }
-
-    if (search_bases_filter != NULL) {
-        filter = talloc_asprintf(sdap_attrs, "(&%s(objectclass=%s)(%s=*))",
-                                 search_bases_filter,
-                                 state->opts->group_map[SDAP_OC_GROUP].name,
-                                 state->opts->group_map[SDAP_AT_GROUP_NAME].name);
-    } else {
-        filter = talloc_asprintf(sdap_attrs, "(&(objectclass=%s)(%s=*))",
-                                 state->opts->group_map[SDAP_OC_GROUP].name,
-                                 state->opts->group_map[SDAP_AT_GROUP_NAME].name);
-    }
-    if (!filter) {
-        talloc_free(sdap_attrs);
-        return ENOMEM;
-    }
-
-    subreq = sdap_get_generic_send(state, state->ev, state->opts,
-                                   state->sh, state->member_dn,
-                                   LDAP_SCOPE_BASE,
-                                   filter, sdap_attrs,
-                                   state->opts->group_map,
-                                   SDAP_OPTS_GROUP,
-                                   dp_opt_get_int(state->opts->basic,
-                                                  SDAP_SEARCH_TIMEOUT),
-                                   false);
-    if (!subreq) {
-        talloc_free(sdap_attrs);
-        return EIO;
-    }
-    talloc_steal(subreq, sdap_attrs);
-
-    tevent_req_set_callback(subreq, sdap_nested_group_process_group, req);
-    return EOK;
-}
-
-static void sdap_nested_group_process_user(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);
-    TALLOC_CTX *tmp_ctx;
-    size_t count = 0;
-    struct sysdb_attrs **replies = NULL;
-    int hret;
-    hash_key_t key;
-    hash_value_t value;
-
-    tmp_ctx = talloc_new(NULL);
-    if (!tmp_ctx) {
-        tevent_req_error(req, ENOMEM);
-        return;
-    }
-
-    ret = sdap_nested_get_user_recv(subreq, tmp_ctx, &count, &replies);
-    talloc_zfree(subreq);
-    if (ret != EOK && ret != ENOENT) {
-        tevent_req_error(req, ret);
-        goto done;
-    } else if (ret == ENOENT || count == 0) {
-        /* Nothing to do if the user doesn't exist */
-        goto skip;
-    }
-
-    if (count != 1) {
-        /* There should only ever be one reply for a
-         * BASE search. If otherwise, it's a serious
-         * error.
-         */
-        DEBUG(1,("Received multiple replies for a BASE search!\n"));
-        tevent_req_error(req, EIO);
-        goto done;
-    }
-
-    /* Save the user attributes to the user hash so we can store
-     * them all at once later.
-     */
-
-    key.type = HASH_KEY_STRING;
-    key.str = state->member_dn;
-
-    value.type = HASH_VALUE_PTR;
-    value.ptr = replies[0];
-
-    hret = hash_enter(state->users, &key, &value);
-    if (hret != HASH_SUCCESS) {
-        tevent_req_error(req, EIO);
-        goto done;
-    }
-    talloc_steal(state->users, replies[0]);
-
-skip:
-    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);
-    } else if (ret != EAGAIN) {
-        tevent_req_error(req, ret);
-    }
-
-    /* EAGAIN means that we should re-enter
-     * the mainloop
-     */
-
-done:
-    talloc_free(tmp_ctx);
-}
-
-static void sdap_group_internal_nesting_done(struct tevent_req *subreq);
-static void sdap_nested_group_process_group(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);
-    TALLOC_CTX *tmp_ctx;
-    size_t count;
-    struct sysdb_attrs **replies;
-
-    tmp_ctx = talloc_new(NULL);
-    if (!tmp_ctx) {
-        tevent_req_error(req, ENOMEM);
-        return;
-    }
-
-    ret = sdap_get_generic_recv(subreq, tmp_ctx, &count, &replies);
-    talloc_zfree(subreq);
-    if (ret != EOK && ret != ENOENT) {
-        tevent_req_error(req, ret);
-        goto done;
-    } else if (ret == ENOENT || count == 0) {
-        /* Nothing to do if the group doesn't exist */
-        goto skip;
-    }
-
-    if (count != 1) {
-        /* There should only ever be one reply for a
-         * BASE search. If otherwise, it's a serious
-         * error.
-         */
-        DEBUG(1,("Received multiple replies for a BASE search!\n"));
-        tevent_req_error(req, EIO);
-        goto done;
-    }
-
-    /* Recurse down into the member group */
-    subreq = sdap_nested_group_process_send(state, state->ev, state->domain,
-                                            state->sysdb, replies[0],
-                                            state->users, state->groups,
-                                            state->opts, state->sh,
-                                            state->enable_deref,
-                                            state->nesting_level + 1);
-    if (!subreq) {
-        tevent_req_error(req, EIO);
-        goto done;
-    }
-    tevent_req_set_callback(subreq, sdap_group_internal_nesting_done, req);
-
-    talloc_free(tmp_ctx);
-    return;
-
-skip:
-    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++;
-        }
-        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);
-    } else if (ret != EAGAIN) {
-        tevent_req_error(req, ret);
-    }
-
-    /* EAGAIN means that we should re-enter
-     * the mainloop
-     */
-
-done:
-    talloc_free(tmp_ctx);
-}
-
-static void sdap_group_internal_nesting_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;
-    }
-
-    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);
-    } else if (ret != EAGAIN) {
-        tevent_req_error(req, ret);
-    }
-
-    /* EAGAIN means that we should re-enter
-     * the mainloop
-     */
-}
-
-static void sdap_nested_group_process_ldap_user(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);
-    TALLOC_CTX *tmp_ctx;
-    size_t count = 0;
-    struct sysdb_attrs **replies = NULL;
-    int hret;
-    hash_key_t key;
-    hash_value_t value;
-
-    tmp_ctx = talloc_new(NULL);
-    if (!tmp_ctx) {
-        tevent_req_error(req, ENOMEM);
-        return;
-    }
-
-    ret = sdap_nested_get_user_recv(subreq, tmp_ctx, &count, &replies);
-    talloc_zfree(subreq);
-    if (ret != EOK && ret != ENOENT) {
-        tevent_req_error(req, ret);
-        goto done;
-    } else if (ret == ENOENT || count == 0) {
-        /* No user found. Assume it's a group */
-        ret = sdap_nested_group_lookup_group(req);
-        if (ret != EOK) {
-            tevent_req_error(req, ret);
-        }
-        goto done;
-    }
-
-    if (count != 1) {
-        /* There should only ever be one reply for a
-         * BASE search. If otherwise, it's a serious
-         * error.
-         */
-        DEBUG(1,("Received multiple replies for a BASE search!\n"));
-        tevent_req_error(req, EIO);
-        goto done;
-    }
-
-    /* Save the user attributes to the user hash so we can store
-     * them all at once later.
-     */
-    key.type = HASH_KEY_STRING;
-    key.str = state->member_dn;
-
-    value.type = HASH_VALUE_PTR;
-    value.ptr = replies[0];
-
-    hret = hash_enter(state->users, &key, &value);
-    if (hret != HASH_SUCCESS) {
-        tevent_req_error(req, EIO);
-        goto done;
-    }
-    talloc_steal(state->users, replies[0]);
-
-    /* Move on to the next member */
-    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);
-    } else if (ret != EAGAIN) {
-        tevent_req_error(req, ret);
-    }
-
-    /* EAGAIN means that we should re-enter
-     * the mainloop
-     */
-
-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 == NULL) {
-        /* 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;
-    const char *tmp_name;
-    size_t i;
-
-    while (dctx->result_index < dctx->num_results) {
-        /* 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;
-        }
-
-        /* Ensure that all members returned from the deref request are included
-         * in the member processing. Sometimes we will get more results back from
-         * deref/asq than we got from the initial lookup, as is the case with
-         * Active Directory and its range retrieval mechanism.
-         */
-        for (i = 0; i < state->members->num_values; i++) {
-            /* FIXME: This is inefficient for very large sets of groups */
-            if (strcasecmp((const char *)state->members->values[i].data,
-                           orig_dn) == 0) break;
-        }
-        if (i >= state->members->num_values) {
-            state->members->values = talloc_realloc(state,
-                                                    state->members->values,
-                                                    struct ldb_val,
-                                                    state->members->num_values + 1);
-            if (!state->members->values) {
-                return ENOMEM;
-            }
-            state->members->values[state->members->num_values].data =
-                    (uint8_t *)talloc_strdup(state->members->values, orig_dn);
-            if (!state->members->values[state->members->num_values].data) {
-                return ENOMEM;
-            }
-            state->members->values[state->members->num_values].length = strlen(orig_dn);
-            state->members->num_values++;
-        }
-
-        if (dctx->deref_result[dctx->result_index]->map == \
-            state->opts->user_map) {
-
-            /* check if the user is in search base */
-            if (!sss_ldap_dn_in_search_bases(state, orig_dn,
-                                             state->opts->user_search_bases,
-                                             NULL)) {
-                dctx->result_index++;
-                continue;
-            }
-
-            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) {
-            ret = sysdb_attrs_get_string(dctx->deref_result[dctx->result_index]->attrs,
-                                       state->opts->group_map[SDAP_AT_GROUP_NAME].sys_name,
-                                       &tmp_name);
-            if (ret == ENOENT) {
-                DEBUG(7, ("Dereferenced a group without name, skipping ...\n"));
-            } else if (ret) {
-                return EIO;
-            }
-
-            /* check if the group is in search base */
-            if (!sss_ldap_dn_in_search_bases(state, orig_dn,
-                                             state->opts->group_search_bases,
-                                             NULL)) {
-                dctx->result_index++;
-                continue;
-            }
-
-            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->enable_deref,
-                                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);
-
-    return EOK;
-}
diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h
index 156255b97a67973bb0bba690d18d934a9d18a026..ea0616dff4450598d242d9848d6e3540e54274c6 100644
--- a/src/providers/ldap/sdap_async_private.h
+++ b/src/providers/ldap/sdap_async_private.h
@@ -120,4 +120,21 @@ errno_t get_sysdb_grouplist(TALLOC_CTX *mem_ctx,
                             struct sss_domain_info *domain,
                             const char *name,
                             char ***grouplist);
+
+/* from sdap_async_nested_groups.c */
+
+struct tevent_req *sdap_nested_group_send(TALLOC_CTX *mem_ctx,
+                                          struct tevent_context *ev,
+                                          struct sss_domain_info *domain,
+                                          struct sdap_options *opts,
+                                          struct sdap_handle *sh,
+                                          struct sysdb_attrs *group);
+
+errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx,
+                               struct tevent_req *req,
+                               unsigned long *_num_users,
+                               struct sysdb_attrs ***_users,
+                               unsigned long *_num_groups,
+                               struct sysdb_attrs ***_groups);
+
 #endif /* _SDAP_ASYNC_PRIVATE_H_ */
-- 
1.7.11.7

