From 8317576b049ffe9c5c599f4795fab1eaffff0ef9 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 25 Sep 2017 16:51:49 +0200
Subject: [PATCH 1/4] mmap_cache: use record size not header size

---
 src/util/mmap_cache.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/util/mmap_cache.h b/src/util/mmap_cache.h
index 63e096027..db5973fab 100644
--- a/src/util/mmap_cache.h
+++ b/src/util/mmap_cache.h
@@ -37,6 +37,7 @@ typedef uint32_t rel_ptr_t;
 #define MC_ALIGN32(size) ( ((size) + MC_32 -1) & (~(MC_32 -1)) )
 #define MC_ALIGN64(size) ( ((size) + MC_64 -1) & (~(MC_64 -1)) )
 #define MC_HEADER_SIZE MC_ALIGN64(sizeof(struct sss_mc_header))
+#define MC_REC_SIZE MC_ALIGN64(sizeof(struct sss_mc_rec))
 
 #define MC_HT_SIZE(elems) ( (elems) * MC_32 )
 #define MC_HT_ELEMS(size) ( (size) / MC_32 )
@@ -73,7 +74,7 @@ typedef uint32_t rel_ptr_t;
 #define MC_VALID_BARRIER(val) (((val) & 0xff000000) == 0xf0000000)
 
 #define MC_CHECK_RECORD_LENGTH(mc_ctx, rec) \
-        ((rec)->len >= MC_HEADER_SIZE && (rec)->len != MC_INVALID_VAL32 \
+        ((rec)->len >= MC_REC_SIZE && (rec)->len != MC_INVALID_VAL32 \
          && ((rec)->len <= ((mc_ctx)->dt_size \
                             - MC_PTR_DIFF(rec, (mc_ctx)->data_table))))
 

From 0ea575471480a5a06e20f4fc2310c89d040daa28 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Wed, 18 Oct 2017 15:58:38 +0200
Subject: [PATCH 2/4] mmap_cache: add SID and type to struct sss_mc_rec

The type is used to indicate a new type the memory cache record which
does not contain the user or group data but a link to a data object.
This new type can be used to handled alias names or UPN and will help in
case-insensitive setup if clients use different cases during lookups.

The added third hash slot will be used to store the hash of the SID of
the user or group if available.

This patch updates the major version number of the memory cache as well.

Realated to https://pagure.io/SSSD/sssd/issue/3193
            https://pagure.io/SSSD/sssd/issue/2727
---
 src/util/mmap_cache.h | 35 ++++++++++++++++++++++++++---------
 1 file changed, 26 insertions(+), 9 deletions(-)

diff --git a/src/util/mmap_cache.h b/src/util/mmap_cache.h
index db5973fab..2eb03330b 100644
--- a/src/util/mmap_cache.h
+++ b/src/util/mmap_cache.h
@@ -54,13 +54,13 @@ typedef uint32_t rel_ptr_t;
 #define MC_INVALID_VAL MC_INVALID_VAL32
 
 /*
- * 40 seem a good compromise for slot size
- * 4 blocks are enough for the average passwd entry of 42 bytes
- * passwd records have 84 bytes of overhead, 160 - 82 = 78 bytes
- * 3 blocks can contain a very minimal entry, 120 - 82 = 38 bytes
+ * 48 seem a good compromise for slot size
+ * 4 blocks are more than two times the average passwd entry of 42 bytes
+ * passwd records have 84 bytes of overhead, 192 - 82 = 110 bytes
+ * 3 blocks can contain a typical entry, 144 - 82 = 62 bytes
  *
  * 3 blocks are enough for groups w/o users (private user groups)
- * group records have 68 bytes of overhead, 120 - 66 = 54 bytes
+ * group records have 68 bytes of overhead, 144 - 66 = 78 bytes
  */
 #define MC_SLOT_SIZE 40
 #define MC_SIZE_TO_SLOTS(len) (((len) + (MC_SLOT_SIZE - 1)) / MC_SLOT_SIZE)
@@ -79,8 +79,8 @@ typedef uint32_t rel_ptr_t;
                             - MC_PTR_DIFF(rec, (mc_ctx)->data_table))))
 
 
-#define SSS_MC_MAJOR_VNO    1
-#define SSS_MC_MINOR_VNO    1
+#define SSS_MC_MAJOR_VNO    2
+#define SSS_MC_MINOR_VNO    0
 
 #define SSS_MC_HEADER_UNINIT    0   /* after ftruncate or before reset */
 #define SSS_MC_HEADER_ALIVE     1   /* current and in use */
@@ -103,6 +103,13 @@ struct sss_mc_header {
     uint32_t b2;            /* barrier 2 */
 };
 
+#define SSS_MC_REC_TYPE_DATA 0 /* record with the cache data */
+                               /* Would it make sense to use more specific
+                                * data types like SSS_MC_REC_TYPE_PWD_DATA,
+                                * SSS_MC_REC_TYPE_GRP_DATA and
+                                * SSS_MC_REC_TYPE_INITGR_DATA ? */
+#define SSS_MC_REC_TYPE_LINK 1 /* link record for an alias name */
+
 struct sss_mc_rec {
     uint32_t b1;            /* barrier 1 */
     uint32_t len;           /* total record length including record data */
@@ -111,10 +118,14 @@ struct sss_mc_rec {
                             /* next1 is related to hash1 */
     rel_ptr_t next2;        /* ptr of next record rel to data_table */
                             /* next2 is related to hash2 */
+    rel_ptr_t next3;        /* ptr of next record rel to data_table */
+                            /* next3 is related to hash3 */
     uint32_t hash1;         /* val of first hash (usually name of record) */
     uint32_t hash2;         /* val of second hash (usually id of record) */
-    uint32_t padding;       /* padding & reserved for future changes */
-    uint32_t b2;            /* barrier 2 - 32 bytes mark, fits a slot */
+    uint32_t hash3;         /* val of third hash (usually SID of record) */
+    uint32_t type;          /* type of record, SSS_MC_REC_TYPE_DATA or
+                             * SSS_MC_REC_TYPE_LINK */
+    uint32_t b2;            /* barrier 2 - 40 bytes mark, fits a slot */
     char data[0];
 };
 
@@ -150,6 +161,12 @@ struct sss_mc_initgr_data {
                              * after gids */
 };
 
+struct sss_mc_link_data {
+    rel_ptr_t name;         /* ptr to name string, rel. to struct base addr */
+    uint32_t hash;          /* name hash of the related data record */
+    char strs[0];           /* zero terminated alias name */
+};
+
 #pragma pack()
 
 

From 09fcfc938549ed1210c1ab277c01242a1d2f27fd Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 25 Sep 2017 16:52:26 +0200
Subject: [PATCH 3/4] mmap_cache: add link record to handle alias names

If the name used to lookup a user or group object is different from the
"canonical" name, i.e. the name returned by getpwnam() or getgrnam()
calls, a link record is added to the memory hash to speed up further
lookups with this name.

Related to https://pagure.io/SSSD/sssd/issue/3193
---
 src/responder/nss/nss_protocol_grent.c | 16 ++++++++
 src/responder/nss/nss_protocol_pwent.c | 16 ++++++++
 src/responder/nss/nsssrv_mmap_cache.c  | 69 +++++++++++++++++++++++++++++++++-
 src/responder/nss/nsssrv_mmap_cache.h  |  4 ++
 src/sss_client/nss_mc.h                |  3 ++
 src/sss_client/nss_mc_common.c         | 49 ++++++++++++++++++++++++
 src/sss_client/nss_mc_group.c          | 27 ++++++++++++-
 src/sss_client/nss_mc_passwd.c         | 27 ++++++++++++-
 8 files changed, 206 insertions(+), 5 deletions(-)

diff --git a/src/responder/nss/nss_protocol_grent.c b/src/responder/nss/nss_protocol_grent.c
index ee228c722..0cb58a3f1 100644
--- a/src/responder/nss/nss_protocol_grent.c
+++ b/src/responder/nss/nss_protocol_grent.c
@@ -201,6 +201,7 @@ nss_protocol_fill_grent(struct nss_ctx *nss_ctx,
     struct ldb_message *msg;
     struct sized_string *name;
     struct sized_string pwfield;
+    struct sized_string inputname;
     uint32_t gid;
     uint32_t num_results;
     uint32_t num_members;
@@ -286,6 +287,21 @@ nss_protocol_fill_grent(struct nss_ctx *nss_ctx,
                       "Failed to store group %s (%s) in mem-cache [%d]: %s!\n",
                       name->str, result->domain->name, ret, sss_strerror(ret));
             }
+
+            if (cmd_ctx->rawname != NULL
+                    && strcmp(name->str, cmd_ctx->rawname) != 0) {
+                inputname.str = cmd_ctx->rawname;
+                inputname.len = strlen(inputname.str) + 1;
+                ret = sss_mmap_cache_link_store(&nss_ctx->grp_mc_ctx, name,
+                                                &inputname);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_MINOR_FAILURE,
+                          "Failed to store link %s to group %s (%s) "
+                          "in mmap cache [%d]: %s!\n", cmd_ctx->rawname,
+                          name->str, result->domain->name, ret,
+                          sss_strerror(ret));
+                }
+            }
         }
     }
 
