On Mon, Feb 22, 2016 at 06:04:07PM +0100, Jakub Hrozek wrote:
> Hi,
> 
> the attached patches implement https://fedorahosted.org/sssd/ticket/2522
> 
> Here is what I tested:
>     1) topgr -> bottomgr -> extgr -> [email protected]
>         - this is a simple case to test a member of an external group
> 
>     2) ipa_uni -> ipa_uni_ext -> [email protected]
>                                     |
>                                     +-- [email protected]
>                                      \- [email protected]
> 
>         - this is to test that a universal group with members from two
>           domains resolves correctly
> 
>     3) idm_admin -> idm_admin_ext -> [email protected]
>                                          \- [email protected]
>                                   -> [email protected]
>         - tests that a combination of user and group members work
> 
>     4) toc_admin -> toc_admin_ext -> [email protected]
>                                   -> [email protected]
>         - tests that two users with the same name from different domains
>           work correctly
> 
> I also tested adding and removing group members and found some bugs in
> our groups processing (#2940, #2952)
> 
> Please feel free to suggest more tests. If the patches are accepted,
> I'll also add unit tests for the nested groups code, but currently we
> need to make the patches available to upstream.
> 
> I also tested a combination of user lookups and group lookups,
> especially with someuser to test that the initgroups and group lookups
> play well together. And that's where I found one issue that I think we
> should fix in master -- the initgroups code adds the external members as
> direct members of the POSIX group, so I did the same in the group
> lookup code. This is safer, but doesn't allow us to remove the hack we
> have (6fac5e5f0c54a0f92872ce1450606cfcb577a920) to retain external
> members.
> 
> I think in 1.14 it would be more systematic to store the group
> membership as:
>     gr -> ext_gr -> ad_member
> rather than:
>     gr -> ext_gr
>        -> ad_member
> 
> The reason we can't remove those hacks is that if the external group is
> up-to-date and only the top group is resolved, we would only write the
> group memberships as stored in LDAP, effectively removing the AD
> memberships. This of course depends on fixing the issues in memberof
> plugin, but I still think it's worth it. The current code works well,
> though, so I guess we can accept it for 1.13.
> 
> Our initgroups code should be well equipped to handle that and we could
> remove the hacks we have at the moment.

The attached patches fix issues in the reallocation logic that Sumit
found during his review. Only the third patch had changed.
>From e8a93192c3e5246175c3ec9473e1e4c6dd1d20b8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <[email protected]>
Date: Mon, 25 Jan 2016 16:03:23 +0100
Subject: [PATCH 1/3] Add a new option ldap_group_external_member

Required for:
    https://fedorahosted.org/sssd/ticket/2522
---
 src/config/SSSDConfig/__init__.py.in     |  1 +
 src/config/etc/sssd.api.d/sssd-ad.conf   |  1 +
 src/config/etc/sssd.api.d/sssd-ipa.conf  |  1 +
 src/config/etc/sssd.api.d/sssd-ldap.conf |  1 +
 src/db/sysdb.h                           |  1 +
 src/man/sssd-ldap.5.xml                  | 16 ++++++++++++++++
 src/providers/ad/ad_opts.c               |  1 +
 src/providers/ipa/ipa_opts.c             |  1 +
 src/providers/ldap/ldap_opts.c           |  3 +++
 src/providers/ldap/sdap.h                |  1 +
 10 files changed, 27 insertions(+)

diff --git a/src/config/SSSDConfig/__init__.py.in 
b/src/config/SSSDConfig/__init__.py.in
index 
495cb650ee86e50031962a4fcf0c21aa79dc0142..65e5e655d2eb25c3366bb9f37b9542902fa6302e
 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -334,6 +334,7 @@ option_strings = {
     'ldap_group_objectsid' : _("objectSID attribute"),
     'ldap_group_modify_timestamp' : _('Modification time attribute for 
groups'),
     'ldap_group_type' : _('Type of the group and other flags'),
+    'ldap_group_external_member' : _('The LDAP group external member 
attribute'),
     #replaced by ldap_entry_usn# 'ldap_group_entry_usn' : _('entryUSN 
attribute'),
     'ldap_group_nesting_level' : _('Maximum nesting level SSSd will follow'),
 
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf 
b/src/config/etc/sssd.api.d/sssd-ad.conf
index 
149590f4f30de3438f2fc5534ae65c98ee0f10ad..23006d26ca6fe7ca2b912ef091b4c73d5d23bee1
 100644
--- a/src/config/etc/sssd.api.d/sssd-ad.conf
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
@@ -110,6 +110,7 @@ ldap_group_objectsid = str, None, false
 ldap_group_modify_timestamp = str, None, false
 ldap_group_entry_usn = str, None, false
 ldap_group_type = int, None, false
+ldap_group_external_member = str, None, false
 ldap_force_upper_case_realm = bool, None, false
 ldap_group_nesting_level = int, None, false
 ldap_netgroup_search_base = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf 
b/src/config/etc/sssd.api.d/sssd-ipa.conf
index 
822599db6390ad2244a71db770c0b162345a3321..8cd20c0c621a513ca7bc85be6908de41d024b148
 100644
--- a/src/config/etc/sssd.api.d/sssd-ipa.conf
+++ b/src/config/etc/sssd.api.d/sssd-ipa.conf
@@ -104,6 +104,7 @@ ldap_group_objectsid = str, None, false
 ldap_group_modify_timestamp = str, None, false
 ldap_group_entry_usn = str, None, false
 ldap_group_type = int, None, false
+ldap_group_external_member = str, None, false
 ldap_force_upper_case_realm = bool, None, false
 ldap_group_nesting_level = int, None, false
 ldap_netgroup_search_base = str, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf 
b/src/config/etc/sssd.api.d/sssd-ldap.conf
index 
fc9fcefce94891760a3f3ada4c044dbcaf156945..8b52f268af195bc68d45389cda52a0ad0aba1aa3
 100644
--- a/src/config/etc/sssd.api.d/sssd-ldap.conf
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -98,6 +98,7 @@ ldap_group_objectsid = str, None, false
 ldap_group_modify_timestamp = str, None, false
 ldap_group_entry_usn = str, None, false
 ldap_group_type = int, None, false
+ldap_group_external_member = str, None, false
 ldap_group_nesting_level = int, None, false
 ldap_force_upper_case_realm = bool, None, false
 ldap_netgroup_search_base = str, None, false
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 
2e797fd7fa39163c2ab6a10e51228e0f1af3f9e3..95a9086766228a6c36c56d3a68a0bb0e493c0cbe
 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -81,6 +81,7 @@
 #define SYSDB_USER_CATEGORY "userCategory"
 #define SYSDB_HOST_CATEGORY "hostCategory"
 #define SYSDB_GROUP_TYPE "groupType"
+#define SYSDB_EXTERNAL_MEMBER "externalMember"
 
 #define SYSDB_GECOS "gecos"
 #define SYSDB_LAST_LOGIN "lastLogin"
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index 
66b9024bcdc6faced67c4e44f9cde7caa9a5ecc8..a30100408c6e77f9156878cb6ff63dfbf7b041d1
 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -942,6 +942,22 @@
                 </varlistentry>
 
                 <varlistentry>
+                    <term>ldap_group_external_member (string)</term>
+                    <listitem>
+                        <para>
+                            The LDAP attribute that references group
+                            members that are defined in an external
+                            domain. At the moment, only IPA's external
+                            members are supported.
+                        </para>
+                        <para>
+                            Default: ipaExternalMember in the IPA provider,
+                            otherwise unset.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
                     <term>ldap_group_nesting_level (integer)</term>
                     <listitem>
                         <para>
diff --git a/src/providers/ad/ad_opts.c b/src/providers/ad/ad_opts.c
index 
28d4768b20bd035f7c1971c95f9b6b690844816e..15024adb7959de9e16cdc92ca30daa74bb5f648d
 100644
--- a/src/providers/ad/ad_opts.c
+++ b/src/providers/ad/ad_opts.c
@@ -233,6 +233,7 @@ struct sdap_attr_map ad_2008r2_group_map[] = {
     { "ldap_group_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL 
},
     { "ldap_group_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL },
     { "ldap_group_type", "groupType", SYSDB_GROUP_TYPE, NULL },
+    { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL },
     SDAP_ATTR_MAP_TERMINATOR
 };
 
diff --git a/src/providers/ipa/ipa_opts.c b/src/providers/ipa/ipa_opts.c
index 
cd87852e5891fd43d7ec728f76860f3050a54d2f..fe469852b527ad872502b3346c8c11ef9eea3bcd
 100644
--- a/src/providers/ipa/ipa_opts.c
+++ b/src/providers/ipa/ipa_opts.c
@@ -219,6 +219,7 @@ struct sdap_attr_map ipa_group_map[] = {
     { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, 
NULL },
     { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL },
     { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL },
+    { "ldap_group_external_member", "ipaExternalMember", 
SYSDB_EXTERNAL_MEMBER, NULL },
     SDAP_ATTR_MAP_TERMINATOR
 };
 
diff --git a/src/providers/ldap/ldap_opts.c b/src/providers/ldap/ldap_opts.c
index 
84ba2b54271bcb6650e0336131ace8bfc1a40fc8..ff9bf0d8b6d4a8f677e08219e5105e3750b7a4a8
 100644
--- a/src/providers/ldap/ldap_opts.c
+++ b/src/providers/ldap/ldap_opts.c
@@ -195,6 +195,7 @@ struct sdap_attr_map rfc2307_group_map[] = {
     { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, 
NULL },
     { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL },
     { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL },
+    { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL },
     SDAP_ATTR_MAP_TERMINATOR
 };
 
@@ -251,6 +252,7 @@ struct sdap_attr_map rfc2307bis_group_map[] = {
     { "ldap_group_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, 
NULL },
     { "ldap_group_entry_usn", NULL, SYSDB_USN, NULL },
     { "ldap_group_type", NULL, SYSDB_GROUP_TYPE, NULL },
+    { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL },
     SDAP_ATTR_MAP_TERMINATOR
 };
 
@@ -307,6 +309,7 @@ struct sdap_attr_map gen_ad2008r2_group_map[] = {
     { "ldap_group_modify_timestamp", "whenChanged", SYSDB_ORIG_MODSTAMP, NULL 
},
     { "ldap_group_entry_usn", SDAP_AD_USN, SYSDB_USN, NULL },
     { "ldap_group_type", "groupType", SYSDB_GROUP_TYPE, NULL },
+    { "ldap_group_external_member", NULL, SYSDB_EXTERNAL_MEMBER, NULL },
     SDAP_ATTR_MAP_TERMINATOR
 };
 
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 
e06f2b6ac47990f21985fb86f8ad3f3ae5a74df3..9dc2e16a0da76246a1f4492cf70e9124edba4a31
 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -304,6 +304,7 @@ enum sdap_group_attrs {
     SDAP_AT_GROUP_MODSTAMP,
     SDAP_AT_GROUP_USN,
     SDAP_AT_GROUP_TYPE,
+    SDAP_AT_GROUP_EXT_MEMBER,
 
     SDAP_OPTS_GROUP /* attrs counter */
 };
-- 
2.4.3

>From 9f8e4c4b757851fc213d0d0d6ae4edf85cb5c832 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <[email protected]>
Date: Mon, 25 Jan 2016 16:11:59 +0100
Subject: [PATCH 2/3] IPA: Add interface to call into IPA provider from LDAP
 provider

https://fedorahosted.org/sssd/ticket/2522

Adds a pluggable interface that is able to resolve the IPA group's
external members. At the moment, the request calls the full be_
interface to make sure all corner cases like id-views are handled
internally.
---
 src/providers/ipa/ipa_id.c                    |   5 +-
 src/providers/ipa/ipa_init.c                  |  28 +++
 src/providers/ipa/ipa_subdomains.h            |  11 ++
 src/providers/ipa/ipa_subdomains_ext_groups.c | 275 ++++++++++++++++++++++++++
 src/providers/ipa/ipa_subdomains_id.c         |   1 +
 src/providers/ldap/sdap.h                     |  23 +++
 6 files changed, 342 insertions(+), 1 deletion(-)

diff --git a/src/providers/ipa/ipa_id.c b/src/providers/ipa/ipa_id.c
index 
27cc2548d8802c81311c6c5bd10a0db4e8930fa1..29e22982c415220c931f0422e10cd06dfa1a195b
 100644
--- a/src/providers/ipa/ipa_id.c
+++ b/src/providers/ipa/ipa_id.c
@@ -405,7 +405,10 @@ static int ipa_initgr_get_overrides_step(struct tevent_req 
*req)
         /* This should never happen, the search filter used to get the list
          * of groups includes "uuid=*"
          */
-        DEBUG(SSSDBG_OP_FAILURE, "A group with no UUID, error!\n");
+        DEBUG(SSSDBG_OP_FAILURE,
+              "The group %s has no UUID attribute %s, error!\n",
+              ldb_dn_get_linearized(state->groups[state->group_idx]->dn),
+              state->groups_id_attr);
         return EINVAL;
     }
 
diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c
index 
0e16dd97c78a087256fb77be500c9741484867c5..453e2b25673ac709c9fa3809d35b7885630c8b24
 100644
--- a/src/providers/ipa/ipa_init.c
+++ b/src/providers/ipa/ipa_init.c
@@ -139,6 +139,24 @@ int common_ipa_init(struct be_ctx *bectx)
     return EOK;
 }
 
+static struct sdap_ext_member_ctx *
+ipa_create_ext_members_ctx(TALLOC_CTX *mem_ctx,
+                           struct ipa_id_ctx *id_ctx)
+{
+    struct sdap_ext_member_ctx *ext_ctx = NULL;
+
+    ext_ctx = talloc_zero(mem_ctx, struct sdap_ext_member_ctx);
+    if (ext_ctx == NULL) {
+        return NULL;
+    }
+
+    ext_ctx->pvt = id_ctx;
+    ext_ctx->ext_member_resolve_send = ipa_ext_group_member_send;
+    ext_ctx->ext_member_resolve_recv = ipa_ext_group_member_recv;
+
+    return ext_ctx;
+}
+
 int sssm_ipa_id_init(struct be_ctx *bectx,
                      struct bet_ops **ops,
                      void **pvt_data)
@@ -360,6 +378,16 @@ int sssm_ipa_id_init(struct be_ctx *bectx,
               "will not work [%d]: %s\n", ret, strerror(ret));
     }
 
+    ipa_ctx->sdap_id_ctx->opts->ext_ctx = ipa_create_ext_members_ctx(
+                                                    ipa_ctx->sdap_id_ctx->opts,
+                                                    ipa_ctx);
+    if (ipa_ctx->sdap_id_ctx->opts->ext_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Unable to set SRV the extrernal group ctx\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
     *ops = &ipa_id_ops;
     *pvt_data = ipa_ctx;
     ret = EOK;
diff --git a/src/providers/ipa/ipa_subdomains.h 
b/src/providers/ipa/ipa_subdomains.h
index 
0c13f8ed2eeda87237dfb097f532c7137095ddf1..23c3b7e3cd3ee1e0ac1dbcf98dc71a6c2337b835
 100644
--- a/src/providers/ipa/ipa_subdomains.h
+++ b/src/providers/ipa/ipa_subdomains.h
@@ -137,4 +137,15 @@ struct tevent_req *ipa_get_ad_memberships_send(TALLOC_CTX 
*mem_ctx,
                                         const char *domain);
 
 errno_t ipa_get_ad_memberships_recv(struct tevent_req *req, int *dp_error_out);
+
+struct tevent_req *ipa_ext_group_member_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             const char *ext_member,
+                                             void *pvt);
+errno_t ipa_ext_group_member_recv(TALLOC_CTX *mem_ctx,
+                                  struct tevent_req *req,
+                                  enum sysdb_member_type *_member_type,
+                                  struct sss_domain_info **_dom,
+                                  struct sysdb_attrs **_member);
+
 #endif /* _IPA_SUBDOMAINS_H_ */
diff --git a/src/providers/ipa/ipa_subdomains_ext_groups.c 
b/src/providers/ipa/ipa_subdomains_ext_groups.c
index 
d487a58b8adffabe09ff50e31cb750b800b1d252..5dc6d0d6417ec3fb5e7865e4cbaf3c07f4afbd07
 100644
--- a/src/providers/ipa/ipa_subdomains_ext_groups.c
+++ b/src/providers/ipa/ipa_subdomains_ext_groups.c
@@ -923,3 +923,278 @@ static errno_t ipa_add_ad_memberships_recv(struct 
tevent_req *req,
 
     return EOK;
 }
+
+static errno_t
+search_user_or_group_by_sid_str(TALLOC_CTX *mem_ctx,
+                                struct sss_domain_info *domain,
+                                const char *sid_str,
+                                enum sysdb_member_type *_member_type,
+                                struct ldb_message **_msg)
+{
+    errno_t ret;
+    struct ldb_message *msg = NULL;
+    const char *attrs[] = { SYSDB_NAME,
+                            SYSDB_SID_STR,
+                            SYSDB_ORIG_DN,
+                            SYSDB_OBJECTCLASS,
+                            SYSDB_CACHE_EXPIRE,
+                            NULL };
+    TALLOC_CTX *tmp_ctx = NULL;
+    char *sanitized_sid = NULL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
+        return ENOMEM;
+    }
+
+    /* In theory SID shouldn't contain any special LDAP characters, but let's
+     * be paranoid
+     */
+    ret = sss_filter_sanitize(tmp_ctx, sid_str, &sanitized_sid);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = sysdb_search_user_by_sid_str(tmp_ctx, domain,
+                                       sid_str, attrs, &msg);
+    if (ret == EOK) {
+        *_member_type = SYSDB_MEMBER_USER;
+    } else if (ret == ENOENT) {
+        ret = sysdb_search_group_by_sid_str(tmp_ctx, domain,
+                                            sid_str, attrs, &msg);
+        if (ret == EOK) {
+            *_member_type = SYSDB_MEMBER_GROUP;
+        }
+    }
+
+    switch (ret) {
+    case EOK:
+        DEBUG(SSSDBG_TRACE_FUNC, "Found %s in sysdb\n", sid_str);
+        *_msg = talloc_steal(mem_ctx, msg);
+        break;
+    case ENOENT:
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "Could not find %s in sysdb", sid_str);
+        break;
+    default:
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Error looking for %s in sysdb [%d]: %s\n",
+              sid_str, ret, sss_strerror(ret));
+        break;
+    }
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+ipa_ext_group_member_check(TALLOC_CTX *mem_ctx,
+                           struct ipa_id_ctx *ipa_ctx,
+                           struct sss_domain_info *member_dom,
+                           const char *ext_member,
+                           enum sysdb_member_type *_member_type,
+                           struct sysdb_attrs **_member)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    errno_t ret;
+    uint64_t expire;
+    time_t now = time(NULL);
+    struct ldb_message *msg;
+    struct sysdb_attrs **members;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
+        return ENOMEM;
+    }
+
+    ret = search_user_or_group_by_sid_str(tmp_ctx, member_dom, ext_member,
+                                          _member_type, &msg);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Error looking up sid %s: [%d]: %s\n",
+               ext_member, ret, sss_strerror(ret));
+        goto done;
+    }
+
+    ret = sysdb_msg2attrs(tmp_ctx, 1, &msg, &members);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Could not convert result to sysdb_attrs [%d]: %s\n",
+               ret, sss_strerror(ret));
+        goto done;
+    }
+
+    /* Return the member both expired and valid */
+    *_member = talloc_steal(mem_ctx, members[0]);
+
+    expire = ldb_msg_find_attr_as_uint64(msg, SYSDB_CACHE_EXPIRE, 0);
+    if (expire != 0 && expire <= now) {
+        DEBUG(SSSDBG_TRACE_FUNC, "%s is expired", ext_member);
+        ret = EAGAIN;
+        goto done;
+    }
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+/* For the IPA external member resolution, we expect a SID as the input.
+ * The _recv() function output is the member and a type (user/group)
+ * since nothing else can be a group member.
+ */
+struct ipa_ext_member_state {
+    const char *ext_member;
+    struct sss_domain_info *dom;
+
+    enum sysdb_member_type member_type;
+    struct sysdb_attrs *member;
+};
+
+static void ipa_ext_group_member_done(struct tevent_req *subreq);
+
+struct tevent_req *ipa_ext_group_member_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             const char *ext_member,
+                                             void *pvt)
+{
+    struct ipa_id_ctx *ipa_ctx;
+    struct ipa_ext_member_state *state;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct be_acct_req *ar;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct ipa_ext_member_state);
+    if (req == NULL) {
+        return NULL;
+    }
+    state->ext_member = ext_member;
+
+    ipa_ctx = talloc_get_type(pvt, struct ipa_id_ctx);
+    if (ipa_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Wrong private context!\n");
+        ret = EINVAL;
+        goto immediate;
+    }
+
+    state->dom = find_domain_by_sid(ipa_ctx->sdap_id_ctx->be->domain,
+                                    ext_member);
+    if (state->dom == NULL) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Cannot find domain of SID [%s]\n", ext_member);
+        ret = ENOENT;
+        goto immediate;
+    }
+
+    ret = ipa_ext_group_member_check(state, ipa_ctx, state->dom, ext_member,
+                                     &state->member_type, &state->member);
+    if (ret == EOK) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "external member %s already cached\n", ext_member);
+        goto immediate;
+    }
+
+    ret = get_be_acct_req_for_sid(state, ext_member, state->dom->name, &ar);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Cannot create the account request for [%s]\n", ext_member);
+        goto immediate;
+    }
+
+    subreq = be_get_account_info_send(state, ev, NULL,
+                                      ipa_ctx->sdap_id_ctx->be, ar);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+    tevent_req_set_callback(subreq, ipa_ext_group_member_done, req);
+
+    return req;
+
+immediate:
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+    } else {
+        tevent_req_done(req);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static void ipa_ext_group_member_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct ipa_ext_member_state *state = tevent_req_data(req,
+                                                struct ipa_ext_member_state);
+    errno_t ret;
+    int err_maj;
+    int err_min;
+    const char *err_msg;
+    struct ldb_message *msg;
+    struct sysdb_attrs **members;
+
+    ret = be_get_account_info_recv(subreq, state,
+                                   &err_maj, &err_min, &err_msg);
+    talloc_free(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "be request failed %d:%d: %s\n", err_maj, err_min, err_msg);
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = search_user_or_group_by_sid_str(state,
+                                          state->dom,
+                                          state->ext_member,
+                                          &state->member_type,
+                                          &msg);
+    if (ret != EOK) {
+        DEBUG(ret == ENOENT ? SSSDBG_TRACE_FUNC : SSSDBG_OP_FAILURE,
+              "Could not find %s in sysdb [%d]: %s\n",
+              state->ext_member, ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = sysdb_msg2attrs(state, 1, &msg, &members);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Could not convert result to sysdb_attrs [%d]: %s\n",
+               ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    state->member = members[0];
+    tevent_req_done(req);
+}
+
+errno_t ipa_ext_group_member_recv(TALLOC_CTX *mem_ctx,
+                                  struct tevent_req *req,
+                                  enum sysdb_member_type *_member_type,
+                                  struct sss_domain_info **_dom,
+                                  struct sysdb_attrs **_member)
+{
+    struct ipa_ext_member_state *state = tevent_req_data(req,
+                                                struct ipa_ext_member_state);
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (_member_type != NULL) {
+        *_member_type = state->member_type;
+    }
+
+    if (_dom) {
+        *_dom = state->dom;
+    }
+
+    if (_member != NULL) {
+        *_member = talloc_steal(mem_ctx, state->member);
+    }
+
+    return EOK;
+}
diff --git a/src/providers/ipa/ipa_subdomains_id.c 
b/src/providers/ipa/ipa_subdomains_id.c
index 
472985d4ab4f785aa9c4af94bf8021829ca1c3c8..70a1b6a12799b5a645bbf69f8cc19c30dcff82c5
 100644
--- a/src/providers/ipa/ipa_subdomains_id.c
+++ b/src/providers/ipa/ipa_subdomains_id.c
@@ -1230,6 +1230,7 @@ static errno_t ipa_get_ad_apply_override_step(struct 
tevent_req *req)
      * attributes set, i.e. where overrides might not have been applied. */
     ret = sysdb_asq_search(state, state->obj_dom, state->obj_msg->dn,
                           "(&("SYSDB_GC")("SYSDB_GIDNUM"=*)" \
+                            "("SYSDB_POSIX"=TRUE)" \
                             "(!("ORIGINALAD_PREFIX SYSDB_GIDNUM"=*))" \
                             "(!("ORIGINALAD_PREFIX SYSDB_NAME"=*)))",
                           SYSDB_INITGR_ATTR,
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 
9dc2e16a0da76246a1f4492cf70e9124edba4a31..e0e05da0c8270a8f131870bc755da862e43783cb
 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -423,6 +423,26 @@ struct sdap_domain {
     void *pvt;
 };
 
+typedef struct tevent_req *
+(*ext_member_send_fn_t)(TALLOC_CTX *mem_ctx,
+                        struct tevent_context *ev,
+                        const char *ext_member,
+                        void *pvt);
+typedef errno_t
+(*ext_member_recv_fn_t)(TALLOC_CTX *mem_ctx,
+                        struct tevent_req *req,
+                        enum sysdb_member_type *member_type,
+                        struct sss_domain_info **_dom,
+                        struct sysdb_attrs **_member);
+
+struct sdap_ext_member_ctx {
+    /* Typically ID context of the external ID provider */
+    void *pvt;
+
+    ext_member_send_fn_t ext_member_resolve_send;
+    ext_member_recv_fn_t ext_member_resolve_recv;
+};
+
 struct sdap_options {
     struct dp_option *basic;
     struct sdap_attr_map *gen_map;
@@ -435,6 +455,9 @@ struct sdap_options {
     /* ID-mapping support */
     struct sdap_idmap_ctx *idmap_ctx;
 
+    /* Resolving external members */
+    struct sdap_ext_member_ctx *ext_ctx;
+
     /* FIXME - should this go to a special struct to avoid mixing with 
name-service-switch maps? */
     struct sdap_attr_map *sudorule_map;
     struct sdap_attr_map *autofs_mobject_map;
-- 
2.4.3

>From db489b139e9de02a4de1937ab45d935024dc9256 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <[email protected]>
Date: Mon, 25 Jan 2016 16:13:03 +0100
Subject: [PATCH 3/3] LDAP: Use the IPA provider interface to resolve external
 group members

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

Currently the approach is not optimized for performance, because each
external member is resolved in a full transaction to make sure even ID
views and similar information is processed.

In future, we should implement https://fedorahosted.org/sssd/ticket/2943
we will again be able to process all the data in a single transaction.
---
 src/providers/ldap/sdap_async_groups.c        |  49 +-
 src/providers/ldap/sdap_async_nested_groups.c | 615 +++++++++++++++++++++++++-
 src/providers/ldap/sdap_async_private.h       |  16 +-
 src/tests/cmocka/test_nested_groups.c         |   4 +-
 4 files changed, 656 insertions(+), 28 deletions(-)

diff --git a/src/providers/ldap/sdap_async_groups.c 
b/src/providers/ldap/sdap_async_groups.c
index 
75bfa413d43f7bb679d954fd1b90ea875ee731f5..3ccf7deb53d365a106cd18af1835f527424d5bf3
 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -1758,6 +1758,7 @@ struct sdap_get_groups_state {
     struct sysdb_attrs **groups;
     size_t count;
     size_t check_count;
+    hash_table_t *missing_external;
 
     hash_table_t *user_hash;
     hash_table_t *group_hash;
@@ -2333,6 +2334,8 @@ int sdap_get_groups_recv(struct tevent_req *req,
     return EOK;
 }
 
+static void sdap_nested_ext_done(struct tevent_req *subreq);
+
 static void sdap_nested_done(struct tevent_req *subreq)
 {
     errno_t ret, tret;
@@ -2348,7 +2351,8 @@ static void sdap_nested_done(struct tevent_req *subreq)
                                             struct sdap_get_groups_state);
 
     ret = sdap_nested_group_recv(state, subreq, &user_count, &users,
-                                 &group_count, &groups);
+                                 &group_count, &groups,
+                                 &state->missing_external);
     talloc_zfree(subreq);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, "Nested group processing failed: 
[%d][%s]\n",
@@ -2387,8 +2391,25 @@ static void sdap_nested_done(struct tevent_req *subreq)
     }
     in_transaction = false;
 
-    /* Processing complete */
-    tevent_req_done(req);
+    if (hash_count(state->missing_external) == 0) {
+        /* No external members. Processing complete */
+        DEBUG(SSSDBG_TRACE_INTERNAL, "No external members, done");
+        tevent_req_done(req);
+        return;
+    }
+
+    /* At the moment, we need to save the direct groups & members in one
+     * transaction and then query the others in a separate requests
+     */
+    subreq = sdap_nested_group_lookup_external_send(state, state->ev,
+                                                    state->dom,
+                                                    state->opts->ext_ctx,
+                                                    state->missing_external);
+    if (subreq == NULL) {
+        ret = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(subreq, sdap_nested_ext_done, req);
     return;
 
 fail:
@@ -2401,6 +2422,28 @@ fail:
     tevent_req_error(req, ret);
 }
 
+static void sdap_nested_ext_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sdap_get_groups_state *state = tevent_req_data(req,
+                                            struct sdap_get_groups_state);
+
+    ret = sdap_nested_group_lookup_external_recv(state, subreq);
+    talloc_free(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Cannot resolve external members [%d]: %s\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+    return;
+}
+
 static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx,
                                                 struct sysdb_ctx *sysdb,
                                                 struct sss_domain_info *domain,
diff --git a/src/providers/ldap/sdap_async_nested_groups.c 
b/src/providers/ldap/sdap_async_nested_groups.c
index 
9d715225243d8672850563473bd3938d4cc5db6b..f0d04fa0948abd58470785d07b8d42f3cfeb9eb0
 100644
--- a/src/providers/ldap/sdap_async_nested_groups.c
+++ b/src/providers/ldap/sdap_async_nested_groups.c
@@ -56,6 +56,13 @@ struct sdap_nested_group_member {
     const char *group_filter;
 };
 
+const size_t external_members_chunk = 16;
+
+struct sdap_external_missing_member {
+    const char **parent_group_dns;
+    size_t parent_dn_idx;
+};
+
 struct sdap_nested_group_ctx {
     struct sss_domain_info *domain;
     struct sdap_options *opts;
@@ -64,6 +71,7 @@ struct sdap_nested_group_ctx {
     struct sdap_handle *sh;
     hash_table_t *users;
     hash_table_t *groups;
+    hash_table_t *missing_external;
     bool try_deref;
     int deref_treshold;
     int max_nesting_level;
@@ -184,37 +192,32 @@ done:
     return ret;
 }
 
-static errno_t sdap_nested_group_hash_entry(hash_table_t *table,
-                                            struct sysdb_attrs *entry,
-                                            const char *table_name)
+static errno_t sdap_nested_group_hash_insert(hash_table_t *table,
+                                             const char *entry_key,
+                                             void *entry_value,
+                                             bool overwrite,
+                                             const char *table_name)
 {
     hash_key_t key;
     hash_value_t value;
-    const char *name = NULL;
-    errno_t ret;
     int hret;
 
-    ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &name);
-    if (ret != EOK) {
-        return ret;
-    }
-
     DEBUG(SSSDBG_TRACE_ALL, "Inserting [%s] into hash table [%s]\n",
-                             name, table_name);
+                             entry_key, table_name);
 
     key.type = HASH_KEY_STRING;
-    key.str = talloc_strdup(NULL, name);
+    key.str = talloc_strdup(NULL, entry_key);
     if (key.str == NULL) {
         return ENOMEM;
     }
 
-    if (hash_has_key(table, &key)) {
+    if (overwrite == false && hash_has_key(table, &key)) {
         talloc_free(key.str);
         return EEXIST;
     }
 
     value.type = HASH_VALUE_PTR;
-    value.ptr = entry;
+    value.ptr = entry_value;
 
     hret = hash_enter(table, &key, &value);
     if (hret != HASH_SUCCESS) {
@@ -228,6 +231,21 @@ static errno_t sdap_nested_group_hash_entry(hash_table_t 
*table,
     return EOK;
 }
 
+static errno_t sdap_nested_group_hash_entry(hash_table_t *table,
+                                            struct sysdb_attrs *entry,
+                                            const char *table_name)
+{
+    const char *name = NULL;
+    errno_t ret;
+
+    ret = sysdb_attrs_get_string(entry, SYSDB_ORIG_DN, &name);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    return sdap_nested_group_hash_insert(table, name, entry, false, 
table_name);
+}
+
 static errno_t
 sdap_nested_group_hash_user(struct sdap_nested_group_ctx *group_ctx,
                             struct sysdb_attrs *user)
@@ -297,6 +315,76 @@ sdap_nested_group_hash_group(struct sdap_nested_group_ctx 
*group_ctx,
     return sdap_nested_group_hash_entry(group_ctx->groups, group, "groups");
 }
 
+static errno_t sdap_nested_group_external_add(hash_table_t *table,
+                                              const char *ext_member,
+                                              const char *parent_group_dn)
+{
+    hash_key_t key;
+    hash_value_t value;
+    int hret;
+    int ret;
+    struct sdap_external_missing_member *ext_mem;
+
+    key.type = HASH_KEY_STRING;
+    key.str = discard_const(ext_member);
+
+    DEBUG(SSSDBG_TRACE_ALL,
+          "Inserting external member [%s] into external members hash table\n",
+          ext_member);
+
+    hret = hash_lookup(table, &key, &value);
+    switch (hret) {
+    case HASH_ERROR_KEY_NOT_FOUND:
+        ext_mem = talloc_zero(table, struct sdap_external_missing_member);
+        if (ext_mem == NULL) {
+            return ENOMEM;
+        }
+        ext_mem->parent_group_dns = talloc_zero_array(ext_mem,
+                                                      const char *,
+                                                      external_members_chunk);
+        if (ext_mem->parent_group_dns == NULL) {
+            talloc_free(ext_mem);
+            return ENOMEM;
+        }
+
+        ret = sdap_nested_group_hash_insert(table, ext_member, ext_mem,
+                                            true, "missing external users");
+        if (ret != EOK) {
+            return ret;
+        }
+        break;
+
+    case HASH_SUCCESS:
+        ext_mem = talloc_get_type(value.ptr,
+                                  struct sdap_external_missing_member);
+        if (ext_mem->parent_dn_idx == \
+                talloc_array_length(ext_mem->parent_group_dns)) {
+            ext_mem->parent_group_dns = talloc_realloc(ext_mem,
+                                                ext_mem->parent_group_dns,
+                                                const char *,
+                                                ext_mem->parent_dn_idx + \
+                                                    external_members_chunk);
+            if (ext_mem->parent_group_dns == NULL) {
+                talloc_free(ext_mem);
+                return ENOMEM;
+            }
+        }
+        break;
+    default:
+        return EIO;
+    }
+
+    ext_mem->parent_group_dns[ext_mem->parent_dn_idx] = \
+                                        
talloc_strdup(ext_mem->parent_group_dns,
+                                                      parent_group_dn);
+    if (ext_mem->parent_group_dns[ext_mem->parent_dn_idx] == NULL) {
+        return ENOMEM;
+    }
+    ext_mem->parent_dn_idx++;
+
+    return EOK;
+}
+
 static errno_t sdap_nested_group_sysdb_search(struct sss_domain_info *domain,
                                               const char *filter,
                                               bool user)
@@ -478,6 +566,13 @@ sdap_nested_group_split_members(TALLOC_CTX *mem_ctx,
     errno_t ret;
     int i;
 
+    if (members == NULL) {
+        *_missing = NULL;
+        *_num_missing = 0;
+        *_num_groups = 0;
+        return EOK;
+    }
+
     tmp_ctx = talloc_new(NULL);
     if (tmp_ctx == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
@@ -619,6 +714,65 @@ done:
     return ret;
 }
 
+static errno_t
+sdap_nested_group_add_ext_members(TALLOC_CTX *mem_ctx,
+                                  struct sdap_nested_group_ctx *group_ctx,
+                                  struct sysdb_attrs *group,
+                                  struct ldb_message_element *ext_members)
+{
+    errno_t ret;
+    const char *ext_member_attr;
+    const char *orig_dn;
+
+    if (ext_members == NULL) {
+        return EOK;
+    }
+
+    ret = sysdb_attrs_get_string(group, SYSDB_ORIG_DN, &orig_dn);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "A group with no originalDN!?!\n");
+        return ret;
+    }
+
+    for (size_t i = 0; i < ext_members->num_values; i++) {
+        ext_member_attr = (const char *) ext_members->values[i].data;
+
+        ret = sdap_nested_group_external_add(group_ctx->missing_external,
+                                             ext_member_attr,
+                                             orig_dn);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                    "Cannot add %s into external members [%d]: %s\n",
+                    ext_member_attr, ret, sss_strerror(ret));
+            return ret;
+        }
+    }
+
+    return EOK;
+}
+
+static struct ldb_message_element *
+sdap_nested_group_ext_members(struct sdap_options *opts,
+                              struct sysdb_attrs *group)
+{
+    errno_t ret;
+    struct ldb_message_element *ext_members = NULL;
+
+    if (opts->ext_ctx == NULL) {
+        return NULL;
+    }
+
+    ret = sysdb_attrs_get_el_ext(group,
+                 opts->group_map[SDAP_AT_GROUP_EXT_MEMBER].sys_name,
+                 false, &ext_members);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve external member list "
+                                   "[%d]: %s\n", ret, sss_strerror(ret));
+    }
+
+    return ext_members;
+}
+
 
 struct sdap_nested_group_state {
     struct sdap_nested_group_ctx *group_ctx;
@@ -667,6 +821,14 @@ sdap_nested_group_send(TALLOC_CTX *mem_ctx,
         goto immediately;
     }
 
+    ret = sss_hash_create(state->group_ctx, 32,
+                          &state->group_ctx->missing_external);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n",
+                                    ret, strerror(ret));
+        goto immediately;
+    }
+
     state->group_ctx->try_deref = true;
     state->group_ctx->deref_treshold = dp_opt_get_int(opts->basic,
                                                       SDAP_DEREF_THRESHOLD);
