From d78a086bbf6d697d7a61706adca31066709d385d Mon Sep 17 00:00:00 2001
From: Jan Zeleny <jzeleny@redhat.com>
Date: Mon, 23 Apr 2012 04:46:33 -0400
Subject: [PATCH 02/10] Ghost members - support in LDAP provider

The original approach was to store name and original DN in an object in
sysdb. When later referenced as member of a group, it was retrieved by
its original DN and the correct information about its sysdb DN was
stored in the group object which referenced it.

The new approach doesn't use fake user objects, therefore this
information has to be reached differently when constructing group
memberships. The approach is to store all users to a hash table where
original DN is used as the key and username as value. When constructing
group memberships, the name is retrieved from this hash table instead of
sysdb. This hash table is constructed when retrieving user objects from
LDAP server - if the user is not present in sysdb, it is automatically
stored in the hash table.

Another situation is for rfc2307. Because there is no nesting there, we
can construct the SYSDB_GHOST attribute directly and therefore don't
need a hash table of ghost users.
---
 src/providers/ldap/sdap_async_groups.c |  480 +++++++++++++++++++-------------
 1 file changed, 290 insertions(+), 190 deletions(-)

diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c
index c3cc2ac92b80a52632655be03f8386ab2e68d7b9..637a2527fe5d6cd3fad4d579e81150ccf0db617d 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -96,12 +96,17 @@ static int sdap_fill_memberships(struct sysdb_attrs *group_attrs,
                                  struct sysdb_ctx *ctx,
                                  struct sdap_options *opts,
                                  struct sss_domain_info *domain,
+                                 hash_table_t *ghosts,
                                  struct ldb_val *values,
                                  int num_values)
 {
     struct ldb_message_element *el;
-    int i, j;
+    struct ldb_message_element *ghostel;
+    int i, j, k;
     int ret;
+    errno_t hret;
+    hash_key_t key;
+    hash_value_t value;
 
     ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el);
     if (ret) {
@@ -116,29 +121,76 @@ static int sdap_fill_memberships(struct sysdb_attrs *group_attrs,
         goto done;
     }
 
-    for (i = 0, j = el->num_values; i < num_values; i++) {
+    ret = sysdb_attrs_get_el(group_attrs, SYSDB_GHOST, &ghostel);
+    if (ret) {
+        goto done;
+    }
 
-        /* sync search entry with this as origDN */
-        ret = sdap_find_entry_by_origDN(el->values, ctx, domain,
-                                        (char *)values[i].data,
-                                        (char **)&el->values[j].data);
+    if (ghostel->num_values == 0) {
+        /* Element was probably newly created, look for "member" again */
+        ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el);
         if (ret != EOK) {
-            if (ret != ENOENT) {
+            goto done;
+        }
+    }
+
+
+    /* Just allocate both big enough to contain all members for now */
+    ghostel->values = talloc_realloc(group_attrs, ghostel->values, struct ldb_val,
+                                     ghostel->num_values + num_values);
+    if (!ghostel->values) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    j = el->num_values;
+    k = ghostel->num_values;
+    for (i = 0; i < num_values; i++) {
+        if (ghosts == NULL) {
+            hret = HASH_ERROR_KEY_NOT_FOUND;
+        } else {
+            key.type = HASH_KEY_STRING;
+            key.str = (char *)values[i].data;
+            hret = hash_lookup(ghosts, &key, &value);
+        }
+
+        if (hret == HASH_ERROR_KEY_NOT_FOUND) {
+            /* sync search entry with this as origDN */
+            ret = sdap_find_entry_by_origDN(el->values, ctx, domain,
+                                            (char *)values[i].data,
+                                            (char **)&el->values[j].data);
+            if (ret != EOK) {
+                /* This should never return ENOENT
+                 * -> fail if it does
+                 */
                 goto done;
             }
 
-            DEBUG(7, ("    member #%d (%s): not found!\n",
-                      i, (char *)values[i].data));
-        } else {
             DEBUG(7, ("    member #%d (%s): [%s]\n",
                       i, (char *)values[i].data,
                       (char *)el->values[j].data));
 
             el->values[j].length = strlen((char *)el->values[j].data);
             j++;
+        } else if (hret != HASH_SUCCESS) {
+            ret = EFAULT;
+            goto done;
+        } else {
+            DEBUG(SSSDBG_TRACE_FUNC, ("    member #%d (%s): ghost!\n",
+                                      i, (char *)values[i].data));
+            ghostel->values[k].data = (uint8_t *)talloc_strdup(ghostel->values,
+                                                               value.ptr);
+            if (ghostel->values[k].data == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            ghostel->values[k].length = strlen((char *)ghostel->values[k].data);
+            k++;
         }
     }
     el->num_values = j;
+    ghostel->num_values = k;
+
     ret = EOK;
 
 done:
@@ -186,6 +238,7 @@ static int sdap_save_group(TALLOC_CTX *memctx,
                            struct sss_domain_info *dom,
                            struct sysdb_attrs *attrs,
                            bool populate_members,
+                           hash_table_t *ghosts,
                            char **_usn_value,
                            time_t now)
 {
@@ -193,12 +246,14 @@ static int sdap_save_group(TALLOC_CTX *memctx,
     struct sysdb_attrs *group_attrs;
     const char *name = NULL;
     gid_t gid;
-    int ret;
+    int ret, cnt, i;
     char *usn_value = NULL;
     TALLOC_CTX *tmpctx = NULL;
     bool posix_group;
     bool use_id_mapping = dp_opt_get_bool(opts->basic, SDAP_ID_MAPPING);
     char *sid_str;
+    hash_key_t key;
+    hash_value_t value;
 
     tmpctx = talloc_new(memctx);
     if (!tmpctx) {
@@ -321,16 +376,58 @@ static int sdap_save_group(TALLOC_CTX *memctx,
 
     if (populate_members) {
         struct ldb_message_element *el1;
+        struct ldb_message_element *gh;
         ret = sysdb_attrs_get_el(attrs, opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el1);
         if (ret != EOK) {
             goto fail;
         }
+
         ret = sysdb_attrs_get_el(group_attrs, SYSDB_MEMBER, &el);
         if (ret != EOK) {
             goto fail;
         }
         el->values = el1->values;
         el->num_values = el1->num_values;
+
+        ret = sysdb_attrs_get_el(attrs, SYSDB_GHOST, &gh);
+        if (ret != EOK) {
+            goto fail;
+        }
+        ret = sysdb_attrs_get_el(group_attrs, SYSDB_GHOST, &el);
+        if (ret != EOK) {
+            goto fail;
+        }
+        el->values = gh->values;
+        el->num_values = gh->num_values;
+
+        /* Now process RFC2307bis ghost hash table */
+        if (ghosts != NULL) {
+            cnt = el->num_values + el1->num_values;
+            el->values = talloc_realloc(attrs, el->values, struct ldb_val,
+                                        cnt);
+            if (el->values == NULL) {
+                ret = ENOMEM;
+                goto fail;
+            }
+            for (i = 0; i < el1->num_values; i++) {
+                key.type = HASH_KEY_STRING;
+                key.str = (char *)el1->values[i].data;
+                ret = hash_lookup(ghosts, &key, &value);
+                if (ret == HASH_ERROR_KEY_NOT_FOUND) {
+                    continue;
+                } else if (ret != HASH_SUCCESS) {
+                    ret = EFAULT;
+                    goto fail;
+                }
+                el->values[el->num_values].data = (uint8_t *)talloc_strdup(el->values, value.ptr);
+                if (el->values[el->num_values].data == NULL) {
+                    ret = ENOMEM;
+                    goto fail;
+                }
+                el->values[el->num_values].length = strlen(value.ptr)+1;
+                el->num_values++;
+            }
+        }
     }
 
     ret = sdap_save_all_names(name, attrs, !dom->case_sensitive, group_attrs);
@@ -373,6 +470,7 @@ static int sdap_save_grpmem(TALLOC_CTX *memctx,
                             struct sdap_options *opts,
                             struct sss_domain_info *dom,
                             struct sysdb_attrs *attrs,
+                            hash_table_t *ghosts,
                             time_t now)
 {
     struct ldb_message_element *el;
@@ -405,6 +503,7 @@ static int sdap_save_grpmem(TALLOC_CTX *memctx,
         }
 
         ret = sdap_fill_memberships(group_attrs, ctx, opts, dom,
+                                    ghosts,
                                     el->values, el->num_values);
         if (ret) {
             goto fail;
@@ -434,6 +533,7 @@ static int sdap_save_groups(TALLOC_CTX *memctx,
                             struct sysdb_attrs **groups,
                             int num_groups,
                             bool populate_members,
+                            hash_table_t *ghosts,
                             char **_usn_value)
 {
     TALLOC_CTX *tmpctx;
@@ -487,7 +587,8 @@ static int sdap_save_groups(TALLOC_CTX *memctx,
         /* if 2 pass savemembers = false */
         ret = sdap_save_group(tmpctx, sysdb,
                               opts, dom, groups[i],
-                              populate_members, &usn_value, now);
+                              populate_members, ghosts,
+                              &usn_value, now);
 
         /* Do not fail completely on errors.
          * Just report the failure to save and go on */
@@ -520,7 +621,8 @@ static int sdap_save_groups(TALLOC_CTX *memctx,
 
         for (i = 0; i < nsaved_groups; i++) {
 
-            ret = sdap_save_grpmem(tmpctx, sysdb, opts, dom, saved_groups[i], now);
+            ret = sdap_save_grpmem(tmpctx, sysdb, opts, dom, saved_groups[i],
+                                   ghosts, now);
             /* Do not fail completely on errors.
              * Just report the failure to save and go on */
             if (ret) {
@@ -557,13 +659,12 @@ struct sdap_process_group_state {
     struct sysdb_ctx *sysdb;
 
     struct sysdb_attrs *group;
-    struct sysdb_attrs **new_members;
     struct ldb_message_element* sysdb_dns;
+    struct ldb_message_element* ghost_dns;
     char **queued_members;
     int queue_len;
     const char **attrs;
     const char *filter;
-    size_t member_idx;
     size_t queue_idx;
     size_t count;
     size_t check_count;
@@ -578,7 +679,32 @@ static int sdap_process_group_members_2307bis(struct tevent_req *req,
                                    struct sdap_process_group_state *state,
                                    struct ldb_message_element *memberel);
 static int sdap_process_group_members_2307(struct sdap_process_group_state *state,
-                                   struct ldb_message_element *memberel);
+                                   struct ldb_message_element *memberel,
+                                   struct ldb_message_element *ghostel);
+
+static errno_t sdap_process_group_create_dns(TALLOC_CTX *mem_ctx,
+                                             size_t num_values,
+                                             struct ldb_message_element **_dns)
+{
+    struct ldb_message_element *dns;
+
+    dns = talloc(mem_ctx, struct ldb_message_element);
+    if (dns == NULL) {
+        return ENOMEM;
+    }
+
+    dns->num_values = 0;
+    dns->values = talloc_array(dns, struct ldb_val,
+                               num_values);
+    if (dns->values == NULL) {
+        talloc_zfree(dns);
+        return ENOMEM;
+    }
+
+    *_dns = dns;
+
+    return EOK;
+}
 
 struct tevent_req *sdap_process_group_send(TALLOC_CTX *memctx,
                                            struct tevent_context *ev,
@@ -590,6 +716,7 @@ struct tevent_req *sdap_process_group_send(TALLOC_CTX *memctx,
                                            bool enumeration)
 {
     struct ldb_message_element *el;
+    struct ldb_message_element *ghostel;
     struct sdap_process_group_state *grp_state;
     struct tevent_req *req = NULL;
     const char **attrs;
@@ -621,8 +748,6 @@ struct tevent_req *sdap_process_group_send(TALLOC_CTX *memctx,
     grp_state->sysdb = sysdb;
     grp_state->group =  group;
     grp_state->check_count = 0;
-    grp_state->new_members = NULL;
-    grp_state->member_idx = 0;
     grp_state->queue_idx = 0;
     grp_state->queued_members = NULL;
     grp_state->queue_len = 0;
@@ -644,27 +769,47 @@ struct tevent_req *sdap_process_group_send(TALLOC_CTX *memctx,
         goto done;
     }
 
-    grp_state->sysdb_dns = talloc(grp_state, struct ldb_message_element);
-    if (!grp_state->sysdb_dns) {
-        talloc_zfree(req);
-        return NULL;
-    }
-    grp_state->sysdb_dns->values = talloc_array(grp_state, struct ldb_val,
-                                                el->num_values);
-    if (!grp_state->sysdb_dns->values) {
-        talloc_zfree(req);
-        return NULL;
-    }
-    grp_state->sysdb_dns->num_values = 0;
+    ret = sysdb_attrs_get_el(group,
+                             SYSDB_GHOST,
+                             &ghostel);
+    if (ret) {
+        goto done;
+    }
+
+    if (ghostel->num_values == 0) {
+        /* Element was probably newly created, look for "member" again */
+        ret = sysdb_attrs_get_el(group,
+                                 opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
+                                 &el);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+
+    ret = sdap_process_group_create_dns(grp_state, el->num_values,
+                                        &grp_state->sysdb_dns);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = sdap_process_group_create_dns(grp_state, el->num_values,
+                                        &grp_state->ghost_dns);
+    if (ret != EOK) {
+        goto done;
+    }
 
     switch (opts->schema_type) {
         case SDAP_SCHEMA_RFC2307:
-            ret = sdap_process_group_members_2307(grp_state, el);
+            ret = sdap_process_group_members_2307(grp_state, el, ghostel);
             break;
 
         case SDAP_SCHEMA_IPA_V1:
         case SDAP_SCHEMA_AD:
         case SDAP_SCHEMA_RFC2307BIS:
+            /* Note that this code branch will be used only if
+             * ldap_nesting_level = 0 is set in config file
+             */
             ret = sdap_process_group_members_2307bis(req, grp_state, el);
             break;
 
@@ -815,12 +960,6 @@ sdap_process_group_members_2307bis(struct tevent_req *req,
         memberel->num_values = state->sysdb_dns->num_values;
     } else {
         state->count = state->check_count;
-        state->new_members = talloc_zero_array(state,
-                struct sysdb_attrs *,
-                state->count + 1);
-        if (!state->new_members) {
-            return ENOMEM;
-        }
         ret = EBUSY;
     }
 
@@ -828,41 +967,35 @@ sdap_process_group_members_2307bis(struct tevent_req *req,
 }
 
 static int
-sdap_add_group_member_2307(struct sdap_process_group_state *state,
+sdap_add_group_member_2307(struct ldb_message_element *sysdb_dns,
+                           struct sss_domain_info *dom,
                            const char *username)
 {
-    char *strdn;
-
-    strdn = sysdb_user_strdn(state->sysdb_dns->values,
-                             state->dom->name, username);
-    if (!strdn) {
+    sysdb_dns->values[sysdb_dns->num_values].data =
+            (uint8_t *) talloc_strdup(sysdb_dns->values, username);
+    if (sysdb_dns->values[sysdb_dns->num_values].data == NULL) {
         return ENOMEM;
     }
-
-    state->sysdb_dns->values[state->sysdb_dns->num_values].data =
-            (uint8_t *) strdn;
-    state->sysdb_dns->values[state->sysdb_dns->num_values].length =
-            strlen(strdn);
-    state->sysdb_dns->num_values++;
+    sysdb_dns->values[sysdb_dns->num_values].length =
+            strlen(username);
+    sysdb_dns->num_values++;
 
     return EOK;
 }
 
 static int
 sdap_process_missing_member_2307(struct sdap_process_group_state *state,
-                                 char *member_name, bool *in_transaction,
-                                 time_t now)
+                                 char *member_name, time_t now)
 {
-    int ret, sret;
+    int ret;
     TALLOC_CTX *tmp_ctx;
     const char *filter;
     const char *username;
+    const char *user_dn;
     size_t count;
     struct ldb_message **msgs = NULL;
     static const char *attrs[] = { SYSDB_NAME, NULL };
 
-    if (!in_transaction) return EINVAL;
-
     tmp_ctx = talloc_new(NULL);
     if (!tmp_ctx) return ENOMEM;
 
@@ -870,7 +1003,7 @@ sdap_process_missing_member_2307(struct sdap_process_group_state *state,
     filter = talloc_asprintf(tmp_ctx, "(%s=%s)", SYSDB_NAME_ALIAS, member_name);
     if (!filter) {
         ret = ENOMEM;
-        goto fail;
+        goto done;
     }
 
     ret = sysdb_search_users(tmp_ctx, state->sysdb, filter,
@@ -881,74 +1014,51 @@ sdap_process_missing_member_2307(struct sdap_process_group_state *state,
         if (count != 1) {
             DEBUG(1, ("More than one entry with this alias?\n"));
             ret = EIO;
-            goto fail;
+            goto done;
         }
 
         /* fill username with primary name */
         username = ldb_msg_find_attr_as_string(msgs[0], SYSDB_NAME, NULL);
-        goto done;
-    } else if (ret != EOK && ret != ENOENT) {
+        if (username == NULL) {
+            ret = EINVAL;
+            DEBUG(SSSDBG_MINOR_FAILURE, ("Inconsistent sysdb: user "
+                                         "without primary name?\n"));
+            goto done;
+        }
+        user_dn = sysdb_user_strdn(tmp_ctx, state->dom->name, username);
+        if (username == NULL) {
+            return ENOMEM;
+        }
+
+        ret = sdap_add_group_member_2307(state->sysdb_dns, state->dom, user_dn);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, ("Could not add group member %s\n", username));
+        }
+    } else if (ret == ENOENT || count == 0) {
+        /* The entry really does not exist, add a ghost */
+        DEBUG(SSSDBG_TRACE_FUNC, ("Adding a ghost entry\n"));
+        ret = sdap_add_group_member_2307(state->ghost_dns, state->dom, member_name);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, ("Could not add group member %s\n", member_name));
+        }
+    } else {
         ret = EIO;
-        goto fail;
     }
 
-    username = member_name;
-    /* The entry really does not exist, add a fake entry */
-    DEBUG(7, ("Adding a dummy entry\n"));
-
-    if (!*in_transaction) {
-        ret = sysdb_transaction_start(state->sysdb);
-        if (ret != EOK) {
-            DEBUG(1, ("Cannot start sysdb transaction: [%d]: %s\n",
-                       ret, strerror(ret)));
-            return ret;
-        }
-        *in_transaction = true;
-    }
-
-    ret = sysdb_add_fake_user(state->sysdb, username, NULL, now);
-    if (ret != EOK) {
-        DEBUG(1, ("Cannot store fake user entry: [%d]: %s\n",
-                  ret, strerror(ret)));
-        goto fail;
-    }
-
-    /*
-     * Convert the just received DN into the corresponding sysdb DN
-     * for saving into member attribute of the group
-     */
 done:
-    ret = sdap_add_group_member_2307(state, username);
-    if (ret != EOK) {
-        DEBUG(1, ("Could not add group member %s\n", username));
-        goto fail;
-    }
-
     talloc_free(tmp_ctx);
-    return EOK;
-fail:
-    talloc_free(tmp_ctx);
-    if (*in_transaction) {
-        sret = sysdb_transaction_cancel(state->sysdb);
-        if (sret == EOK) {
-            *in_transaction = false;
-        } else {
-            DEBUG(0, ("Unable to cancel transaction! [%d][%s]\n",
-                       sret, strerror(sret)));
-        }
-    }
     return ret;
 }
 
 static int
 sdap_process_group_members_2307(struct sdap_process_group_state *state,
-                                struct ldb_message_element *memberel)
+                                struct ldb_message_element *memberel,
+                                struct ldb_message_element *ghostel)
 {
     struct ldb_message *msg;
-    bool in_transaction = false;
     char *member_name;
+    char *userdn;
     int ret;
-    errno_t sret;
     time_t now;
     int i;
 
@@ -968,7 +1078,12 @@ sdap_process_group_members_2307(struct sdap_process_group_state *state,
              */
             DEBUG(7, ("Member already cached in sysdb: %s\n", member_name));
 
-            ret = sdap_add_group_member_2307(state, member_name);
+            userdn = sysdb_user_strdn(state->sysdb_dns, state->dom->name, member_name);
+            if (userdn == NULL) {
+                return ENOMEM;
+            }
+
+            ret = sdap_add_group_member_2307(state->sysdb_dns, state->dom, userdn);
             if (ret != EOK) {
                 DEBUG(1, ("Could not add member %s into sysdb\n", member_name));
                 goto done;
@@ -978,8 +1093,7 @@ sdap_process_group_members_2307(struct sdap_process_group_state *state,
             DEBUG(7, ("member #%d (%s): not found in sysdb\n",
                        i, member_name));
 
-            ret = sdap_process_missing_member_2307(state, member_name,
-                                                   &in_transaction, now);
+            ret = sdap_process_missing_member_2307(state, member_name, now);
             if (ret != EOK) {
                 DEBUG(1, ("Error processing missing member #%d (%s):\n",
                           i, member_name));
@@ -992,30 +1106,15 @@ sdap_process_group_members_2307(struct sdap_process_group_state *state,
         }
     }
 
-    /* sdap_process_missing_member_2307 starts transaction */
-    if (in_transaction) {
-        ret = sysdb_transaction_commit(state->sysdb);
-        if (ret) {
-            DEBUG(2, ("Cannot commit sysdb transaction\n"));
-            goto done;
-        }
-        in_transaction = false;
-    }
-
     ret = EOK;
     talloc_free(memberel->values);
     memberel->values = talloc_steal(state->group, state->sysdb_dns->values);
     memberel->num_values = state->sysdb_dns->num_values;
+    talloc_free(ghostel->values);
+    ghostel->values = talloc_steal(state->group, state->ghost_dns->values);
+    ghostel->num_values = state->ghost_dns->num_values;
 
 done:
-    if (in_transaction) {
-        /* If the transaction is still active here, we need to cancel it */
-        sret = sysdb_transaction_cancel(state->sysdb);
-        if (sret != EOK) {
-            DEBUG(0, ("Unable to cancel transaction! [%d][%s]\n",
-                      sret, strerror(sret)));
-        }
-    }
     return ret;
 }
 
@@ -1029,8 +1128,7 @@ static void sdap_process_group_members(struct tevent_req *subreq)
     struct sdap_process_group_state *state =
                         tevent_req_data(req, struct sdap_process_group_state);
     struct ldb_message_element *el;
-    struct ldb_dn *dn;
-    char* dn_string;
+    uint8_t* name_string;
 
     state->check_count--;
     DEBUG(9, ("Members remaining: %d\n", state->check_count));
@@ -1055,31 +1153,12 @@ static void sdap_process_group_members(struct tevent_req *subreq)
         goto next;
     }
 
-    /*
-     * Convert the just received DN into the corresponding sysdb DN
-     * for later usage by sdap_save_groups()
-     */
-    dn = sysdb_user_dn(state->sysdb, state, state->dom->name,
-                       (char*)el[0].values[0].data);
-    if (!dn) {
-        tevent_req_error(req, ENOMEM);
-        return;
-    }
-
-    dn_string = ldb_dn_alloc_linearized(state->group, dn);
-    if (!dn_string) {
-        tevent_req_error(req, ENOMEM);
-        return;
-    }
-
-    state->sysdb_dns->values[state->sysdb_dns->num_values].data =
-            (uint8_t*)dn_string;
-    state->sysdb_dns->values[state->sysdb_dns->num_values].length =
-            strlen(dn_string);
-    state->sysdb_dns->num_values++;
-
-    state->new_members[state->member_idx] = usr_attrs[0];
-    state->member_idx++;
+    name_string = el[0].values[0].data;
+    state->ghost_dns->values[state->ghost_dns->num_values].data =
+            talloc_steal(state->ghost_dns->values, name_string);
+    state->ghost_dns->values[state->ghost_dns->num_values].length =
+            strlen((char *)name_string);
+    state->ghost_dns->num_values++;
 
 next:
     if (ret) {
@@ -1110,21 +1189,13 @@ next:
     }
 
     if (state->check_count == 0) {
-        ret = sdap_save_users(state, state->sysdb,
-                              state->dom, state->opts,
-                              state->new_members, state->count, NULL);
-        if (ret) {
-            DEBUG(2, ("Failed to store users.\n"));
-            tevent_req_error(req, ret);
-            return;
-        }
-
         /*
          * To avoid redundant sysdb lookups, populate the "member" attribute
          * of the group entry with the sysdb DNs of the members.
          */
         ret = sysdb_attrs_get_el(state->group,
-                state->opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name, &el);
+                        state->opts->group_map[SDAP_AT_GROUP_MEMBER].sys_name,
+                        &el);
         if (ret != EOK) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   ("Failed to get the group member attribute [%d]: %s\n",
@@ -1134,6 +1205,14 @@ next:
         }
         el->values = talloc_steal(state->group, state->sysdb_dns->values);
         el->num_values = state->sysdb_dns->num_values;
+
+        ret = sysdb_attrs_get_el(state->group, SYSDB_GHOST, &el);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+        el->values = talloc_steal(state->group, state->ghost_dns->values);
+        el->num_values = state->ghost_dns->num_values;
         DEBUG(9, ("Processed Group - Done\n"));
         tevent_req_done(req);
     }
@@ -1446,7 +1525,8 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
         DEBUG(9, ("Saving groups without members first "
                   "to allow unrolling of nested groups.\n"));
         ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts,
-                               state->groups, state->count, false, NULL);
+                               state->groups, state->count, false,
+                               NULL, NULL);
         if (ret) {
             DEBUG(2, ("Failed to store groups.\n"));
             tevent_req_error(req, ret);
@@ -1497,7 +1577,7 @@ static void sdap_get_groups_done(struct tevent_req *subreq)
         DEBUG(9, ("All groups processed\n"));
 
         ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts,
-                               state->groups, state->count, true,
+                               state->groups, state->count, true, NULL,
                                &state->higher_usn);
         if (ret) {
             DEBUG(2, ("Failed to store groups.\n"));
@@ -1530,10 +1610,12 @@ int sdap_get_groups_recv(struct tevent_req *req,
     return EOK;
 }
 
-static errno_t sdap_nested_group_populate_users(struct sysdb_ctx *sysdb,
+static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx,
+                                                struct sysdb_ctx *sysdb,
                                                 struct sdap_options *opts,
                                                 struct sysdb_attrs **users,
-                                                int num_users);
+                                                int num_users,
+                                                hash_table_t **_ghosts);
 
 static void sdap_nested_done(struct tevent_req *subreq)
 {
@@ -1546,6 +1628,7 @@ static void sdap_nested_done(struct tevent_req *subreq)
     bool in_transaction = false;
     struct sysdb_attrs **users = NULL;
     struct sysdb_attrs **groups = NULL;
+    hash_table_t *ghosts;
     struct tevent_req *req = tevent_req_callback_data(subreq,
                                                       struct tevent_req);
     struct sdap_get_groups_state *state = tevent_req_data(req,
@@ -1608,14 +1691,15 @@ static void sdap_nested_done(struct tevent_req *subreq)
     }
     in_transaction = true;
 
-    ret = sdap_nested_group_populate_users(state->sysdb, state->opts,
-                                           users, user_count);
+    ret = sdap_nested_group_populate_users(state, state->sysdb, state->opts,
+                                           users, user_count, &ghosts);
     if (ret != EOK) {
         goto fail;
     }
 
     ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts,
-                           groups, group_count, false, &state->higher_usn);
+                           groups, group_count, false, ghosts,
+                           &state->higher_usn);
     if (ret != EOK) {
         goto fail;
     }
@@ -1641,10 +1725,12 @@ fail:
     tevent_req_error(req, ret);
 }
 
-static errno_t sdap_nested_group_populate_users(struct sysdb_ctx *sysdb,
+static errno_t sdap_nested_group_populate_users(TALLOC_CTX *mem_ctx,
+                                                struct sysdb_ctx *sysdb,
                                                 struct sdap_options *opts,
                                                 struct sysdb_attrs **users,
-                                                int num_users)
+                                                int num_users,
+                                                hash_table_t **_ghosts)
 {
     int i;
     errno_t ret, sret;
@@ -1659,24 +1745,36 @@ static errno_t sdap_nested_group_populate_users(struct sysdb_ctx *sysdb,
     const char *sysdb_name;
     struct sysdb_attrs *attrs;
     static const char *search_attrs[] = { SYSDB_NAME, NULL };
+    hash_table_t *ghosts;
+    hash_key_t key;
+    hash_value_t value;
     size_t count;
-    time_t now;
+
+    if (_ghosts == NULL) {
+        return EINVAL;
+    }
 
     if (num_users == 0) {
         /* Nothing to do if there are no users */
+        *_ghosts = NULL;
         return EOK;
     }
 
     tmp_ctx = talloc_new(NULL);
     if (!tmp_ctx) return ENOMEM;
 
+    ret = sss_hash_create(tmp_ctx, num_users, &ghosts);
+    if (ret != HASH_SUCCESS) {
+        ret = ENOMEM;
+        goto done;
+    }
+
     ret = sysdb_transaction_start(sysdb);
     if (ret) {
         DEBUG(1, ("Failed to start transaction!\n"));
         goto done;
     }
 
-    now = time(NULL);
     for (i = 0; i < num_users; i++) {
         ret = sysdb_attrs_primary_name(sysdb, users[i],
                                     opts->user_map[SDAP_AT_USER_NAME].name,
@@ -1744,15 +1842,17 @@ static errno_t sdap_nested_group_populate_users(struct sysdb_ctx *sysdb,
             ret = sysdb_set_user_attr(sysdb, sysdb_name, attrs, SYSDB_MOD_REP);
             if (ret != EOK) goto done;
         }
-
-        /* If the entry does not exist add a fake user record */
-        ret = sysdb_add_fake_user(sysdb, username, original_dn, now);
-        if (ret != EOK) {
-            DEBUG(1, ("Cannot store fake user entry, ignoring: [%d]: %s\n",
-                      ret, strerror(ret)));
-            continue;
-        } else {
-            DEBUG(9, ("Added incomplete user %s!\n", username));
+        else
+        {
+            key.type = HASH_KEY_STRING;
+            key.str = discard_const(original_dn);
+            value.type = HASH_VALUE_PTR;
+            value.ptr = discard_const(username);
+            ret = hash_enter(ghosts, &key, &value);
+            if (ret != HASH_SUCCESS) {
+                ret = ENOMEM;
+                goto done;
+            }
         }
     }
 
@@ -1764,13 +1864,16 @@ static errno_t sdap_nested_group_populate_users(struct sysdb_ctx *sysdb,
 
     ret = EOK;
 done:
-    talloc_zfree(tmp_ctx);
     if (ret != EOK) {
         sret = sysdb_transaction_cancel(sysdb);
         if (sret != EOK) {
             DEBUG(2, ("Could not cancel transaction\n"));
         }
+        *_ghosts = NULL;
+    } else {
+        *_ghosts = talloc_steal(mem_ctx, ghosts);
     }
+    talloc_zfree(tmp_ctx);
     return ret;
 }
 
@@ -2242,7 +2345,6 @@ sdap_nested_group_check_cache(TALLOC_CTX *mem_ctx,
     struct ldb_message **msgs = NULL;
     char *member_dn;
     uint64_t expiration;
-    uint64_t create_time;
     uid_t user_uid;
     time_t now = time(NULL);
     static const char *attrs[] = { SYSDB_CACHE_EXPIRE, SYSDB_UIDNUM,
@@ -2288,11 +2390,9 @@ sdap_nested_group_check_cache(TALLOC_CTX *mem_ctx,
 
         user_uid = ldb_msg_find_attr_as_uint64(msgs[0], SYSDB_UIDNUM, 0);
         if (!user_uid) {
-            /* Refresh the fake user if he was created before cache_timeout */
-            create_time = ldb_msg_find_attr_as_uint64(msgs[0],
-                                                    SYSDB_CREATE_TIME,
-                                                    0);
-            expiration = create_time + dom->user_timeout;
+            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],
-- 
1.7.10.2