diff --git a/src/responder/nss/nss_protocol_pwent.c b/src/responder/nss/nss_protocol_pwent.c
index db5c071e2..780f2f783 100644
--- a/src/responder/nss/nss_protocol_pwent.c
+++ b/src/responder/nss/nss_protocol_pwent.c
@@ -236,6 +236,7 @@ nss_protocol_fill_pwent(struct nss_ctx *nss_ctx,
     struct sized_string gecos;
     struct sized_string homedir;
     struct sized_string shell;
+    struct sized_string inputname;
     uint32_t gid;
     uint32_t uid;
     uint32_t num_results;
@@ -304,6 +305,21 @@ nss_protocol_fill_pwent(struct nss_ctx *nss_ctx,
                       "Failed to store user %s (%s) in mmap cache [%d]: %s!\n",
                       name->str, result->domain->name, ret, sss_strerror(ret));
             }
+
+            if (cmd_ctx->rawname != NULL
+                    && strcmp(name->str, cmd_ctx->rawname) != 0) {
+                inputname.str = cmd_ctx->rawname;
+                inputname.len = strlen(inputname.str) + 1;
+                ret = sss_mmap_cache_link_store(&nss_ctx->pwd_mc_ctx, name,
+                                                &inputname);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_MINOR_FAILURE,
+                          "Failed to store link %s to user %s (%s) "
+                          "in mmap cache [%d]: %s!\n", cmd_ctx->rawname,
+                          name->str, result->domain->name, ret,
+                          sss_strerror(ret));
+                }
+            }
         }
     }
 
diff --git a/src/responder/nss/nsssrv_mmap_cache.c b/src/responder/nss/nsssrv_mmap_cache.c
index a87ad646f..62f36ea73 100644
--- a/src/responder/nss/nsssrv_mmap_cache.c
+++ b/src/responder/nss/nsssrv_mmap_cache.c
@@ -315,8 +315,11 @@ static void sss_mc_invalidate_rec(struct sss_mc_ctx *mcc,
     rec->expire = MC_INVALID_VAL64;
     rec->next1 = MC_INVALID_VAL32;
     rec->next2 = MC_INVALID_VAL32;
+    rec->next3 = MC_INVALID_VAL32;
     rec->hash1 = MC_INVALID_VAL32;
     rec->hash2 = MC_INVALID_VAL32;
+    rec->hash3 = MC_INVALID_VAL32;
+    rec->type = MC_INVALID_VAL32;
     MC_LOWER_BARRIER(rec);
 }
 
