dpward / rpms / sssd

Forked from rpms/sssd 3 years ago
Clone
Blob Blame History Raw
From 2341059ba000d4fa87691f84bf3c39822b38aecb Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Tue, 18 Oct 2016 18:18:44 +0200
Subject: [PATCH 146/151] LDAP/AD: resolve domain local groups for remote users

If a user from a trusted domain in the same forest is a direct or
indirect member of domain local groups from the local domain those
memberships must be resolved as well. Since those domain local groups
are not valid in the trusted domain a DC from the trusted domain which
is used to lookup the user data is not aware of them. As a consequence
those memberships must be resolved against a local DC in a second step.

Resolves https://fedorahosted.org/sssd/ticket/3206

Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
(cherry picked from commit 25699846bd1c9f8bb513b6271eb4366ab682fbd2)
---
 src/db/sysdb.h                                |   2 +
 src/providers/ldap/sdap_async_initgroups.c    | 158 +++++++++-
 src/providers/ldap/sdap_async_initgroups_ad.c | 407 ++++++++++++++++++++++++++
 src/providers/ldap/sdap_async_private.h       |  10 +
 4 files changed, 570 insertions(+), 7 deletions(-)

diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 4164657c2b329a240d46fe3ecdfb4b2eefffc5b3..a0279fb249e1258c9cb73a4fcab55e4b242c61f3 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -224,6 +224,8 @@
                         SYSDB_OVERRIDE_DN, \
                         SYSDB_OVERRIDE_OBJECT_DN, \
                         SYSDB_DEFAULT_OVERRIDE_NAME, \
+                        SYSDB_UUID, \
+                        SYSDB_ORIG_DN, \
                         NULL}
 
 #define SYSDB_GRSRC_ATTRS {SYSDB_NAME, SYSDB_GIDNUM, \
diff --git a/src/providers/ldap/sdap_async_initgroups.c b/src/providers/ldap/sdap_async_initgroups.c
index f1ba65a98b07a593b0e3a722d0b2c56c8e4b821e..8eaba261c49082d086df9f19464ac0f40fae71fb 100644
--- a/src/providers/ldap/sdap_async_initgroups.c
+++ b/src/providers/ldap/sdap_async_initgroups.c
@@ -2317,6 +2317,7 @@ static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req)
     struct sdap_rfc2307bis_nested_ctx *state =
             tevent_req_data(req, struct sdap_rfc2307bis_nested_ctx);
     char *oc_list;
+    const char *class;
 
     tmp_ctx = talloc_new(state);
     if (!tmp_ctx) {
@@ -2324,9 +2325,21 @@ static errno_t rfc2307bis_nested_groups_step(struct tevent_req *req)
         goto done;
     }
 
-    ret = sdap_get_group_primary_name(state, state->opts,
-                                      state->groups[state->group_iter],
-                                      state->dom, &state->primary_name);
+    ret = sysdb_attrs_get_string(state->groups[state->group_iter],
+                                 SYSDB_OBJECTCLASS, &class);
+    if (ret == EOK) {
+        /* If there is a objectClass attribute the object is coming from the
+         * cache and the name attribute of the object already has the primary
+         * name.
+         * If the objectClass attribute is missing the object is coming from
+         * LDAP and we have to find the primary name first. */
+        ret = sysdb_attrs_get_string(state->groups[state->group_iter],
+                                     SYSDB_NAME, &state->primary_name);
+    } else {
+        ret = sdap_get_group_primary_name(state, state->opts,
+                                          state->groups[state->group_iter],
+                                          state->dom, &state->primary_name);
+    }
     if (ret != EOK) {
         goto done;
     }
@@ -3069,6 +3082,103 @@ fail:
     tevent_req_error(req, ret);
 }
 