@@ -760,7 +922,8 @@ errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx,
                                unsigned long *_num_users,
                                struct sysdb_attrs ***_users,
                                unsigned long *_num_groups,
-                               struct sysdb_attrs ***_groups)
+                               struct sysdb_attrs ***_groups,
+                               hash_table_t **_missing_external)
 {
     struct sdap_nested_group_state *state = NULL;
     struct sysdb_attrs **users = NULL;
@@ -807,6 +970,11 @@ errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx,
         *_groups = talloc_steal(mem_ctx, groups);
     }
 
+    if (_missing_external) {
+        *_missing_external = talloc_steal(mem_ctx,
+                                          state->group_ctx->missing_external);
+    }
+
     return EOK;
 }
 
@@ -816,6 +984,7 @@ struct sdap_nested_group_process_state {
     struct sdap_nested_group_member *missing;
     int num_missing_total;
     int num_missing_groups;
+    struct ldb_message_element *ext_members;
     int nesting_level;
     char *group_dn;
     bool deref;
@@ -866,13 +1035,16 @@ sdap_nested_group_process_send(TALLOC_CTX *mem_ctx,
 
     DEBUG(SSSDBG_TRACE_INTERNAL, "About to process group [%s]\n", orig_dn);
 
-    /* get member list */
+    /* get member list, both direct and external */
+    state->ext_members = sdap_nested_group_ext_members(state->group_ctx->opts,
+                                                       group);
+
     ret = sysdb_attrs_get_el_ext(group, 
group_map[SDAP_AT_GROUP_MEMBER].sys_name,
                                  false, &members);
-    if (ret == ENOENT) {
-        ret = EOK; /* no members */
+    if (ret == ENOENT && state->ext_members == NULL) {
+        ret = EOK; /* no members, direct or external */
         goto immediately;
-    } else if (ret != EOK) {
+    } else if (ret != EOK && ret != ENOENT) {
         DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve member list "
                                     "[%d]: %s\n", ret, strerror(ret));
         goto immediately;
@@ -890,14 +1062,31 @@ sdap_nested_group_process_send(TALLOC_CTX *mem_ctx,
         goto immediately;
     }
 
-    DEBUG(SSSDBG_TRACE_INTERNAL, "Looking up %d/%d members of group [%s]\n",
-          state->num_missing_total, members->num_values, orig_dn);
+    ret = sdap_nested_group_add_ext_members(state,
+                                            state->group_ctx,
+                                            group,
+                                            state->ext_members);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to split external member list "
+                                    "[%d]: %s\n", ret, sss_strerror(ret));
+        goto immediately;
+    }
 
-    if (state->num_missing_total == 0) {
+    if (state->num_missing_total == 0
+            && hash_count(state->group_ctx->missing_external) == 0) {
         ret = EOK; /* we're done */
         goto immediately;
     }
 
+    /* If there are only indirect members of the group, it's still safe to
+     * proceed and let the direct lookup code just fall through.
+     */
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Looking up %d/%d members of group [%s]\n",
+                                 state->num_missing_total,
+                                 members ? members->num_values : 0,
+                                 orig_dn);
+
     /* process members */
     if (group_ctx->try_deref
             && state->num_missing_total > group_ctx->deref_treshold) {
@@ -2268,3 +2457,385 @@ static errno_t sdap_nested_group_deref_recv(struct 
tevent_req *req)
 
     return EOK;
 }
+
+struct sdap_ext_member {
+    struct sdap_external_missing_member *missing_mem;
+    const char *ext_member_attr;
+
+    enum sysdb_member_type member_type;
+    struct sss_domain_info *dom;
+    struct sysdb_attrs *attrs;
+};
+
+struct sdap_nested_group_lookup_external_state {
+    struct tevent_context *ev;
+    struct sdap_ext_member_ctx *ext_ctx;
+    struct sss_domain_info *group_dom;
+    hash_table_t *missing_external;
+
+    hash_entry_t *entries;
+    unsigned long n_entries;
+    unsigned long eniter;
+
+    struct sdap_ext_member *ext_members;
+
+    ext_member_send_fn_t ext_member_resolve_send;
+    ext_member_recv_fn_t ext_member_resolve_recv;
+};
+
+static errno_t
+sdap_nested_group_lookup_external_step(struct tevent_req *req);
+static void
+sdap_nested_group_lookup_external_done(struct tevent_req *subreq);
+static errno_t
+sdap_nested_group_lookup_external_link(struct tevent_req *req);
+static errno_t
+sdap_nested_group_lookup_external_link_member(
+                        struct sdap_nested_group_lookup_external_state *state,
+                        struct sdap_ext_member *member);
+static errno_t
+sdap_nested_group_memberof_dn_by_original_dn(
+                            TALLOC_CTX *mem_ctx,
+                            struct sss_domain_info *group_dom,
+                            const char *original_dn,
+                            const char ***_parents);
+
+struct tevent_req *
+sdap_nested_group_lookup_external_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       struct sss_domain_info *group_dom,
+                                       struct sdap_ext_member_ctx *ext_ctx,
+                                       hash_table_t *missing_external)
+{
+    struct sdap_nested_group_lookup_external_state *state = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state,
+                            struct sdap_nested_group_lookup_external_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->group_dom = group_dom;
+    state->ext_ctx = ext_ctx;
+    state->missing_external = missing_external;
+
+    if (state->ext_ctx->ext_member_resolve_send == NULL
+            || state->ext_ctx->ext_member_resolve_recv == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Wrong private context\n");
+        ret = EINVAL;
+        goto immediately;
+    }
+
+    ret = hash_entries(state->missing_external,
+                       &state->n_entries, &state->entries);
+    if (ret != HASH_SUCCESS) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "hash_entries returned %d\n", ret);
+        ret = EIO;
+        goto immediately;
+    }
+    state->eniter = 0;
+
+    state->ext_members = talloc_zero_array(state,
+                                           struct sdap_ext_member,
+                                           state->n_entries);
+    if (state->ext_members == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    ret = sdap_nested_group_lookup_external_step(req);
+    if (ret != EAGAIN) {
+        goto immediately;
+    }
+
+    return req;
+
+immediately:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t
+sdap_nested_group_lookup_external_step(struct tevent_req *req)
+{
+    struct tevent_req *subreq = NULL;
+    struct sdap_nested_group_lookup_external_state *state = NULL;
+    state = tevent_req_data(req,
+                            struct sdap_nested_group_lookup_external_state);
+
+    subreq = state->ext_ctx->ext_member_resolve_send(state,
+                                        state->ev,
+                                        state->entries[state->eniter].key.str,
+                                        state->ext_ctx->pvt);
+    if (subreq == NULL) {
+        return ENOMEM;
+    }
+    DEBUG(SSSDBG_TRACE_FUNC, "Refreshing member %lu/%lu\n",
+                             state->eniter, state->n_entries);
+    tevent_req_set_callback(subreq,
+                            sdap_nested_group_lookup_external_done,
+                            req);
+
+    return EAGAIN;
+}
+
+static void
+sdap_nested_group_lookup_external_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = NULL;
+    struct sdap_nested_group_lookup_external_state *state = NULL;
+    enum sysdb_member_type member_type;
+    struct sysdb_attrs *member;
+    struct sss_domain_info *member_dom;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req,
+                            struct sdap_nested_group_lookup_external_state);
+
+    ret = state->ext_ctx->ext_member_resolve_recv(state, subreq,
+                                                  &member_type,
+                                                  &member_dom,
+                                                  &member);
+    talloc_free(subreq);
+    if (ret == EOK) {
+        DEBUG(SSSDBG_TRACE_FUNC, "Refreshing member %lu\n", state->eniter);
+        state->ext_members[state->eniter].missing_mem = \
+                                    state->entries[state->eniter].value.ptr;
+        state->ext_members[state->eniter].dom = member_dom;
+
+        state->ext_members[state->eniter].ext_member_attr = \
+                        talloc_steal(state->ext_members,
+                                     state->entries[state->eniter].key.str);
+        state->ext_members[state->eniter].member_type = member_type;
+        state->ext_members[state->eniter].attrs = \
+                            talloc_steal(state->ext_members, member);
+    }
+
+    state->eniter++;
+    if (state->eniter >= state->n_entries) {
+        DEBUG(SSSDBG_TRACE_FUNC, "All external members processed\n");
+        ret = sdap_nested_group_lookup_external_link(req);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+        tevent_req_done(req);
+        return;
+    }
+
+    ret = sdap_nested_group_lookup_external_step(req);
+    if (ret != EOK && ret != EAGAIN) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    return;
+}
+
+static errno_t
+sdap_nested_group_lookup_external_link(struct tevent_req *req)
+{
+    errno_t ret, tret;
+    bool in_transaction = false;
+    struct sdap_nested_group_lookup_external_state *state = NULL;
+    state = tevent_req_data(req,
+                            struct sdap_nested_group_lookup_external_state);
+
+    ret = sysdb_transaction_start(state->group_dom->sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n");
+        goto fail;
+    }
+    in_transaction = true;
+
+
+    for (size_t i = 0; i < state->eniter; i++) {
+        if (state->ext_members[i].attrs == NULL) {
+            DEBUG(SSSDBG_MINOR_FAILURE, "The member %s could not be 
resolved\n",
+                                        state->ext_members[i].ext_member_attr);
+            continue;
+        }
+
+        ret = sdap_nested_group_lookup_external_link_member(state,
+                                                    &state->ext_members[i]);
+        if (ret != EOK) {
+            goto fail;
+        }
+    }
+
+    ret = sysdb_transaction_commit(state->group_dom->sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n");
+        goto fail;
+    }
+    in_transaction = false;
+
+    return EOK;
+
+fail:
+    if (in_transaction) {
+        tret = sysdb_transaction_cancel(state->group_dom->sysdb);
+        if (tret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Failed to cancel transaction\n");
+        }
+    }
+    return EFAULT;
+}
+
+static errno_t
+sdap_nested_group_lookup_external_link_member(
+                        struct sdap_nested_group_lookup_external_state *state,
+                        struct sdap_ext_member *member)
+{
+    const char *name;
+    int ret;
+    const char **parents = NULL;
+    size_t i;
+    TALLOC_CTX *tmp_ctx;
+    const char *orig_dn;
+
+    tmp_ctx = talloc_new(state);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    ret = sysdb_attrs_get_string(member->attrs, SYSDB_NAME, &name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "No name for a user\n");
+        goto done;
+    }
+
+    /* This only works because the groups were saved in a previous
+     * transaction */
+    for (i=0; i < member->missing_mem->parent_dn_idx; i++) {
+        orig_dn = member->missing_mem->parent_group_dns[i];
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "Linking external members %s from domain %s to parents of %s\n",
+              name, member->dom->name, orig_dn);
+        ret = sdap_nested_group_memberof_dn_by_original_dn(tmp_ctx,
+                                                           state->group_dom,
+                                                           orig_dn,
+                                                           &parents);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "Cannot find parents of %s\n", orig_dn);
+            continue;
+        }
+
+        /* We don't have to remove the members here, since all members 
attributes
+         * are always written anew
+         */
+        ret = sysdb_update_members_dn(member->dom, name, member->member_type,
+                                      parents, NULL);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Cannot link %s@%s to its parents\n",
+                                       name, member->dom->name);
+            goto done;
+        }
+
+    }
+
+    ret = EOK;
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t
+sdap_nested_group_memberof_dn_by_original_dn(
+                            TALLOC_CTX *mem_ctx,
+                            struct sss_domain_info *group_dom,
+                            const char *original_dn,
+                            const char ***_parents)
+{
+    errno_t ret;
+    char *sanitized_dn;
+    char *filter;
+    const char *attrs[] = { SYSDB_NAME,
+                            SYSDB_MEMBEROF,
+                            NULL };
+    struct ldb_message **msgs = NULL;
+    size_t count;
+    TALLOC_CTX *tmp_ctx;
+    struct ldb_message_element *memberof;
+    const char **parents;
+
+    tmp_ctx = talloc_new(mem_ctx);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    ret = sss_filter_sanitize(tmp_ctx, original_dn, &sanitized_dn);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+                "Cannot sanitize originalDN [%s]\n", original_dn);
+        goto done;
+    }
+
+    filter = talloc_asprintf(tmp_ctx, "(%s=%s)", SYSDB_ORIG_DN, sanitized_dn);
+    if (filter == NULL) {
+        goto done;
+    }
+
+    ret = sysdb_search_groups(tmp_ctx, group_dom, filter, attrs,
+                              &count, &msgs);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (count != 1) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "More than one entry found by originalDN?\n");
+        goto done;
+    }
+
+    memberof = ldb_msg_find_element(msgs[0], SYSDB_MEMBEROF);
+    if (memberof == NULL || memberof->num_values == 0) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "The external group is not a member of any groups\n");
+        ret = ENOENT;
+        goto done;
+    }
+
+    parents = talloc_zero_array(tmp_ctx,
+                                const char *,
+                                memberof->num_values + 1);
+    if (parents == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (size_t i = 0; i < memberof->num_values; i++) {
+        parents[i] = talloc_strdup(parents,
+                                   (const char *) memberof->values[i].data);
+        if (parents[i] == NULL) {
+            ret = ENOMEM;
+            goto done;
+        }
+    }
+
+    *_parents = talloc_steal(mem_ctx, parents);
+    ret = EOK;
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+sdap_nested_group_lookup_external_recv(TALLOC_CTX *mem_ctx,
+                                       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 
db542eaf869efcd53d0937bef3fc6e99cc78b938..9cde6f5dfe0114f797135b4989b9a4bd336a3f27
 100644
--- a/src/providers/ldap/sdap_async_private.h
+++ b/src/providers/ldap/sdap_async_private.h
@@ -130,8 +130,20 @@ errno_t sdap_nested_group_recv(TALLOC_CTX *mem_ctx,
                                unsigned long *_num_users,
                                struct sysdb_attrs ***_users,
                                unsigned long *_num_groups,
-                               struct sysdb_attrs ***_groups);
+                               struct sysdb_attrs ***_groups,
+                               hash_table_t **missing_external);
 
+struct tevent_req *
+sdap_nested_group_lookup_external_send(TALLOC_CTX *mem_ctx,
+                                       struct tevent_context *ev,
+                                       struct sss_domain_info *group_dom,
+                                       struct sdap_ext_member_ctx *ext_ctx,
+                                       hash_table_t *missing_external);
+errno_t
+sdap_nested_group_lookup_external_recv(TALLOC_CTX *mem_ctx,
+                                       struct tevent_req *req);
+
+/* from sdap_async_initgroups.c */
 errno_t sdap_add_incomplete_groups(struct sysdb_ctx *sysdb,
                                    struct sss_domain_info *domain,
                                    struct sdap_options *opts,
@@ -139,7 +151,7 @@ errno_t sdap_add_incomplete_groups(struct sysdb_ctx *sysdb,
                                    struct sysdb_attrs **ldap_groups,
                                    int ldap_groups_count);
 
-/* from sdap_async_nested_groups.c */
+/* from sdap_ad_groups.c */
 errno_t sdap_check_ad_group_type(struct sss_domain_info *dom,
                                  struct sdap_options *opts,
                                  struct sysdb_attrs *group_attrs,
diff --git a/src/tests/cmocka/test_nested_groups.c 
b/src/tests/cmocka/test_nested_groups.c
index 
dc29768c5660d5815d5fab56ee70cc8c9caab330..a3345ef5e087fc90466ce8400dda549fa5d79af8
 100644
--- a/src/tests/cmocka/test_nested_groups.c
+++ b/src/tests/cmocka/test_nested_groups.c
@@ -57,6 +57,7 @@ struct nested_groups_test_ctx {
     struct sdap_domain *sdap_domain;
     struct sdap_idmap_ctx *idmap_ctx;
     struct sdap_id_ctx *sdap_id_ctx;
+    hash_table_t *missing_external;
 
     struct sysdb_attrs **users;
     struct sysdb_attrs **groups;
@@ -110,7 +111,8 @@ static void nested_groups_test_done(struct tevent_req *req)
 
     ctx->tctx->error = sdap_nested_group_recv(ctx, req,
                                               &ctx->num_users, &ctx->users,
-                                              &ctx->num_groups, &ctx->groups);
+                                              &ctx->num_groups, &ctx->groups,
+                                              &ctx->missing_external);
     talloc_zfree(req);
 
     ctx->tctx->done = true;
-- 
2.4.3

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

Reply via email to