@@ -621,7 +624,8 @@ static errno_t sss_mc_get_record(struct sss_mc_ctx **_mcc,
     rec->len = rec_len;
     rec->next1 = MC_INVALID_VAL;
     rec->next2 = MC_INVALID_VAL;
-    rec->padding = MC_INVALID_VAL;
+    rec->next3 = MC_INVALID_VAL;
+    rec->type = MC_INVALID_VAL;
     MC_LOWER_BARRIER(rec);
 
     /* and now mark slots as used */
@@ -641,8 +645,24 @@ static inline void sss_mmap_set_rec_header(struct sss_mc_ctx *mcc,
 {
     rec->len = len;
     rec->expire = time(NULL) + ttl;
+    rec->type = SSS_MC_REC_TYPE_DATA;
     rec->hash1 = sss_mc_hash(mcc, key1, key1_len);
     rec->hash2 = sss_mc_hash(mcc, key2, key2_len);
+    rec->hash3 = MC_INVALID_VAL;
+}
+
+static inline void sss_mmap_set_link_rec_header(struct sss_mc_ctx *mcc,
+                                                struct sss_mc_rec *rec,
+                                                size_t len, int ttl,
+                                                const char *key1,
+                                                size_t key1_len)
+{
+    rec->len = len;
+    rec->expire = time(NULL) + ttl;
+    rec->type = SSS_MC_REC_TYPE_LINK;
+    rec->hash1 = sss_mc_hash(mcc, key1, key1_len);
+    rec->hash2 = MC_INVALID_VAL;
+    rec->hash3 = MC_INVALID_VAL;
 }
 
 static inline void sss_mmap_chain_in_rec(struct sss_mc_ctx *mcc,
@@ -683,6 +703,53 @@ static errno_t sss_mmap_cache_invalidate(struct sss_mc_ctx *mcc,
  * passwd map
  ***************************************************************************/
 
+errno_t sss_mmap_cache_link_store(struct sss_mc_ctx **_mcc,
+                                  struct sized_string *canonical_name,
+                                  struct sized_string *name)
+{
+    struct sss_mc_ctx *mcc = *_mcc;
+    struct sss_mc_rec *rec;
+    size_t rec_len;
+    int ret;
+    struct sss_mc_link_data *data;
+
+    if (mcc == NULL) {
+        /* cache not initialized ? */
+        return EINVAL;
+    }
+
+    rec_len = sizeof(struct sss_mc_rec) + sizeof(struct sss_mc_link_data)
+                                        + name->len;
+
+    if (rec_len > mcc->dt_size) {
+        return ENOMEM;
+    }
+
+    ret = sss_mc_get_record(_mcc, rec_len, name, &rec);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    data = (struct sss_mc_link_data *) rec->data;
+
+    MC_RAISE_BARRIER(rec);
+
+    /* header */
+    sss_mmap_set_link_rec_header(mcc, rec, rec_len, mcc->valid_time_slot,
+                                 name->str, name->len);
+
+    data->name = MC_PTR_DIFF(data->strs, data);
+    data->hash = sss_mc_hash(mcc, canonical_name->str, canonical_name->len);
+    memcpy(&data->strs[0], name->str, name->len);
+
+    MC_LOWER_BARRIER(rec);
+
+    /* finally chain the rec in the hash table */
+    sss_mc_add_rec_to_chain(mcc, rec, rec->hash1);
+
+    return EOK;
+}
+
 errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
                                 struct sized_string *name,
                                 struct sized_string *pw,
diff --git a/src/responder/nss/nsssrv_mmap_cache.h b/src/responder/nss/nsssrv_mmap_cache.h
index b84fbc8ed..90709cd49 100644
--- a/src/responder/nss/nsssrv_mmap_cache.h
+++ b/src/responder/nss/nsssrv_mmap_cache.h
@@ -37,6 +37,10 @@ errno_t sss_mmap_cache_init(TALLOC_CTX *mem_ctx, const char *name,
                             enum sss_mc_type type, size_t n_elem,
                             time_t valid_time, struct sss_mc_ctx **mcc);
 
+errno_t sss_mmap_cache_link_store(struct sss_mc_ctx **_mcc,
+                                  struct sized_string *canonical_name,
+                                  struct sized_string *name);
+
 errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
                                 struct sized_string *name,
                                 struct sized_string *pw,
diff --git a/src/sss_client/nss_mc.h b/src/sss_client/nss_mc.h
index a39d45f14..68f451a7b 100644
--- a/src/sss_client/nss_mc.h
+++ b/src/sss_client/nss_mc.h
@@ -68,6 +68,9 @@ errno_t sss_nss_str_ptr_from_buffer(char **str, void **cookie,
                                     char *buf, size_t len);
 uint32_t sss_nss_mc_next_slot_with_hash(struct sss_mc_rec *rec,
                                         uint32_t hash);
+errno_t sss_nss_mc_find_rec_by_hash(struct sss_cli_mc_ctx *ctx,
+                                           uint32_t hash,
+                                           struct sss_mc_rec **_rec);
 
 /* passwd db */
 errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
diff --git a/src/sss_client/nss_mc_common.c b/src/sss_client/nss_mc_common.c
index 696d67246..d812dfa53 100644
--- a/src/sss_client/nss_mc_common.c
+++ b/src/sss_client/nss_mc_common.c
@@ -381,3 +381,52 @@ uint32_t sss_nss_mc_next_slot_with_hash(struct sss_mc_rec *rec,
     }
 
 }
+
+errno_t sss_nss_mc_find_rec_by_hash(struct sss_cli_mc_ctx *ctx,
+                                           uint32_t hash,
+                                           struct sss_mc_rec **_rec)
+{
+    uint32_t slot;
+    int ret;
+    struct sss_mc_rec *rec = NULL;
+
+    slot = ctx->hash_table[hash];
+
+    /* If slot is not within the bounds of mmaped region and
+     * it's value is not MC_INVALID_VAL, then the cache is
+     * probbably corrupted. */
+    while (MC_SLOT_WITHIN_BOUNDS(slot, ctx->dt_size)) {
+        /* free record from previous iteration */
+        free(rec);
+        rec = NULL;
+
+        ret = sss_nss_mc_get_record(ctx, slot, &rec);
+        if (ret) {
+            goto done;
+        }
+
+        /* check record matches what we are searching for */
+        if (hash == rec->hash1) {
+            break;
+        }
+
+        slot = sss_nss_mc_next_slot_with_hash(rec, hash);
+    }
+
+    if (!MC_SLOT_WITHIN_BOUNDS(slot, ctx->dt_size)) {
+        ret = ENOENT;
+        goto done;
+    }
+
+    *_rec = rec;
+
+    ret = 0;
+
+done:
+    if (ret) {
+        free(rec);
+    }
+
+    return ret;
+}
+
diff --git a/src/sss_client/nss_mc_group.c b/src/sss_client/nss_mc_group.c
index ce88d42fd..ecd9cf376 100644
--- a/src/sss_client/nss_mc_group.c
+++ b/src/sss_client/nss_mc_group.c
@@ -107,13 +107,16 @@ errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
                             char *buffer, size_t buflen)
 {
     struct sss_mc_rec *rec = NULL;
+    struct sss_mc_rec *new_rec = NULL;
     struct sss_mc_grp_data *data;
+    struct sss_mc_link_data *link_data;
     char *rec_name;
     uint32_t hash;
     uint32_t slot;
     int ret;
     const size_t strs_offset = offsetof(struct sss_mc_grp_data, strs);
     size_t data_size;
+    bool link = false;
 
     ret = sss_nss_mc_get_ctx("group", &gr_mc_ctx);
     if (ret) {
@@ -147,13 +150,33 @@ errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
             continue;
         }
 
+        if (rec->type == SSS_MC_REC_TYPE_LINK) {
+            link_data = (struct sss_mc_link_data *) rec->data;
+            rec_name = (char *) link_data + link_data->name;
+            if (strcmp(name, rec_name) != 0) {
+                slot = sss_nss_mc_next_slot_with_hash(rec, hash);
+                continue;
+            }
+
+            ret = sss_nss_mc_find_rec_by_hash(&gr_mc_ctx, link_data->hash,
+                                              &new_rec);
+            if (ret) {
+                goto done;
+            }
+            free(rec);
+            rec = new_rec;
+            link = true;
+        }
+
         data = (struct sss_mc_grp_data *)rec->data;
         /* Integrity check
+         * - record must be a data record
          * - name_len cannot be longer than all strings
          * - data->name cannot point outside strings
          * - all strings must be within copy of record
          * - size of record must be lower that data table size */
-        if (name_len > data->strs_len
+        if (rec->type != SSS_MC_REC_TYPE_DATA
+            || name_len > data->strs_len
             || (data->name + name_len) > (strs_offset + data->strs_len)
             || data->strs_len > rec->len
             || rec->len > data_size) {
@@ -162,7 +185,7 @@ errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
         }
 
         rec_name = (char *)data + data->name;
-        if (strcmp(name, rec_name) == 0) {
+        if (link || strcmp(name, rec_name) == 0) {
             break;
         }
 
diff --git a/src/sss_client/nss_mc_passwd.c b/src/sss_client/nss_mc_passwd.c
index 0da7ad0ae..464727e7d 100644
--- a/src/sss_client/nss_mc_passwd.c
+++ b/src/sss_client/nss_mc_passwd.c
@@ -100,13 +100,16 @@ errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
                             char *buffer, size_t buflen)
 {
     struct sss_mc_rec *rec = NULL;
+    struct sss_mc_rec *new_rec = NULL;
     struct sss_mc_pwd_data *data;
+    struct sss_mc_link_data *link_data;
     char *rec_name;
     uint32_t hash;
     uint32_t slot;
     int ret;
     const size_t strs_offset = offsetof(struct sss_mc_pwd_data, strs);
     size_t data_size;
+    bool link = false;
 
     ret = sss_nss_mc_get_ctx("passwd", &pw_mc_ctx);
     if (ret) {
@@ -140,13 +143,33 @@ errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
             continue;
         }
 
+        if (rec->type == SSS_MC_REC_TYPE_LINK) {
+            link_data = (struct sss_mc_link_data *) rec->data;
+            rec_name = (char *) link_data + link_data->name;
+            if (strcmp(name, rec_name) != 0) {
+                slot = sss_nss_mc_next_slot_with_hash(rec, hash);
+                continue;
+            }
+
+            ret = sss_nss_mc_find_rec_by_hash(&pw_mc_ctx, link_data->hash,
+                                              &new_rec);
+            if (ret) {
+                goto done;
+            }
+            free(rec);
+            rec = new_rec;
+            link = true;
+        }
+
         data = (struct sss_mc_pwd_data *)rec->data;
         /* Integrity check
+         * - record must be a data record
          * - name_len cannot be longer than all strings
          * - data->name cannot point outside strings
          * - all strings must be within copy of record
          * - size of record must be lower that data table size */
-        if (name_len > data->strs_len
+        if (rec->type != SSS_MC_REC_TYPE_DATA
+            || name_len > data->strs_len
             || (data->name + name_len) > (strs_offset + data->strs_len)
             || data->strs_len > rec->len
             || rec->len > data_size) {
@@ -155,7 +178,7 @@ errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
         }
 
         rec_name = (char *)data + data->name;
-        if (strcmp(name, rec_name) == 0) {
+        if (link || strcmp(name, rec_name) == 0) {
             break;
         }
 

From 7913da9567e0cc40423f3885a98089fee02f8c25 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Fri, 29 Sep 2017 16:16:43 +0200
Subject: [PATCH 4/4] sss_client: add additional data to getpw{nam|uid} results

Related to https://pagure.io/SSSD/sssd/issue/3002
           https://pagure.io/SSSD/sssd/issue/3221
           https://pagure.io/SSSD/sssd/issue/2727
---
 src/responder/nss/nss_private.h        |   6 +
 src/responder/nss/nss_protocol_grent.c |  21 ++-
 src/responder/nss/nss_protocol_pwent.c |  18 ++-
 src/responder/nss/nss_utils.c          |  73 +++++++++
 src/responder/nss/nsssrv_mmap_cache.c  |  15 +-
 src/responder/nss/nsssrv_mmap_cache.h  |   6 +-
 src/sss_client/idmap/sss_nss_idmap.h   |   8 +
 src/sss_client/nss_mc.h                |  20 +++
 src/sss_client/nss_mc_group.c          |  70 ++++++++-
 src/sss_client/nss_mc_passwd.c         |  66 +++++++-
 src/tests/cmocka/test_nss_srv.c        | 273 +++++++++++++++++++++++++++++----
 src/util/util.h                        |   5 +
 12 files changed, 525 insertions(+), 56 deletions(-)

diff --git a/src/responder/nss/nss_private.h b/src/responder/nss/nss_private.h
index a0b573d6e..b5abf43da 100644
--- a/src/responder/nss/nss_private.h
+++ b/src/responder/nss/nss_private.h
@@ -138,4 +138,10 @@ const char *
 nss_get_pwfield(struct nss_ctx *nctx,
                 struct sss_domain_info *dom);
 
+errno_t get_extra_data(TALLOC_CTX *mem_ctx,
+                       struct sss_domain_info *domain,
+                       const char override_space,
+                       struct ldb_message *msg,
+                       struct sized_data *extra_data);
+
 #endif /* _NSS_PRIVATE_H_ */
diff --git a/src/responder/nss/nss_protocol_grent.c b/src/responder/nss/nss_protocol_grent.c
index 0cb58a3f1..05d6655de 100644
--- a/src/responder/nss/nss_protocol_grent.c
+++ b/src/responder/nss/nss_protocol_grent.c
@@ -202,6 +202,7 @@ nss_protocol_fill_grent(struct nss_ctx *nss_ctx,
     struct sized_string *name;
     struct sized_string pwfield;
     struct sized_string inputname;
+    struct sized_data extra_data = { 0 };
     uint32_t gid;
     uint32_t num_results;
     uint32_t num_members;
@@ -209,6 +210,7 @@ nss_protocol_fill_grent(struct nss_ctx *nss_ctx,
     size_t members_size;
     size_t rp;
     size_t rp_members;
+    size_t rp_members_end;
     size_t rp_num_members;
     size_t body_len;
     uint8_t *body;
@@ -242,10 +244,21 @@ nss_protocol_fill_grent(struct nss_ctx *nss_ctx,
             continue;
         }
 
+        if (!cmd_ctx->enumeration) {
+            ret = get_extra_data(tmp_ctx, result->domain,
+                                 nss_ctx->rctx->override_space, msg,
+                                 &extra_data);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_OP_FAILURE, "get_extra_data failed.\n");
+                continue;
+            }
+        }
+
         /* Adjust packet size: gid, num_members + string fields. */
 
         ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)
-                                          + name->len + pwfield.len);
+                                          + name->len + pwfield.len
+                                          + extra_data.len);
         if (ret != EOK) {
             goto done;
         }
@@ -269,6 +282,8 @@ nss_protocol_fill_grent(struct nss_ctx *nss_ctx,
         if (ret != EOK) {
             goto done;
         }
+        rp_members_end = rp;
+        SAFEALIGN_SET_STRING(&body[rp], extra_data.data, extra_data.len, &rp);
 
         sss_packet_get_body(packet, &body, &body_len);
         SAFEALIGN_SET_UINT32(&body[rp_num_members], num_members, NULL);
@@ -278,10 +293,10 @@ nss_protocol_fill_grent(struct nss_ctx *nss_ctx,
         /* Do not store entry in memory cache during enumeration. */
         if (!cmd_ctx->enumeration) {
             members = (char *)&body[rp_members];
-            members_size = body_len - rp_members;
+            members_size = rp_members_end - rp_members;
             ret = sss_mmap_cache_gr_store(&nss_ctx->grp_mc_ctx, name, &pwfield,
                                           gid, num_members, members,
-                                          members_size);
+                                          members_size, &extra_data);
             if (ret != EOK) {
                 DEBUG(SSSDBG_MINOR_FAILURE,
                       "Failed to store group %s (%s) in mem-cache [%d]: %s!\n",
diff --git a/src/responder/nss/nss_protocol_pwent.c b/src/responder/nss/nss_protocol_pwent.c
index 780f2f783..8d627bc40 100644
--- a/src/responder/nss/nss_protocol_pwent.c
+++ b/src/responder/nss/nss_protocol_pwent.c
@@ -237,6 +237,7 @@ nss_protocol_fill_pwent(struct nss_ctx *nss_ctx,
     struct sized_string homedir;
     struct sized_string shell;
     struct sized_string inputname;
+    struct sized_data extra_data = { 0 };
     uint32_t gid;
     uint32_t uid;
     uint32_t num_results;
@@ -273,11 +274,22 @@ nss_protocol_fill_pwent(struct nss_ctx *nss_ctx,
             continue;
         }
 
+        if (!cmd_ctx->enumeration) {
+            ret = get_extra_data(tmp_ctx, result->domain,
+                                 nss_ctx->rctx->override_space, msg,
+                                 &extra_data);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_OP_FAILURE, "get_extra_data failed.\n");
+                continue;
+            }
+        }
+
         /* Adjust packet size: uid, gid + string fields. */
 
         ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)
                                           + name->len + gecos.len + homedir.len
-                                          + shell.len + pwfield.len);
+                                          + shell.len + pwfield.len
+                                          + extra_data.len);
         if (ret != EOK) {
             goto done;
         }
@@ -293,13 +305,15 @@ nss_protocol_fill_pwent(struct nss_ctx *nss_ctx,
         SAFEALIGN_SET_STRING(&body[rp], gecos.str, gecos.len, &rp);
         SAFEALIGN_SET_STRING(&body[rp], homedir.str, homedir.len, &rp);
         SAFEALIGN_SET_STRING(&body[rp], shell.str, shell.len, &rp);
+        SAFEALIGN_SET_STRING(&body[rp], extra_data.data, extra_data.len, &rp);
 
         num_results++;
 
         /* Do not store entry in memory cache during enumeration. */
         if (!cmd_ctx->enumeration) {
             ret = sss_mmap_cache_pw_store(&nss_ctx->pwd_mc_ctx, name, &pwfield,
-                                          uid, gid, &gecos, &homedir, &shell);
+                                          uid, gid, &gecos, &homedir, &shell,
+                                          &extra_data);
             if (ret != EOK) {
                 DEBUG(SSSDBG_MINOR_FAILURE,
                       "Failed to store user %s (%s) in mmap cache [%d]: %s!\n",
diff --git a/src/responder/nss/nss_utils.c b/src/responder/nss/nss_utils.c
index b4950e5a6..628fac923 100644
--- a/src/responder/nss/nss_utils.c
+++ b/src/responder/nss/nss_utils.c
@@ -36,3 +36,76 @@ nss_get_pwfield(struct nss_ctx *nctx,
 
     return nctx->pwfield;
 }
+
+errno_t get_extra_data(TALLOC_CTX *mem_ctx,
+                       struct sss_domain_info *domain,
+                       const char override_space,
+                       struct ldb_message *msg,
+                       struct sized_data *extra_data)
+{
+    const char *name;
+    char *short_name;
+    size_t len = 0;
+    size_t pos = 0;
+    uint8_t *data;
+    const char *flat_name;
+    const char *sid_str;
+
+    if (domain == NULL || domain->name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing domain name.");
+        return EINVAL;
+    }
+
+    /* Use domain->name if domain->flat_name is undefined */
+    /* FIXME: or should it be better "" ? */
+    flat_name = domain->flat_name != NULL ? domain->flat_name : domain->name;
+
+    name = sss_get_name_from_msg(domain, msg);
+    if (name == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "Object has no name.\n");
+        return EINVAL;
+    }
+
+    short_name = sss_output_name(mem_ctx, name, domain->case_preserve,
+                                 override_space);
+    if (short_name == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_output_name failed.\n");
+        return ENOMEM;
+    }
+
+    /* If a SID cannot be found add an empty string */
+    sid_str = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, "");
+
+    len = strlen(short_name) + strlen(domain->name) + strlen(flat_name)
+                             + strlen(sid_str) + 4 +  2 * sizeof(uint32_t);
+    data = talloc_size(mem_ctx, len);
+    if (data == NULL) {
+        talloc_free(short_name);
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+        return ENOMEM;
+    }
+
+    /* overall length of the extra data including the length itself */
+    SAFEALIGN_COPY_UINT32(data + pos, &len, &pos);
+    /* 4 0-terminated strings will follow */
+    SAFEALIGN_SET_UINT32(data + pos, 4, &pos);
+
+    memcpy(data + pos, short_name, strlen(short_name) + 1);
+    pos += strlen(short_name) + 1;
+    talloc_free(short_name);
+
+    memcpy(data + pos, domain->name, strlen(domain->name) + 1);
+    pos += strlen(domain->name) + 1;
+
+    memcpy(data + pos, flat_name, strlen(flat_name) + 1);
+    pos += strlen(flat_name) + 1;
+
+    memcpy(data + pos, sid_str, strlen(sid_str) + 1);
+    pos += strlen(sid_str) + 1;
+
+    extra_data->data = data;
+    extra_data->len = len;
+
+    return EOK;
+}
+
diff --git a/src/responder/nss/nsssrv_mmap_cache.c b/src/responder/nss/nsssrv_mmap_cache.c
index 62f36ea73..1c9c5ac7e 100644
--- a/src/responder/nss/nsssrv_mmap_cache.c
+++ b/src/responder/nss/nsssrv_mmap_cache.c
@@ -756,7 +756,8 @@ errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
                                 uid_t uid, gid_t gid,
                                 struct sized_string *gecos,
                                 struct sized_string *homedir,
-                                struct sized_string *shell)
+                                struct sized_string *shell,
+                                struct sized_data *extra_data)
 {
     struct sss_mc_ctx *mcc = *_mcc;
     struct sss_mc_rec *rec;
@@ -779,7 +780,8 @@ errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
     }
     to_sized_string(&uidkey, uidstr);
 
-    data_len = name->len + pw->len + gecos->len + homedir->len + shell->len;
+    data_len = name->len + pw->len + gecos->len + homedir->len + shell->len
+                         + extra_data->len;
     rec_len = sizeof(struct sss_mc_rec) +
               sizeof(struct sss_mc_pwd_data) +
               data_len;
@@ -816,6 +818,8 @@ errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
     pos += homedir->len;
     memcpy(&data->strs[pos], shell->str, shell->len);
     pos += shell->len;
+    memcpy(&data->strs[pos], extra_data->data, extra_data->len);
+    pos += extra_data->len;
 
     MC_LOWER_BARRIER(rec);
 
@@ -899,7 +903,8 @@ int sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc,
                             struct sized_string *name,
                             struct sized_string *pw,
                             gid_t gid, size_t memnum,
-                            char *membuf, size_t memsize)
+                            char *membuf, size_t memsize,
+                            struct sized_data *extra_data)
 {
     struct sss_mc_ctx *mcc = *_mcc;
     struct sss_mc_rec *rec;
@@ -922,7 +927,7 @@ int sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc,
     }
     to_sized_string(&gidkey, gidstr);
 
-    data_len = name->len + pw->len + memsize;
+    data_len = name->len + pw->len + memsize + extra_data->len;
     rec_len = sizeof(struct sss_mc_rec) +
               sizeof(struct sss_mc_grp_data) +
               data_len;
@@ -955,6 +960,8 @@ int sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc,
     pos += pw->len;
     memcpy(&data->strs[pos], membuf, memsize);
     pos += memsize;
+    memcpy(&data->strs[pos], extra_data->data, extra_data->len);
+    pos += extra_data->len;
 
     MC_LOWER_BARRIER(rec);
 
diff --git a/src/responder/nss/nsssrv_mmap_cache.h b/src/responder/nss/nsssrv_mmap_cache.h
index 90709cd49..ecf47fb11 100644
--- a/src/responder/nss/nsssrv_mmap_cache.h
+++ b/src/responder/nss/nsssrv_mmap_cache.h
@@ -47,13 +47,15 @@ errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc,
                                 uid_t uid, gid_t gid,
                                 struct sized_string *gecos,
                                 struct sized_string *homedir,
-                                struct sized_string *shell);
+                                struct sized_string *shell,
+                                struct sized_data *extra_data);
 
 errno_t sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc,
                                 struct sized_string *name,
                                 struct sized_string *pw,
                                 gid_t gid, size_t memnum,