+static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq);
+
+errno_t sdap_ad_check_domain_local_groups(struct tevent_req *req)
+{
+    struct sdap_get_initgr_state *state = tevent_req_data(req,
+                                               struct sdap_get_initgr_state);
+    int ret;
+    struct sdap_domain *local_sdom;
+    const char *orig_name;
+    const char *sysdb_name;
+    struct ldb_result *res;
+    struct tevent_req *subreq;
+    struct sysdb_attrs **groups;
+
+    /* We only need to check for domain local groups in the AD case and if the
+     * user is not from our domain, i.e. if the user comes from a sub-domain.
+     */
+    if (state->opts->schema_type != SDAP_SCHEMA_AD
+            || !IS_SUBDOMAIN(state->dom)
+            || !dp_target_enabled(state->id_ctx->be->provider, "ad", DPT_ID)) {
+        return EOK;
+    }
+
+    local_sdom = sdap_domain_get(state->id_ctx->opts, state->dom->parent);
+    if (local_sdom == NULL || local_sdom->pvt == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "No ID ctx available for [%s].\n",
+                                    state->dom->parent->name);
+        return EINVAL;
+    }
+
+    ret = sysdb_attrs_get_string(state->orig_user, SYSDB_NAME, &orig_name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing name in user object.\n");
+        return ret;
+    }
+
+    sysdb_name = sss_create_internal_fqname(state, orig_name, state->dom->name);
+    if (sysdb_name == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_create_internal_fqname failed.\n");
+        return ENOMEM;
+    }
+
+    ret = sysdb_initgroups(state, state->dom, sysdb_name, &res);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_initgroups failed for user [%s].\n",
+                                   sysdb_name);
+        return ret;
+    }
+
+    if (res->count == 0) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "sysdb_initgroups returned no results for user [%s].\n",
+              sysdb_name);
+        return EINVAL;
+    }
+
+    /* The user object, the first entry in the res->msgs, is included as well
+     * to cover the case where the remote user is directly added to
+     * a domain local group. */
+    ret = sysdb_msg2attrs(state, res->count, res->msgs, &groups);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sysdb_msg2attrs failed.\n");
+        return ret;
+    }
+
+    subreq = sdap_ad_get_domain_local_groups_send(state, state->ev, local_sdom,
+                             state->opts, state->sysdb, state->dom->parent,
+                             groups, res->count);
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_get_domain_local_groups_send failed.\n");
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(subreq, sdap_ad_check_domain_local_groups_done,
+                            req);
+
+    return EAGAIN;
+}
+
+static void sdap_ad_check_domain_local_groups_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    int ret;
+
+    ret = sdap_ad_get_domain_local_groups_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+
+    return;
+}
+
 static void sdap_get_initgr_pgid(struct tevent_req *req);
 static void sdap_get_initgr_done(struct tevent_req *subreq)
 {
@@ -3201,8 +3311,6 @@ static void sdap_get_initgr_done(struct tevent_req *subreq)
     if (ret == EOK) {
         DEBUG(SSSDBG_TRACE_FUNC,
               "Primary group already cached, nothing to do.\n");
-        ret = EOK;
-        goto done;
     } else {
         gid = talloc_asprintf(state, "%lu", (unsigned long)primary_gid);
         if (gid == NULL) {
@@ -3219,10 +3327,28 @@ static void sdap_get_initgr_done(struct tevent_req *subreq)
             goto done;
         }
         tevent_req_set_callback(subreq, sdap_get_initgr_pgid, req);
+
+        talloc_free(tmp_ctx);
+        return;
     }
 
-    talloc_free(tmp_ctx);
-    return;
+    ret = sdap_ad_check_domain_local_groups(req);
+    if (ret == EAGAIN) {
+        DEBUG(SSSDBG_TRACE_ALL,
+              "Checking for domain local group memberships.\n");
+        talloc_free(tmp_ctx);
+        return;
+    } else if (ret == EOK) {
+        DEBUG(SSSDBG_TRACE_ALL,
+              "No need to check for domain local group memberships.\n");
+    } else {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sdap_ad_check_domain_local_groups failed, "
+              "meberships to domain local groups might be missing.\n");
+        /* do not let the request fail completely because we already have at
+         * least "some" groups */
+        ret = EOK;
+    }
 
 done:
     talloc_free(tmp_ctx);
@@ -3247,7 +3373,25 @@ static void sdap_get_initgr_pgid(struct tevent_req *subreq)
         return;
     }
 
+    ret = sdap_ad_check_domain_local_groups(req);
+    if (ret == EAGAIN) {
+        DEBUG(SSSDBG_TRACE_ALL,
+              "Checking for domain local group memberships.\n");
+        return;
+    } else if (ret == EOK) {
+        DEBUG(SSSDBG_TRACE_ALL,
+              "No need to check for domain local group memberships.\n");
+    } else {
+        DEBUG(SSSDBG_OP_FAILURE, "sdap_ad_check_domain_local_groups failed.\n");
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sdap_ad_check_domain_local_groups failed, "
+              "meberships to domain local groups might be missing.\n");
+        /* do not let the request fail completely because we already have at
+         * least "some" groups */
+    }
+
     tevent_req_done(req);
+    return;
 }
 
 int sdap_get_initgr_recv(struct tevent_req *req)