-                                char *membuf, size_t memsize);
+                                char *membuf, size_t memsize,
+                                struct sized_data *extra_data);
 
 errno_t sss_mmap_cache_initgr_store(struct sss_mc_ctx **_mcc,
                                     struct sized_string *name,
diff --git a/src/sss_client/idmap/sss_nss_idmap.h b/src/sss_client/idmap/sss_nss_idmap.h
index cbf19479f..beb4f46f2 100644
--- a/src/sss_client/idmap/sss_nss_idmap.h
+++ b/src/sss_client/idmap/sss_nss_idmap.h
@@ -159,4 +159,12 @@ int sss_nss_getlistbycert(const char *cert, char ***fq_name,
  * @param[in] kv_list Key-value list returned by sss_nss_getorigbyname().
  */
 void sss_nss_free_kv(struct sss_nss_kv *kv_list);
+
+enum extra_data_item {
+    EX_DATA_SHORT_NAME = 0,
+    EX_DATA_DOMAIN_NAME,
+    EX_DATA_SHORT_DOMAIN_NAME,
+    EX_DATA_SID_STR,
+    EX_DATA_END
+};
 #endif /* SSS_NSS_IDMAP_H_ */
diff --git a/src/sss_client/nss_mc.h b/src/sss_client/nss_mc.h
index 68f451a7b..78098fc64 100644
--- a/src/sss_client/nss_mc.h
+++ b/src/sss_client/nss_mc.h
@@ -76,17 +76,37 @@ errno_t sss_nss_mc_find_rec_by_hash(struct sss_cli_mc_ctx *ctx,
 errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
                             struct passwd *result,
                             char *buffer, size_t buflen);
+errno_t sss_nss_mc_getpwnam_with_extra(const char *name, size_t name_len,
+                                       struct passwd *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len);
 errno_t sss_nss_mc_getpwuid(uid_t uid,
                             struct passwd *result,
                             char *buffer, size_t buflen);
+errno_t sss_nss_mc_getpwuid_with_extra(uid_t uid,
+                                       struct passwd *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len);
 
 /* group db */
 errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
                             struct group *result,
                             char *buffer, size_t buflen);
+errno_t sss_nss_mc_getgrnam_with_extra(const char *name, size_t name_len,
+                                       struct group *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len);
 errno_t sss_nss_mc_getgrgid(gid_t gid,
                             struct group *result,
                             char *buffer, size_t buflen);
+errno_t sss_nss_mc_getgrgid_with_extra(gid_t gid,
+                                       struct group *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len);
 
 /* initgroups db */
 errno_t sss_nss_mc_initgroups_dyn(const char *name, size_t name_len,
diff --git a/src/sss_client/nss_mc_group.c b/src/sss_client/nss_mc_group.c
index ecd9cf376..a23dd1871 100644
--- a/src/sss_client/nss_mc_group.c
+++ b/src/sss_client/nss_mc_group.c
@@ -34,7 +34,9 @@ struct sss_cli_mc_ctx gr_mc_ctx = { UNINITIALIZED, -1, 0, NULL, 0, NULL, 0,
 
 static errno_t sss_nss_mc_parse_result(struct sss_mc_rec *rec,
                                        struct group *result,
-                                       char *buffer, size_t buflen)
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       size_t *_extra_data_len)
 {
     struct sss_mc_grp_data *data;
     time_t expire;
@@ -43,6 +45,8 @@ static errno_t sss_nss_mc_parse_result(struct sss_mc_rec *rec,
     size_t memsize;
     int ret;
     int i;
+    uint32_t extra_data_len;
+    char *p;
 
     /* additional checks before filling result*/
     expire = rec->expire;
@@ -95,16 +99,48 @@ static errno_t sss_nss_mc_parse_result(struct sss_mc_rec *rec,
             return ret;
         }
     }
-    if (cookie != NULL) {
+    if (cookie == NULL) {
+        /* No extra data */
+        return 0;
+    }
+
+    /* Extra data starts after last gr_mem string  or after gr_passwd if there
+     * are not members */
+    if (data->members == 0) {
+        p = result->gr_passwd + strlen(result->gr_passwd) + 1;
+    } else {
+        p = result->gr_mem[data->members - 1]
+                    + strlen(result->gr_mem[data->members - 1]) + 1;
+    }
+    if ( (p - membuf) + sizeof(uint32_t) > data->strs_len) {
+        /* Remaining data to small for data size */
+        return EINVAL;
+    }
+
+    SAFEALIGN_COPY_UINT32(&extra_data_len, p, NULL);
+
+    if ((p - membuf) + extra_data_len != data->strs_len) {
+        /* Inconsistent extra data */
         return EINVAL;
     }
 
+    if (extra_data != NULL) {
+        *extra_data = p;
+    }
+
+    if (_extra_data_len != NULL) {
+        *_extra_data_len = extra_data_len;
+    }
+
+
     return 0;
 }
 
-errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
-                            struct group *result,
-                            char *buffer, size_t buflen)
+errno_t sss_nss_mc_getgrnam_with_extra(const char *name, size_t name_len,
+                                       struct group *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len)
 {
     struct sss_mc_rec *rec = NULL;
     struct sss_mc_rec *new_rec = NULL;
@@ -197,7 +233,8 @@ errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
         goto done;
     }
 
-    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen);
+    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen,
+                                  extra_data, extra_data_len);
 
 done:
     free(rec);
@@ -205,10 +242,20 @@ errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
     return ret;
 }
 
-errno_t sss_nss_mc_getgrgid(gid_t gid,
+errno_t sss_nss_mc_getgrnam(const char *name, size_t name_len,
                             struct group *result,
                             char *buffer, size_t buflen)
 {
+    return sss_nss_mc_getgrnam_with_extra(name, name_len, result,
+                                          buffer, buflen, NULL, 0);
+}
+
+errno_t sss_nss_mc_getgrgid_with_extra(gid_t gid,
+                                       struct group *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len)
+{
     struct sss_mc_rec *rec = NULL;
     struct sss_mc_grp_data *data;
     char gidstr[11];
@@ -265,7 +312,8 @@ errno_t sss_nss_mc_getgrgid(gid_t gid,
         goto done;
     }
 
-    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen);
+    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen,
+                                  extra_data, extra_data_len);
 
 done:
     free(rec);
@@ -273,3 +321,9 @@ errno_t sss_nss_mc_getgrgid(gid_t gid,
     return ret;
 }
 
+errno_t sss_nss_mc_getgrgid(gid_t gid,
+                            struct group *result,
+                            char *buffer, size_t buflen)
+{
+    return sss_nss_mc_getgrgid_with_extra(gid, result, buffer, buflen, NULL, 0);
+}
diff --git a/src/sss_client/nss_mc_passwd.c b/src/sss_client/nss_mc_passwd.c
index 464727e7d..efadceb3c 100644
--- a/src/sss_client/nss_mc_passwd.c
+++ b/src/sss_client/nss_mc_passwd.c
@@ -27,18 +27,23 @@
 #include <sys/mman.h>
 #include <time.h>
 #include "nss_mc.h"