diff --git a/src/providers/ldap/sdap_async_initgroups_ad.c b/src/providers/ldap/sdap_async_initgroups_ad.c
index ad54c1fb837eb4b8e123a1c230feb697eb37bb41..1fee4ab43a6c13803a088ffa4695dde7f39b3d2b 100644
--- a/src/providers/ldap/sdap_async_initgroups_ad.c
+++ b/src/providers/ldap/sdap_async_initgroups_ad.c
@@ -1412,6 +1412,413 @@ static errno_t sdap_ad_tokengroups_initgr_posix_recv(struct tevent_req *req)
     return EOK;
 }
 
+struct sdap_ad_get_domain_local_groups_state {
+    struct tevent_context *ev;
+    struct sdap_id_conn_ctx *conn;
+    struct sdap_options *opts;
+    struct sdap_id_op *op;
+    struct sysdb_ctx *sysdb;
+    struct sss_domain_info *dom;
+    int dp_error;
+
+    struct sdap_search_base **search_bases;
+    struct sysdb_attrs **groups;
+    size_t num_groups;
+    hash_table_t *group_hash;
+};
+
+static void
+sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq);
+static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *
+sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     struct sdap_domain *local_sdom,
+                                     struct sdap_options *opts,
+                                     struct sysdb_ctx *sysdb,
+                                     struct sss_domain_info *dom,
+                                     struct sysdb_attrs **groups,
+                                     size_t num_groups)
+{
+    struct sdap_ad_get_domain_local_groups_state *state;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct ad_id_ctx *ad_id_ctx;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state,
+                            struct sdap_ad_get_domain_local_groups_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+        return NULL;
+    }
+
+    state->ev = ev;
+    ad_id_ctx = talloc_get_type(local_sdom->pvt, struct ad_id_ctx);
+    state->conn = ad_id_ctx->ldap_ctx;
+    state->opts = opts;
+    state->sysdb = sysdb;
+    state->dom = dom;
+    state->search_bases = state->conn->id_ctx->opts->sdom->group_search_bases;
+    state->groups = groups;
+    state->num_groups = num_groups;
+
+    ret = sss_hash_create(state, 32, &state->group_hash);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_hash_create failed.\n");
+        goto fail;
+    }
+
+    state->op = sdap_id_op_create(state, state->conn->conn_cache);
+    if (state->op == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    subreq = sdap_id_op_connect_send(state->op, state, &ret);
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_connect_send failed.\n");
+        goto fail;
+    }
+
+    tevent_req_set_callback(subreq,
+                            sdap_ad_get_domain_local_groups_connect_done, req);
+
+    return req;
+
+fail:
+    tevent_req_error(req, ret);
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void
+sdap_ad_get_domain_local_groups_connect_done(struct tevent_req *subreq)
+{
+
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req,
+                                  struct sdap_ad_get_domain_local_groups_state);
+    int dp_error = DP_ERR_FATAL;
+    int ret;
+
+    ret = sdap_id_op_connect_recv(subreq, &dp_error);
+    talloc_zfree(subreq);
+
+    if (ret != EOK) {
+        state->dp_error = dp_error;
+        tevent_req_error(req, ret);
+        return;
+    }
+    subreq = rfc2307bis_nested_groups_send(state, state->ev, state->opts,
+                                           state->sysdb, state->dom,
+                                           sdap_id_op_handle(state->op),
+                                           state->search_bases,
+                                           state->groups, state->num_groups,
+                                           state->group_hash, 0);
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "rfc2307bis_nested_groups_send failed.\n");
+        state->dp_error = DP_ERR_FATAL;
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    tevent_req_set_callback(subreq,
+                            sdap_ad_get_domain_local_groups_done, req);
+
+    return;
+}
+
+struct sdap_nested_group {
+    struct sysdb_attrs *group;
+    struct sysdb_attrs **ldap_parents;
+    size_t parents_count;
+};
+
+static errno_t
+sdap_ad_get_domain_local_groups_parse_parents(TALLOC_CTX *mem_ctx,
+                                              struct sdap_nested_group *gr,
+                                              struct sss_domain_info *dom,
+                                              struct sysdb_ctx *sysdb,
+                                              struct sdap_options *opts,
+                                              const char **_sysdb_name,
+                                              enum sysdb_member_type *_type,
+                                              char ***_add_list,
+                                              char ***_del_list)
+{
+    int ret;
+    size_t c;
+    char **groupnamelist = NULL;
+    struct sysdb_attrs *groups[1];
+    enum sysdb_member_type type;
+    const char *sysdb_name;
+    const char *group_name;
+    const char *class;
+    struct sss_domain_info *obj_dom;
+    char *local_groups_base_dn;
+    char **cached_local_parents = NULL;
+    char **add_list = NULL;
+    char **del_list = NULL;
+    TALLOC_CTX *tmp_ctx;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+        return ENOMEM;
+    }
+
+    local_groups_base_dn = talloc_asprintf(tmp_ctx, SYSDB_TMPL_GROUP_BASE,
+                                           dom->name);
+    if (local_groups_base_dn == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (gr->parents_count != 0) {
+        /* Store the parents if needed */
+        ret = sdap_nested_groups_store(sysdb, dom, opts,
+                                       gr->ldap_parents, gr->parents_count);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n",
+                      ret, strerror(ret));
+            goto done;
+        }
+
+        ret = sysdb_attrs_primary_fqdn_list(dom, tmp_ctx,
+                                    gr->ldap_parents, gr->parents_count,
+                                    opts->group_map[SDAP_AT_GROUP_NAME].name,
+                                    &groupnamelist);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_primary_fqdn_list failed.\n");
+            goto done;
+        }
+    }
+
+    ret = sysdb_attrs_get_string(gr->group, SYSDB_NAME, &sysdb_name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sysdb_attrs_get_string failed to get SYSDB_NAME, "
+              "skipping.\n");
+        goto done;
+    }
+
+    ret = sysdb_attrs_get_string(gr->group, SYSDB_OBJECTCLASS, &class);
+    if (ret != EOK) {
+        /* If objectclass is missing gr->group is a nested parent found during
+         * the nested group lookup. It might not already stored in the cache.
+         */
+        DEBUG(SSSDBG_TRACE_LIBS,
+              "sysdb_attrs_get_string failed to get SYSDB_OBJECTCLASS "
+              "for [%s], assuming group.\n", sysdb_name);
+
+        /* make sure group exists in cache */
+        groups[0]= gr->group;
+        ret = sdap_nested_groups_store(sysdb, dom, opts, groups, 1);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE, "Could not save groups [%d]: %s\n",
+                      ret, strerror(ret));
+            goto done;
+        }
+
+        /* Since the object is coming from LDAP it cannot have the internal
+         * fully-qualified name, so we can expand it unconditionally. */
+        group_name = NULL;
+        ret = sysdb_attrs_primary_name(dom->sysdb, gr->group,
+                        opts->group_map[SDAP_AT_GROUP_NAME].name,
+                        &group_name);
+        if (ret != EOK || group_name == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "Could not determine primary name\n");
+            group_name = sysdb_name;
+        }
+
+        group_name = sss_create_internal_fqname(tmp_ctx, group_name,
+                                                dom->name);
+        if (group_name != NULL) {
+            sysdb_name = group_name;
+        }
+
+        type = SYSDB_MEMBER_GROUP;
+    } else {
+        if (class != NULL && strcmp(class, SYSDB_USER_CLASS) == 0) {
+            type = SYSDB_MEMBER_USER;
+        } else {
+            type = SYSDB_MEMBER_GROUP;
+        }
+    }
+
+    /* We need to get the cached list of groups form the local domain the
+     * object is a member of to compare them with the current list just
+     * retrieved (groupnamelist). Even if this list is empty we have to
+     * proceed because the membership might have been removed recently on the
+     * server. */
+
+    obj_dom = find_domain_by_object_name(get_domains_head(dom),
+                                         sysdb_name);
+    if (obj_dom == NULL) {
+        obj_dom = dom;
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot find domain for [%s], "
+                                 "trying with local domain [%s].\n",
+                                 sysdb_name, obj_dom->name);
+    }
+
+    ret = sysdb_get_direct_parents(tmp_ctx, obj_dom, dom, type, sysdb_name,
+                                   &cached_local_parents);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,"sysdb_get_direct_parents failed.\n");
+        goto done;
+    }
+
+    if (cached_local_parents != NULL && cached_local_parents[0] == NULL) {
+        talloc_zfree(cached_local_parents);
+    }
+
+    if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) {
+        if (cached_local_parents != NULL) {
+            for (c = 0; cached_local_parents[c] != NULL; c++) {
+                DEBUG(SSSDBG_TRACE_ALL, "[%s] cached_local_parents [%s].\n",
+                                        sysdb_name, cached_local_parents[c]);
+            }
+        }
+
+        if (groupnamelist != NULL) {
+            for (c = 0; groupnamelist[c] != NULL; c++) {
+                DEBUG(SSSDBG_TRACE_ALL, "[%s] groupnamelist [%s].\n",
+                                        sysdb_name, groupnamelist[c]);
+            }
+        }
+    }
+
+    ret = diff_string_lists(tmp_ctx, cached_local_parents, groupnamelist,
+                            &del_list, &add_list, NULL);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "diff_string_lists failed.\n");
+        goto done;
+    }
+
+    if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) {
+        if (add_list != NULL) {
+            for (c = 0; add_list[c] != NULL; c++) {
+                DEBUG(SSSDBG_TRACE_ALL, "add: [%s] will be member of [%s].\n",
+                                        sysdb_name, add_list[c]);
+            }
+        }
+        if (del_list != NULL) {
+            for (c = 0; del_list[c] != NULL; c++) {
+                DEBUG(SSSDBG_TRACE_ALL, "del: [%s] was member of [%s].\n",
+                                        sysdb_name, del_list[c]);
+            }
+        }
+    }
+
+    *_type = type;
+    *_sysdb_name = talloc_steal(mem_ctx, sysdb_name);
+    *_add_list = talloc_steal(mem_ctx, groupnamelist);
+    *_del_list = talloc_steal(mem_ctx, del_list);
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
+static void sdap_ad_get_domain_local_groups_done(struct tevent_req *subreq)
+{
+
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sdap_ad_get_domain_local_groups_state *state = tevent_req_data(req,
+                                  struct sdap_ad_get_domain_local_groups_state);
+    int ret;
+    int hret;
+    unsigned long count;
+    hash_value_t *values = NULL;
+    struct sdap_nested_group *gr;
+    size_t c;
+    const char *sysdb_name = NULL;
+    enum sysdb_member_type type;
+    char **add_list = NULL;
+    char **del_list = NULL;
+
+    ret = rfc2307bis_nested_groups_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    hret = hash_values(state->group_hash, &count, &values);
+    if (hret != HASH_SUCCESS) {
+        DEBUG(SSSDBG_OP_FAILURE, "hash_values failed.\n");
+        ret = EIO;
+        goto done;
+    }
+
+    for (c = 0; c < count; c++) {
+        gr = talloc_get_type(values[c].ptr,
+                             struct sdap_nested_group);
+
+        /* The values from the hash are either user or group objects returned
+         * by sysdb_initgroups() which where used to start the request or
+         * nested parents found during the request. The nested parents contain
+         * the processed LDAP data and can be identified by a missing
+         * objectclass attribute. */
+        ret = sdap_ad_get_domain_local_groups_parse_parents(state, gr,
+                                                            state->dom,
+                                                            state->sysdb,
+                                                            state->opts,
+                                                            &sysdb_name,
+                                                            &type,
+                                                            &add_list,
+                                                            &del_list);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "sdap_ad_get_domain_local_groups_parse_parents failed.\n");
+            continue;
+        }
+
+        if ((add_list == NULL && del_list == NULL)
+                || (add_list == NULL && del_list != NULL && del_list[0] == NULL)
+                || (add_list != NULL && add_list[0] == NULL && del_list == NULL)
+                || (add_list != NULL && add_list[0] == NULL
+                        && del_list != NULL && del_list[0] == NULL) ) {
+            continue;
+        }
+
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Updating domain local memberships for %s\n",
+                                     sysdb_name);
+        ret = sysdb_update_members(state->dom, sysdb_name, type,
+                                   (const char *const *) add_list,
+                                   (const char *const *) del_list);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sysdb_update_members failed.\n");
+            goto done;
+        }
+    }
+
+    ret = EOK;
+done:
+    talloc_zfree(values);
+
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+
+    return;
+}
+
+errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+    return EOK;
+}
+
 struct sdap_ad_tokengroups_initgroups_state {
     bool use_id_mapping;
     struct sss_domain_info *domain;
diff --git a/src/providers/ldap/sdap_async_private.h b/src/providers/ldap/sdap_async_private.h
index 4af4f7144d8855e4ed705f6a64e0a7818bc0b9a9..266bc03115e2bdd6a283f5f7da565fd00d3a77be 100644
--- a/src/providers/ldap/sdap_async_private.h
+++ b/src/providers/ldap/sdap_async_private.h
@@ -173,4 +173,14 @@ errno_t sdap_nested_groups_store(struct sysdb_ctx *sysdb,
                                  struct sysdb_attrs **groups,
                                  unsigned long count);
 
+struct tevent_req *
+sdap_ad_get_domain_local_groups_send(TALLOC_CTX *mem_ctx,
+                                     struct tevent_context *ev,
+                                     struct sdap_domain *local_sdom,
+                                     struct sdap_options *opts,
+                                     struct sysdb_ctx *sysdb,
+                                     struct sss_domain_info *dom,
+                                     struct sysdb_attrs **groups,
+                                     size_t num_groups);
+errno_t sdap_ad_get_domain_local_groups_recv(struct tevent_req *req);
 #endif /* _SDAP_ASYNC_PRIVATE_H_ */
-- 
2.7.4