+#include "shared/safealign.h"
 
 struct sss_cli_mc_ctx pw_mc_ctx = { UNINITIALIZED, -1, 0, NULL, 0, NULL, 0,
                                     NULL, 0, 0 };
 
 static errno_t sss_nss_mc_parse_result(struct sss_mc_rec *rec,
                                        struct passwd *result,
-                                       char *buffer, size_t buflen)
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       size_t *_extra_data_len)
 {
     struct sss_mc_pwd_data *data;
     time_t expire;
     void *cookie;
     int ret;
+    uint32_t extra_data_len;
+    char *p;
 
     /* additional checks before filling result*/
     expire = rec->expire;
@@ -88,16 +93,42 @@ static errno_t sss_nss_mc_parse_result(struct sss_mc_rec *rec,
     if (ret) {
         return ret;
     }
-    if (cookie != NULL) {
+
+    if (cookie == NULL) {
+        /* No extra data */
+        return 0;
+    }
+
+    /* Extra data starts after shell string */
+    p = result->pw_shell + strlen(result->pw_shell) + 1;
+    if ( (p - buffer) + sizeof(uint32_t) > data->strs_len) {
+        /* Remaining data to small for data size */
+        return EINVAL;
+    }
+
+    SAFEALIGN_COPY_UINT32(&extra_data_len, p, NULL);
+
+    if ((p - buffer) + extra_data_len != data->strs_len) {
+        /* Inconsistent extra data */
         return EINVAL;
     }
 
+    if (extra_data != NULL) {
+        *extra_data = p;
+    }
+
+    if (_extra_data_len != NULL) {
+        *_extra_data_len = extra_data_len;
+    }
+
     return 0;
 }
 
-errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
-                            struct passwd *result,
-                            char *buffer, size_t buflen)
+errno_t sss_nss_mc_getpwnam_with_extra(const char *name, size_t name_len,
+                                       struct passwd *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len)
 {
     struct sss_mc_rec *rec = NULL;
     struct sss_mc_rec *new_rec = NULL;
@@ -190,7 +221,8 @@ errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
         goto done;
     }
 
-    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen);
+    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen,
+                                  extra_data, extra_data_len);
 
 done:
     free(rec);
@@ -198,10 +230,20 @@ errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
     return ret;
 }
 
-errno_t sss_nss_mc_getpwuid(uid_t uid,
+errno_t sss_nss_mc_getpwnam(const char *name, size_t name_len,
                             struct passwd *result,
                             char *buffer, size_t buflen)
 {
+    return sss_nss_mc_getpwnam_with_extra(name, name_len, result,
+                                          buffer, buflen, NULL, NULL);
+}
+
+errno_t sss_nss_mc_getpwuid_with_extra(uid_t uid,
+                                       struct passwd *result,
+                                       char *buffer, size_t buflen,
+                                       char **extra_data,
+                                       uint32_t *extra_data_len)
+{
     struct sss_mc_rec *rec = NULL;
     struct sss_mc_pwd_data *data;
     char uidstr[11];
@@ -258,7 +300,8 @@ errno_t sss_nss_mc_getpwuid(uid_t uid,
         goto done;
     }
 
-    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen);
+    ret = sss_nss_mc_parse_result(rec, result, buffer, buflen,
+                                  extra_data, extra_data_len);
 
 done:
     free(rec);
@@ -266,3 +309,10 @@ errno_t sss_nss_mc_getpwuid(uid_t uid,
     return ret;
 }
 
+errno_t sss_nss_mc_getpwuid(uid_t uid,
+                            struct passwd *result,
+                            char *buffer, size_t buflen)
+{
+    return sss_nss_mc_getpwuid_with_extra(uid, result, buffer, buflen,
+                                          NULL, NULL);
+}
diff --git a/src/tests/cmocka/test_nss_srv.c b/src/tests/cmocka/test_nss_srv.c
index ccedf96be..fd4ba785b 100644
--- a/src/tests/cmocka/test_nss_srv.c
+++ b/src/tests/cmocka/test_nss_srv.c
@@ -39,6 +39,7 @@
 #define TESTS_PATH "tp_" BASE_FILE_STEM
 #define TEST_CONF_DB "test_nss_conf.ldb"
 #define TEST_DOM_NAME "nss_test"
+#define TEST_DOM_FLAT_NAME "NSSTEST"
 #define TEST_SUBDOM_NAME "test.subdomain"
 #define TEST_ID_PROVIDER "ldap"
 #define TEST_DOM_SID "S-1-5-21-444379608-1639770488-2995963434"
@@ -304,9 +305,16 @@ static void mock_fill_bysid(void)
     will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
 }
 
-static int parse_user_packet(uint8_t *body, size_t blen, struct passwd *pwd)
+struct test_sss_nss_ex_data {
+    const char *items[EX_DATA_END];
+};
+
+static int parse_user_packet(uint8_t *body, size_t blen, struct passwd *pwd,
+                             struct test_sss_nss_ex_data *extra_data)
 {
     size_t rp = 2 * sizeof(uint32_t);
+    uint32_t extra_data_len;
+    uint32_t extra_data_count;
 
     SAFEALIGN_COPY_UINT32(&pwd->pw_uid, body+rp, &rp);
     SAFEALIGN_COPY_UINT32(&pwd->pw_gid, body+rp, &rp);
@@ -330,15 +338,41 @@ static int parse_user_packet(uint8_t *body, size_t blen, struct passwd *pwd)
 
     pwd->pw_shell = (char *) body+rp;
     rp += strlen(pwd->pw_shell) + 1;
+
+    /* End of struct passwd data, checking extra data */
+    SAFEALIGN_COPY_UINT32(&extra_data_len, body+rp, &rp);
+    SAFEALIGN_COPY_UINT32(&extra_data_count, body+rp, &rp);
+    extra_data->items[EX_DATA_SHORT_NAME] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_SHORT_NAME]) + 1;
+
+    extra_data->items[EX_DATA_DOMAIN_NAME] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_DOMAIN_NAME]) + 1;
+
+    extra_data->items[EX_DATA_SHORT_DOMAIN_NAME] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_SHORT_DOMAIN_NAME]) + 1;
+
+    extra_data->items[EX_DATA_SID_STR] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_SID_STR]) + 1;
     if (rp != blen) return EINVAL;
+    if (extra_data_len != strlen(extra_data->items[EX_DATA_SHORT_NAME])
+                +  strlen(extra_data->items[EX_DATA_DOMAIN_NAME])
+                +  strlen(extra_data->items[EX_DATA_SHORT_DOMAIN_NAME])
+                +  strlen(extra_data->items[EX_DATA_SID_STR])
+                +  extra_data_count /* Number of \0s */
+                + 2 * sizeof(uint32_t)) {
+        return EINVAL;
+    }
 
     return EOK;
 }
 
-static int parse_group_packet(uint8_t *body, size_t blen, struct group *gr, uint32_t *nmem)
+static int parse_group_packet(uint8_t *body, size_t blen, struct group *gr, uint32_t *nmem,
+                              struct test_sss_nss_ex_data *extra_data)
 {
     size_t rp = 2 * sizeof(uint32_t); /* Len and reserved */
     unsigned i;
+    uint32_t extra_data_len;
+    uint32_t extra_data_count;
 
     SAFEALIGN_COPY_UINT32(&gr->gr_gid, body+rp, &rp);
     SAFEALIGN_COPY_UINT32(nmem, body+rp, &rp);
@@ -363,7 +397,32 @@ static int parse_group_packet(uint8_t *body, size_t blen, struct group *gr, uint
     }
 
     /* Make sure we exactly matched the end of the packet */
+    //if (rp != blen) return EINVAL;
+
+    /* End of struct passwd data, checking extra data */
+    SAFEALIGN_COPY_UINT32(&extra_data_len, body+rp, &rp);
+    SAFEALIGN_COPY_UINT32(&extra_data_count, body+rp, &rp);
+    extra_data->items[EX_DATA_SHORT_NAME] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_SHORT_NAME]) + 1;
+
+    extra_data->items[EX_DATA_DOMAIN_NAME] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_DOMAIN_NAME]) + 1;
+
+    extra_data->items[EX_DATA_SHORT_DOMAIN_NAME] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_SHORT_DOMAIN_NAME]) + 1;
+
+    extra_data->items[EX_DATA_SID_STR] = (char *) body+rp;
+    rp += strlen(extra_data->items[EX_DATA_SID_STR]) + 1;
     if (rp != blen) return EINVAL;
+    if (extra_data_len != strlen(extra_data->items[EX_DATA_SHORT_NAME])
+                +  strlen(extra_data->items[EX_DATA_DOMAIN_NAME])
+                +  strlen(extra_data->items[EX_DATA_SHORT_DOMAIN_NAME])
+                +  strlen(extra_data->items[EX_DATA_SID_STR])
+                +  extra_data_count /* Number of \0s */
+                + 2 * sizeof(uint32_t)) {
+        return EINVAL;
+    }
+
     return EOK;
 }
 
@@ -488,6 +547,16 @@ static void assert_users_equal(struct passwd *a, struct passwd *b)
     assert_string_equal(a->pw_passwd, b->pw_passwd);
 }
 
+static void assert_extra_data_equal(struct test_sss_nss_ex_data *a,
+                                    struct test_sss_nss_ex_data *b)
+{
+    size_t i;
+
+    for (i = 0; i < EX_DATA_END; i++) {
+        assert_string_equal(a->items[i], b->items[i]);
+    }
+}
+
 static errno_t store_group(struct nss_test_ctx *ctx,
                            struct sss_domain_info *dom,
                            struct group *group,
@@ -629,6 +698,13 @@ struct passwd getpwnam_usr = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_usr = {
+    .items[0] = "testuser",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 /* Check getting cached and valid user from cache. Account callback will
  * not be called and test_nss_getpwnam_check will make sure the user is
  * the same as the test entered before starting
@@ -636,14 +712,16 @@ struct passwd getpwnam_usr = {
 static int test_nss_getpwnam_check(uint32_t status, uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &getpwnam_usr);
+    assert_extra_data_equal(&extra_data, &extra_data_usr);
     return EOK;
 }
 
@@ -721,6 +799,13 @@ struct passwd getpwnam_search_usr = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_search_usr = {
+    .items[0] = "testuser_search",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwnam_search_acct_cb(void *pvt)
 {
     struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
@@ -732,14 +817,16 @@ static int test_nss_getpwnam_search_check(uint32_t status,
                                           uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &getpwnam_search_usr);
+    assert_extra_data_equal(&extra_data, &extra_data_search_usr);
     return EOK;
 }
 
@@ -790,6 +877,13 @@ struct passwd getpwnam_update = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_update_usr = {
+    .items[0] = "testuser_update",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwnam_update_acct_cb(void *pvt)
 {
     struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
@@ -802,14 +896,16 @@ static int test_nss_getpwnam_update_check(uint32_t status,
                                           uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &getpwnam_update);
+    assert_extra_data_equal(&extra_data, &extra_data_update_usr);
     return EOK;
 }
 
@@ -866,21 +962,30 @@ struct passwd getpwnam_fqdn = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_fqdn = {
+    .items[0] = "testuser_fqdn",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwnam_check_fqdn(uint32_t status,
                                         uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
     nss_test_ctx->cctx->rctx->domains[0].fqnames = false;
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     getpwnam_fqdn.pw_name = discard_const("testuser_fqdn@"TEST_DOM_NAME);
     assert_users_equal(&pwd, &getpwnam_fqdn);
+    assert_extra_data_equal(&extra_data, &extra_data_fqdn);
     return EOK;
 }
 
@@ -920,18 +1025,27 @@ struct passwd getpwnam_space = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_space = {
+    .items[0] = "space user",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwnam_check_space(uint32_t status,
                                          uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &getpwnam_space);
+    assert_extra_data_equal(&extra_data, &extra_data_space);
     return EOK;
 }
 
@@ -960,21 +1074,30 @@ void test_nss_getpwnam_space(void **state)
 
 }
 
+struct test_sss_nss_ex_data extra_data_space_sub = {
+    .items[0] = "space_user",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwnam_check_space_sub(uint32_t status,
                                              uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_int_equal(pwd.pw_uid, 225);
     assert_int_equal(pwd.pw_gid, 558);
     assert_string_equal(pwd.pw_name, "space_user");
     assert_string_equal(pwd.pw_shell, "/bin/sh");
+    assert_extra_data_equal(&extra_data, &extra_data_space_sub);
     return EOK;
 }
 
@@ -1038,23 +1161,32 @@ struct passwd getpwnam_fancy_fqdn = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_fancy_fqdn = {
+    .items[0] = "testuser_fqdn_fancy",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwnam_check_fancy_fqdn(uint32_t status,
                                               uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
     nss_test_ctx->cctx->rctx->domains[0].fqnames = false;
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_int_equal(pwd.pw_uid, 125);
     assert_int_equal(pwd.pw_gid, 458);
     assert_string_equal(pwd.pw_name, "testuser_fqdn_fancy@@@@@"TEST_DOM_NAME);
     assert_string_equal(pwd.pw_shell, "/bin/sh");
+    assert_extra_data_equal(&extra_data, &extra_data_fancy_fqdn);
     return EOK;
 }
 
@@ -1097,17 +1229,26 @@ struct passwd getpwuid_usr = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_uid = {
+    .items[0] = "testuser1",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwuid_check(uint32_t status, uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &getpwuid_usr);
+    assert_extra_data_equal(&extra_data, &extra_data_uid);
     return EOK;
 }
 
@@ -1191,6 +1332,13 @@ struct passwd getpwuid_srch = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_srch = {
+    .items[0] = "exampleuser_search",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwuid_search_acct_cb(void *pvt)
 {
     struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
@@ -1202,14 +1350,16 @@ static int test_nss_getpwuid_search_check(uint32_t status,
                                           uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &getpwuid_srch);
+    assert_extra_data_equal(&extra_data, &extra_data_srch);
     return EOK;
 }
 
@@ -1259,6 +1409,13 @@ struct passwd getpwuid_update = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_update_uid = {
+    .items[0] = "exampleuser_update",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwuid_update_acct_cb(void *pvt)
 {
     struct nss_test_ctx *ctx = talloc_get_type(pvt, struct nss_test_ctx);
@@ -1271,14 +1428,16 @@ static int test_nss_getpwuid_update_check(uint32_t status,
                                           uint8_t *body, size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &getpwuid_update);
+    assert_extra_data_equal(&extra_data, &extra_data_update_uid);
     return EOK;
 }
 
@@ -1338,6 +1497,7 @@ void test_nss_setup(struct sss_test_conf_param params[],
     assert_non_null(nss_test_ctx->tctx);
 
     nss_test_ctx->tctx->dom->domain_id = discard_const(TEST_DOM_SID);
+    nss_test_ctx->tctx->dom->flat_name = discard_const(TEST_DOM_FLAT_NAME);
 
     nss_test_ctx->nss_cmds = get_nss_cmds();
     assert_non_null(nss_test_ctx->nss_cmds);
@@ -1384,20 +1544,29 @@ struct group getgrnam_no_members = {
     .gr_mem = NULL,
 };
 
+struct test_sss_nss_ex_data extra_data_no_members = {
+    .items[0] = "testgroup",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getgrnam_no_members_check(uint32_t status,
                                               uint8_t *body, size_t blen)
 {
     int ret;
     uint32_t nmem;
     struct group gr;
+    struct test_sss_nss_ex_data extra_data;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 0);
 
     assert_groups_equal(&getgrnam_no_members, &gr, nmem);
+    assert_extra_data_equal(&extra_data, &extra_data_no_members);
     return EOK;
 }
 
@@ -1455,6 +1624,13 @@ struct group testgroup_members = {
     .gr_mem = NULL,
 };
 
+struct test_sss_nss_ex_data extra_data_testgroup_members = {
+    .items[0] = "testgroup_members",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getgrnam_members_check(uint32_t status,
                                            uint8_t *body, size_t blen)
 {
@@ -1469,14 +1645,16 @@ static int test_nss_getgrnam_members_check(uint32_t status,
         .gr_passwd = testgroup_members.gr_passwd,
         .gr_mem = discard_const(exp_members)
     };
+    struct test_sss_nss_ex_data extra_data;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 2);
 
     assert_groups_equal(&expected, &gr, nmem);
+    assert_extra_data_equal(&extra_data, &extra_data_testgroup_members);
     return EOK;
 }
 
@@ -1543,6 +1721,7 @@ static int test_nss_getgrnam_members_check_fqdn(uint32_t status,
         .gr_mem = discard_const(exp_members)
     };
     TALLOC_CTX *tmp_ctx;
+    struct test_sss_nss_ex_data extra_data;
 
     tmp_ctx = talloc_new(nss_test_ctx);
     assert_non_null(tmp_ctx);
@@ -1562,12 +1741,12 @@ static int test_nss_getgrnam_members_check_fqdn(uint32_t status,
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 2);
 
     assert_groups_equal(&expected, &gr, nmem);
-    assert_int_equal(ret, EOK);
+    assert_extra_data_equal(&extra_data, &extra_data_testgroup_members);
 
     talloc_free(tmp_ctx);
     return EOK;
@@ -1631,6 +1810,13 @@ struct group testsubdomgroup = {
     .gr_mem = NULL,
 };
 
+struct test_sss_nss_ex_data extra_data_testsubdomgroup = {
+    .items[0] = "testsubdomgroup",
+    .items[1] = TEST_SUBDOM_NAME,
+    .items[2] = "test",
+    .items[3] = ""
+};
+
 static int test_nss_getgrnam_members_check_subdom(uint32_t status,
                                                   uint8_t *body, size_t blen)
 {
@@ -1644,6 +1830,7 @@ static int test_nss_getgrnam_members_check_subdom(uint32_t status,
         .gr_mem = discard_const(exp_members)
     };
     TALLOC_CTX *tmp_ctx;
+    struct test_sss_nss_ex_data extra_data;
 
     tmp_ctx = talloc_new(nss_test_ctx);
     assert_non_null(tmp_ctx);
@@ -1668,12 +1855,12 @@ static int test_nss_getgrnam_members_check_subdom(uint32_t status,
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 2);
 
     assert_groups_equal(&expected, &gr, nmem);
-    assert_int_equal(ret, EOK);
+    assert_extra_data_equal(&extra_data, &extra_data_testsubdomgroup);
 
     talloc_free(tmp_ctx);
     return EOK;
@@ -1734,6 +1921,7 @@ static int test_nss_getgrnam_check_mix_dom(uint32_t status,
         .gr_mem = discard_const(exp_members)
     };
     TALLOC_CTX *tmp_ctx;
+    struct test_sss_nss_ex_data extra_data;
 
     tmp_ctx = talloc_new(nss_test_ctx);
     assert_non_null(tmp_ctx);
@@ -1748,12 +1936,12 @@ static int test_nss_getgrnam_check_mix_dom(uint32_t status,
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 3);
 
     assert_groups_equal(&expected, &gr, nmem);
-    assert_int_equal(ret, EOK);
+    assert_extra_data_equal(&extra_data, &extra_data_testgroup_members);
 
     talloc_free(tmp_ctx);
     return EOK;
@@ -1826,6 +2014,7 @@ static int test_nss_getgrnam_check_mix_dom_fqdn(uint32_t status,
         .gr_mem = discard_const(exp_members)
     };
     TALLOC_CTX *tmp_ctx;
+    struct test_sss_nss_ex_data extra_data;
 
     tmp_ctx = talloc_new(nss_test_ctx);
     assert_non_null(tmp_ctx);
@@ -1857,12 +2046,12 @@ static int test_nss_getgrnam_check_mix_dom_fqdn(uint32_t status,
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 3);
 
     assert_groups_equal(&expected, &gr, nmem);
-    assert_int_equal(ret, EOK);
+    assert_extra_data_equal(&extra_data, &extra_data_testgroup_members);
 
     talloc_free(tmp_ctx);
     return EOK;
@@ -1944,6 +2133,7 @@ static int test_nss_getgrnam_check_mix_subdom(uint32_t status,
         .gr_mem = discard_const(exp_members)
     };
     TALLOC_CTX *tmp_ctx;
+    struct test_sss_nss_ex_data extra_data;
 
     tmp_ctx = talloc_new(nss_test_ctx);
     assert_non_null(tmp_ctx);
@@ -1973,12 +2163,12 @@ static int test_nss_getgrnam_check_mix_subdom(uint32_t status,
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 3);
 
     assert_groups_equal(&expected, &gr, nmem);
-    assert_int_equal(ret, EOK);
+    assert_extra_data_equal(&extra_data, &extra_data_testsubdomgroup);
 
     talloc_free(tmp_ctx);
     return EOK;
@@ -2046,21 +2236,29 @@ struct group space_group = {
     .gr_mem = NULL,
 };
 
+struct test_sss_nss_ex_data extra_data_space_group = {
+    .items[0] = "space group",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getgrnam_space_check(uint32_t status,
                                          uint8_t *body, size_t blen)
 {
     int ret;
     uint32_t nmem;
     struct group gr;
+    struct test_sss_nss_ex_data extra_data;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 0);
 
     assert_groups_equal(&space_group, &gr, nmem);
-    assert_int_equal(ret, EOK);
+    assert_extra_data_equal(&extra_data, &extra_data_space_group);
 
     return EOK;
 }
@@ -2092,22 +2290,30 @@ void test_nss_getgrnam_space(void **state)
     assert_int_equal(ret, EOK);
 }
 
+struct test_sss_nss_ex_data extra_data_space_group_replaced = {
+    .items[0] = "space_group",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getgrnam_space_sub_check(uint32_t status,
                                              uint8_t *body, size_t blen)
 {
     int ret;
     uint32_t nmem;
     struct group gr;
+    struct test_sss_nss_ex_data extra_data;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_group_packet(body, blen, &gr, &nmem);
+    ret = parse_group_packet(body, blen, &gr, &nmem, &extra_data);
     assert_int_equal(ret, EOK);
     assert_int_equal(nmem, 0);
 
     space_group.gr_name = discard_const("space_group");
     assert_groups_equal(&space_group, &gr, nmem);
-    assert_int_equal(ret, EOK);
+    assert_extra_data_equal(&extra_data, &extra_data_space_group_replaced);
 
     return EOK;
 }
@@ -2716,19 +2922,28 @@ struct passwd upn_user = {
     .pw_passwd = discard_const("*"),
 };
 
+struct test_sss_nss_ex_data extra_data_upnuser = {
+    .items[0] = "upnuser",
+    .items[1] = TEST_DOM_NAME,
+    .items[2] = TEST_DOM_FLAT_NAME,
+    .items[3] = ""
+};
+
 static int test_nss_getpwnam_upn_check(uint32_t status,
                                        uint8_t *body,
                                        size_t blen)
 {
     struct passwd pwd;
+    struct test_sss_nss_ex_data extra_data;
     errno_t ret;
 
     assert_int_equal(status, EOK);
 
-    ret = parse_user_packet(body, blen, &pwd);
+    ret = parse_user_packet(body, blen, &pwd, &extra_data);
     assert_int_equal(ret, EOK);
 
     assert_users_equal(&pwd, &upn_user);
+    assert_extra_data_equal(&extra_data, &extra_data_upnuser);
     return EOK;
 }
 
diff --git a/src/util/util.h b/src/util/util.h
index 373830117..6e1d118cc 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -521,6 +521,11 @@ struct sized_string {
     size_t len;
 };
 
+struct sized_data {
+    const uint8_t *data;
+    size_t len;
+};
+
 void to_sized_string(struct sized_string *out, const char *in);
 
 /* from domain_info.c */
