>From 40329ebff77b0d05c8051d3d4a2af2f6545c8d86 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 31 Jul 2013 10:59:43 +0200
Subject: [PATCH 2/3] LDAP: Make it possible to extend an attribute map

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

This commit adds a new option ldap_user_extra_attrs that is unset by
default. When set, the option contains a list of LDAP attributes the LDAP
provider would download and store in addition to the usual set.

The attributes are fetched in sysdb with the 'extra_' prefix to not
conflict with the sssd sdap maps.
---
 src/config/SSSDConfig/__init__.py.in          |  1 +
 src/config/etc/sssd.api.d/sssd-ldap.conf      |  1 +
 src/db/sysdb.h                                |  3 ++
 src/man/sssd-ldap.5.xml                       | 14 ++++++
 src/providers/ad/ad_common.c                  |  9 ++++
 src/providers/ad/ad_opts.h                    |  1 +
 src/providers/ipa/ipa_common.c                |  9 ++++
 src/providers/ipa/ipa_netgroups.c             |  3 +-
 src/providers/ipa/ipa_opts.h                  |  1 +
 src/providers/ldap/ldap_id.c                  |  3 +-
 src/providers/ldap/ldap_options.c             | 45 +++++++++++++++++++
 src/providers/ldap/ldap_opts.h                |  1 +
 src/providers/ldap/sdap.c                     | 55 +++++++++++++++++++++++
 src/providers/ldap/sdap.h                     |  9 ++++
 src/providers/ldap/sdap_async_enum.c          |  3 +-
 src/providers/ldap/sdap_async_groups.c        |  6 +--
 src/providers/ldap/sdap_async_groups_ad.c     |  4 +-
 src/providers/ldap/sdap_async_initgroups.c    |  6 ++-
 src/providers/ldap/sdap_async_nested_groups.c |  5 ++-
 src/providers/ldap/sdap_async_users.c         |  6 +--
 src/tests/ipa_ldap_opt-tests.c                | 63 +++++++++++++++++++++++++++
 21 files changed, 233 insertions(+), 15 deletions(-)

diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index 6382dd570dcdacd4f47adae92540643c1d369f9b..64807d8d19d6c16e6afefaebf3c2fcc2e424f6df 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -275,6 +275,7 @@ option_strings = {
     'ldap_user_nds_login_expiration_time' : _('loginExpirationTime attribute of NDS'),
     'ldap_user_nds_login_allowed_time_map' : _('loginAllowedTimeMap attribute of NDS'),
     'ldap_user_ssh_public_key' : _('SSH public key attribute'),
+    'ldap_user_extra_attrs' : _('A list of extra attributes to download along with the user entry'),
 
     'ldap_group_search_base' : _('Base DN for group lookups'),
     # not used # 'ldap_group_search_scope' : _('Scope of group lookups'),
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
index 257e859df24f6c0cbd7b2dd72402790e11bf06c5..0057c080f84d4b5f75107903b03a3cc91f03cc83 100644
--- a/src/config/etc/sssd.api.d/sssd-ldap.conf
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -48,6 +48,7 @@ ldap_id_mapping = bool, None, false
 ldap_user_search_base = str, None, false
 ldap_user_search_scope = str, None, false
 ldap_user_search_filter = str, None, false
+ldap_user_extra_attrs = str, None, false
 ldap_user_object_class = str, None, false
 ldap_user_name = str, None, false
 ldap_user_uid_number = str, None, false
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 5d337ffd8c0f7fbe6808d50538192369102caf17..6ea0bef447713cfc85e04db854128dc46b5a70ce 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -208,6 +208,9 @@
 #define SYSDB_MOD_DEL LDB_FLAG_MOD_DELETE
 #define SYSDB_MOD_REP LDB_FLAG_MOD_REPLACE
 
+#define SYSDB_EXTRA_ATTR_PREFIX "extra_"
+#define SYSDB_EXTRA_ATTR_TMPL   SYSDB_EXTRA_ATTR_PREFIX"%s"
+
 /* sysdb version check macros */
 #define SYSDB_VERSION_ERROR_HINT \
     ERROR("Removing cache files in "DB_PATH" should fix the issue, " \
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index f93b418c45d9bcd32499860a858c3f829bb245ca..126a2ba3983ebce85287baa6606f15b31cf75891 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -616,6 +616,20 @@
                     </listitem>
                 </varlistentry>
 
+                <varlistentry>
+                    <term>ldap_user_extra_attrs (string)</term>
+                    <listitem>
+                        <para>
+                            Comma-separated list of attributes that SSSD
+                            would fetch along with the usual set of user
+                            attributes.
+                        </para>
+                        <para>
+                            Default: not set
+                        </para>
+                    </listitem>
+                </varlistentry>
+
                 <varlistentry condition="with_ssh">
                     <term>ldap_user_ssh_public_key (string)</term>
                     <listitem>
diff --git a/src/providers/ad/ad_common.c b/src/providers/ad/ad_common.c
index 57a0a3c39c55fec127948c19238f12d3e8827913..8528ad3dcdab58ff17c200b8f6f226cd50e8cfed 100644
--- a/src/providers/ad/ad_common.c
+++ b/src/providers/ad/ad_common.c
@@ -201,6 +201,15 @@ ad_create_sdap_options(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
+    ret = sdap_extend_map(id_opts,
+                          id_opts->user_map,
+                          SDAP_OPTS_USER, NULL,
+                          &id_opts->user_map,
+                          &id_opts->user_map_cnt);
+    if (ret != EOK) {
+        goto done;
+    }
+
     /* Group map */
     ret = sdap_get_map(id_opts,
                        cdb, conf_path,
diff --git a/src/providers/ad/ad_opts.h b/src/providers/ad/ad_opts.h
index 6b4e874edc8f9cac3c141cb22d4d78ee8320b75d..69a4dfa8e4d0bd4c45858b6af095b1ed512a7ec1 100644
--- a/src/providers/ad/ad_opts.h
+++ b/src/providers/ad/ad_opts.h
@@ -54,6 +54,7 @@ struct dp_option ad_def_ldap_opts[] = {
     { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index f84748267d65bedbadf39db3466d28502bfa0e3e..19de10d8bb92a4b3d0c8353eae128f3b4709a7af 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -547,6 +547,15 @@ int ipa_get_id_options(struct ipa_options *ipa_opts,
         goto done;
     }
 
+    ret = sdap_extend_map(ipa_opts->id,
+                          ipa_opts->id->user_map,
+                          SDAP_OPTS_USER, NULL,
+                          &ipa_opts->id->user_map,
+                          &ipa_opts->id->user_map_cnt);
+    if (ret != EOK) {
+        goto done;
+    }
+
     ret = sdap_get_map(ipa_opts->id,
                        cdb, conf_path,
                        ipa_group_map,
diff --git a/src/providers/ipa/ipa_netgroups.c b/src/providers/ipa/ipa_netgroups.c
index 9cc374bc17eb53accaf607736a19555e17ebf4e1..db29d29ee8f18d3d963402c4811bdef43bae63dc 100644
--- a/src/providers/ipa/ipa_netgroups.c
+++ b/src/providers/ipa/ipa_netgroups.c
@@ -492,7 +492,8 @@ static int ipa_netgr_fetch_users(struct ipa_get_netgroups_state *state,
                                                      SDAP_USER_SEARCH_BASE),
                                    LDAP_SCOPE_SUBTREE,
                                    filter, attrs, state->opts->user_map,
-                                   SDAP_OPTS_USER, state->timeout, true);
+                                   state->opts->user_map_cnt,
+                                   state->timeout, true);
 
     state->current_entity = ENTITY_USER;
     if (subreq == NULL) {
diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h
index 909c431eefa778f97767af241829ac81256fc464..949a054ddbfb84fc523d5478aa3f038a1adcddba 100644
--- a/src/providers/ipa/ipa_opts.h
+++ b/src/providers/ipa/ipa_opts.h
@@ -77,6 +77,7 @@ struct dp_option ipa_def_ldap_opts[] = {
     { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
index ab0a5c911e8a2b4d488b4121fbfc51b52d9ef773..297d6016ee0a64478a57036cd8efc04629a7e701 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -215,7 +215,8 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
     }
 
     /* TODO: handle attrs_type */
-    ret = build_attrs_from_map(state, ctx->opts->user_map, SDAP_OPTS_USER,
+    ret = build_attrs_from_map(state, ctx->opts->user_map,
+                               ctx->opts->user_map_cnt,
                                NULL, &state->attrs, NULL);
     if (ret != EOK) goto fail;
 
diff --git a/src/providers/ldap/ldap_options.c b/src/providers/ldap/ldap_options.c
index 8813572ffad65ff126d50c27e81648d63518504c..11ed9c31f0fc03113e5f7ba58b5ce8b74fe6545a 100644
--- a/src/providers/ldap/ldap_options.c
+++ b/src/providers/ldap/ldap_options.c
@@ -23,6 +23,44 @@
 #include "providers/ldap/sdap_async_private.h"
 #include "util/crypto/sss_crypto.h"
 
+static int sdap_extend_map_with_list(TALLOC_CTX *mem_ctx,
+                                     struct sdap_options *opts,
+                                     int extra_attr_index,
+                                     struct sdap_attr_map *src_map,
+                                     size_t num_entries,
+                                     struct sdap_attr_map **_map,
+                                     size_t *_new_size)
+{
+    const char *extra_attrs;
+    char **extra_attrs_list;
+    errno_t ret;
+
+    extra_attrs = dp_opt_get_string(opts->basic, extra_attr_index);
+    if (extra_attrs == NULL) {
+        *_map = src_map;
+        *_new_size = num_entries;
+        return EOK;
+    }
+
+    /* split server parm into a list */
+    ret = split_on_separator(mem_ctx, extra_attrs, ',', true, true,
+                             &extra_attrs_list, NULL);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to parse server list!\n");
+        return ret;
+    }
+
+    ret = sdap_extend_map(mem_ctx, src_map,
+                          num_entries, extra_attrs_list,
+                          _map, _new_size);
+    talloc_free(extra_attrs_list);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    return EOK;
+}
+
 int ldap_get_options(TALLOC_CTX *memctx,
                      struct sss_domain_info *dom,
                      struct confdb_ctx *cdb,
@@ -257,6 +295,13 @@ int ldap_get_options(TALLOC_CTX *memctx,
         goto done;
     }
 
+    ret = sdap_extend_map_with_list(opts, opts, SDAP_USER_EXTRA_ATTRS,
+                                    opts->user_map, SDAP_OPTS_USER,
+                                    &opts->user_map, &opts->user_map_cnt);
+    if (ret != EOK) {
+        goto done;
+    }
+
     ret = sdap_get_map(opts, cdb, conf_path,
                        default_group_map,
                        SDAP_OPTS_GROUP,
diff --git a/src/providers/ldap/ldap_opts.h b/src/providers/ldap/ldap_opts.h
index d45ad46f078d06b22d16aa6312e449dcdef4d21c..90899b94800420fdbcd2c5c0213e2790343e1dc4 100644
--- a/src/providers/ldap/ldap_opts.h
+++ b/src/providers/ldap/ldap_opts.h
@@ -43,6 +43,7 @@ struct dp_option default_basic_opts[] = {
     { "ldap_user_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_user_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_user_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_user_extra_attrs", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
index 568d2e73a85e84691b76cad8dceeb2fe0f69491e..ea28e64cf585ca2f9ea21fe2249d1f6b299926e1 100644
--- a/src/providers/ldap/sdap.c
+++ b/src/providers/ldap/sdap.c
@@ -74,6 +74,61 @@ int sdap_copy_map(TALLOC_CTX *memctx,
     return EOK;
 }
 
+int sdap_extend_map(TALLOC_CTX *memctx,
+                    struct sdap_attr_map *src_map,
+                    size_t num_entries,
+                    char **extra_attrs,
+                    struct sdap_attr_map **_map,
+                    size_t *_new_size)
+{
+    struct sdap_attr_map *map;
+    size_t nextra = 0;
+    size_t i;
+
+    if (extra_attrs == NULL) {
+        DEBUG(SSSDBG_FUNC_DATA, "No extra attributes\n");
+        *_map = src_map;
+        *_new_size = num_entries;
+        return EOK;
+    }
+
+    for (nextra = 0; extra_attrs[nextra]; nextra++) ;
+    DEBUG(SSSDBG_FUNC_DATA, "%zu extra attributes\n", nextra);
+
+    map = talloc_realloc(memctx, src_map, struct sdap_attr_map,
+                         num_entries + nextra + 1);
+    if (map == NULL) {
+        return ENOMEM;
+    }
+
+    for (i = 0; extra_attrs[i]; i++) {
+        map[num_entries+i].opt_name = talloc_strdup(map, extra_attrs[i]);
+        map[num_entries+i].sys_name = talloc_asprintf(map,
+                                                SYSDB_EXTRA_ATTR_TMPL,
+                                                map[num_entries+i].opt_name);
+        map[num_entries+i].name = talloc_strdup(map,
+                                                map[num_entries+i].opt_name);
+        map[num_entries+i].def_name = talloc_strdup(map,
+                                                map[num_entries+i].opt_name);
+        if (map[num_entries+i].opt_name == NULL ||
+            map[num_entries+i].sys_name == NULL ||
+            map[num_entries+i].name == NULL ||
+            map[num_entries+i].def_name == NULL) {
+            return ENOMEM;
+        }
+        DEBUG(SSSDBG_TRACE_FUNC, "Extending map with %s\n", extra_attrs[i]);
+    }
+
+    /* Sentinel */
+    map[num_entries+nextra].opt_name = NULL;
+    map[num_entries+nextra].sys_name = NULL;
+    map[num_entries+nextra].name = NULL;
+
+    *_map = map;
+    *_new_size = num_entries + nextra;
+    return EOK;
+}
+
 int sdap_get_map(TALLOC_CTX *memctx,
                  struct confdb_ctx *cdb,
                  const char *conf_path,
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index ad239a8d07a445013e5bf86a514acb505fc40a13..38eec1c4dcd10ea839457bbef442e5993a7bf762 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -159,6 +159,7 @@ enum sdap_basic_opt {
     SDAP_USER_SEARCH_BASE,
     SDAP_USER_SEARCH_SCOPE,
     SDAP_USER_SEARCH_FILTER,
+    SDAP_USER_EXTRA_ATTRS,
     SDAP_GROUP_SEARCH_BASE,
     SDAP_GROUP_SEARCH_SCOPE,
     SDAP_GROUP_SEARCH_FILTER,
@@ -412,6 +413,7 @@ struct sdap_options {
     struct dp_option *basic;
     struct sdap_attr_map *gen_map;
     struct sdap_attr_map *user_map;
+    size_t user_map_cnt;
     struct sdap_attr_map *group_map;
     struct sdap_attr_map *netgroup_map;
     struct sdap_attr_map *service_map;
@@ -467,6 +469,13 @@ int sdap_copy_map(TALLOC_CTX *memctx,
                  int num_entries,
                  struct sdap_attr_map **_map);
 
+int sdap_extend_map(TALLOC_CTX *memctx,
+                    struct sdap_attr_map *src_map,
+                    size_t num_entries,
+                    char **extra_attrs,
+                    struct sdap_attr_map **_map,
+                    size_t *_new_size);
+
 int sdap_get_map(TALLOC_CTX *memctx,
                  struct confdb_ctx *cdb,
                  const char *conf_path,
diff --git a/src/providers/ldap/sdap_async_enum.c b/src/providers/ldap/sdap_async_enum.c
index ebd9ffafbedff48d2da5773322a08163a7e0177b..1c69c07f814e3f791dcd4f98edfaa9457ab8ae47 100644
--- a/src/providers/ldap/sdap_async_enum.c
+++ b/src/providers/ldap/sdap_async_enum.c
@@ -617,7 +617,8 @@ static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
     }
 
     /* TODO: handle attrs_type */
-    ret = build_attrs_from_map(state, ctx->opts->user_map, SDAP_OPTS_USER,
+    ret = build_attrs_from_map(state, ctx->opts->user_map,
+                               ctx->opts->user_map_cnt,
                                NULL, &state->attrs, NULL);
     if (ret != EOK) goto fail;
 
diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c
index 4240bb585d57a80199d040ac44891ee1b6429e05..89a5afb91e8fe666f834720c6b97e74e266822a6 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -1038,7 +1038,7 @@ struct tevent_req *sdap_process_group_send(TALLOC_CTX *memctx,
                             struct sdap_process_group_state);
     if (!req) return NULL;
 
-    ret = build_attrs_from_map(grp_state, opts->user_map, SDAP_OPTS_USER,
+    ret = build_attrs_from_map(grp_state, opts->user_map, opts->user_map_cnt,
                                NULL, &attrs, NULL);
     if (ret) {
         goto done;
@@ -1188,7 +1188,7 @@ sdap_process_missing_member_2307bis(struct tevent_req *req,
                                        grp_state->filter,
                                        grp_state->attrs,
                                        grp_state->opts->user_map,
-                                       SDAP_OPTS_USER,
+                                       grp_state->opts->user_map_cnt,
                                        dp_opt_get_int(grp_state->opts->basic,
                                                       SDAP_SEARCH_TIMEOUT),
                                        false);
@@ -1495,7 +1495,7 @@ next:
                                        state->filter,
                                        state->attrs,
                                        state->opts->user_map,
-                                       SDAP_OPTS_USER,
+                                       state->opts->user_map_cnt,
                                        dp_opt_get_int(state->opts->basic,
                                                       SDAP_SEARCH_TIMEOUT),
                                        false);
diff --git a/src/providers/ldap/sdap_async_groups_ad.c b/src/providers/ldap/sdap_async_groups_ad.c
index 9bb21d29bdde4bb9402d8b49f69b184969c94550..8db587c96d569fc691486b252ff8f2c7d96e29c2 100644
--- a/src/providers/ldap/sdap_async_groups_ad.c
+++ b/src/providers/ldap/sdap_async_groups_ad.c
@@ -72,7 +72,7 @@ sdap_get_ad_match_rule_members_send(TALLOC_CTX *mem_ctx,
     state->search_bases = opts->sdom->user_search_bases;
 
     /* Request all of the user attributes that we know about. */
-    ret = build_attrs_from_map(state, opts->user_map, SDAP_OPTS_USER,
+    ret = build_attrs_from_map(state, opts->user_map, opts->user_map_cnt,
                                NULL, &state->attrs, NULL);
     if (ret != EOK) {
         DEBUG(SSSDBG_MINOR_FAILURE,
@@ -157,7 +157,7 @@ sdap_get_ad_match_rule_members_next_base(struct tevent_req *req)
             state->search_bases[state->base_iter]->basedn,
             state->search_bases[state->base_iter]->scope,
             state->filter, state->attrs,
-            state->opts->user_map, SDAP_OPTS_USER,
+            state->opts->user_map, state->opts->user_map_cnt,
             state->timeout, true);
     if (!subreq) {
         return ENOMEM;
diff --git a/src/providers/ldap/sdap_async_initgroups.c b/src/providers/ldap/sdap_async_initgroups.c
index 5334ef84d095b531dc16ccd853a913e27327f5f6..401ab82dcd38ba05c437618bcc1cb4a92fae9698 100644
--- a/src/providers/ldap/sdap_async_initgroups.c
+++ b/src/providers/ldap/sdap_async_initgroups.c
@@ -2704,7 +2704,9 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
         return NULL;
     }
 
-    ret = build_attrs_from_map(state, state->opts->user_map, SDAP_OPTS_USER,
+    ret = build_attrs_from_map(state,
+                               state->opts->user_map,
+                               state->opts->user_map_cnt,
                                NULL, &state->user_attrs, NULL);
     if (ret) {
         talloc_zfree(req);
@@ -2752,7 +2754,7 @@ static errno_t sdap_get_initgr_next_base(struct tevent_req *req)
             state->user_search_bases[state->user_base_iter]->basedn,
             state->user_search_bases[state->user_base_iter]->scope,
             state->filter, state->user_attrs,
-            state->opts->user_map, SDAP_OPTS_USER,
+            state->opts->user_map, state->opts->user_map_cnt,
             state->timeout,
             false);
     if (!subreq) {
diff --git a/src/providers/ldap/sdap_async_nested_groups.c b/src/providers/ldap/sdap_async_nested_groups.c
index 51e88bbfb2d9bc8a7a77737baafb2b3b8b886e95..4ef3d79cbd4b9d0ee48362591106fb9413a1fcaa 100644
--- a/src/providers/ldap/sdap_async_nested_groups.c
+++ b/src/providers/ldap/sdap_async_nested_groups.c
@@ -1587,7 +1587,8 @@ sdap_nested_group_lookup_user_send(TALLOC_CTX *mem_ctx,
     /* search */
     subreq = sdap_get_generic_send(state, ev, group_ctx->opts, group_ctx->sh,
                                    member->dn, LDAP_SCOPE_BASE, filter, attrs,
-                                   group_ctx->opts->user_map, SDAP_OPTS_USER,
+                                   group_ctx->opts->user_map,
+                                   group_ctx->opts->user_map_cnt,
                                    dp_opt_get_int(group_ctx->opts->basic,
                                                   SDAP_SEARCH_TIMEOUT),
                                    false);
@@ -2029,7 +2030,7 @@ sdap_nested_group_deref_send(TALLOC_CTX *mem_ctx,
     }
 
     maps[0].map = opts->user_map;
-    maps[0].num_attrs = SDAP_OPTS_USER;
+    maps[0].num_attrs = opts->user_map_cnt;
     maps[1].map = opts->group_map;
     maps[1].num_attrs = SDAP_OPTS_GROUP;
 
diff --git a/src/providers/ldap/sdap_async_users.c b/src/providers/ldap/sdap_async_users.c
index ef5478f9424ba7c2f26c1ceff4c9cc9c82c3f54c..be0536ef3c3ffaf398c4f2d3e85094d4deb7bd81 100644
--- a/src/providers/ldap/sdap_async_users.c
+++ b/src/providers/ldap/sdap_async_users.c
@@ -447,7 +447,7 @@ int sdap_save_user(TALLOC_CTX *memctx,
         }
     }
 
-    for (i = SDAP_FIRST_EXTRA_USER_AT; i < SDAP_OPTS_USER; i++) {
+    for (i = SDAP_FIRST_EXTRA_USER_AT; i < opts->user_map_cnt; i++) {
         ret = sdap_attrs_add_list(attrs, opts->user_map[i].sys_name,
                                   NULL, user_name, user_attrs);
         if (ret) {
@@ -475,7 +475,7 @@ int sdap_save_user(TALLOC_CTX *memctx,
     /* Make sure that any attributes we requested from LDAP that we
      * did not receive are also removed from the sysdb
      */
-    ret = list_missing_attrs(user_attrs, opts->user_map, SDAP_OPTS_USER,
+    ret = list_missing_attrs(user_attrs, opts->user_map, opts->user_map_cnt,
                              attrs, &missing);
     if (ret != EOK) {
         goto done;
@@ -694,7 +694,7 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
             state->search_bases[state->base_iter]->basedn,
             state->search_bases[state->base_iter]->scope,
             state->filter, state->attrs,
-            state->opts->user_map, SDAP_OPTS_USER,
+            state->opts->user_map, state->opts->user_map_cnt,
             state->timeout,
             state->enumeration); /* If we're enumerating, we need paging */
     if (subreq == NULL) {
diff --git a/src/tests/ipa_ldap_opt-tests.c b/src/tests/ipa_ldap_opt-tests.c
index bbb49935dec47983c4d04ef73385a4ed8a1ea372..76eb0cee7ebf879a858f0b08ca007a2fab91da03 100644
--- a/src/tests/ipa_ldap_opt-tests.c
+++ b/src/tests/ipa_ldap_opt-tests.c
@@ -277,6 +277,64 @@ START_TEST(test_copy_sdap_map)
 }
 END_TEST
 
+START_TEST(test_extra_opts)
+{
+    errno_t ret;
+    char *extra_attrs[] =  { discard_const("foo"),
+                             discard_const("bar"),
+                             NULL };
+    struct sdap_attr_map *in_map;
+    struct sdap_attr_map *out_map;
+    size_t new_size;
+
+    ret = sdap_copy_map(global_talloc_context, rfc2307_user_map,
+                        SDAP_OPTS_USER, &in_map);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    ret = sdap_extend_map(global_talloc_context,
+                          in_map,
+                          SDAP_OPTS_USER,
+                          extra_attrs,
+                          &out_map, &new_size);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    /* Two extra and sentinel */
+    fail_unless(new_size != SDAP_OPTS_USER + 3);
+    ck_assert_str_eq(out_map[SDAP_OPTS_USER].name, "foo");
+    ck_assert_str_eq(out_map[SDAP_OPTS_USER].sys_name, "extra_foo");
+    ck_assert_str_eq(out_map[SDAP_OPTS_USER+1].name, "bar");
+    ck_assert_str_eq(out_map[SDAP_OPTS_USER+1].sys_name, "extra_bar");
+    fail_unless(out_map[SDAP_OPTS_USER+2].name == NULL);
+
+    talloc_free(out_map);
+}
+END_TEST
+
+START_TEST(test_no_extra_opts)
+{
+    errno_t ret;
+    struct sdap_attr_map *in_map;
+    struct sdap_attr_map *out_map;
+    size_t new_size;
+
+    ret = sdap_copy_map(global_talloc_context, rfc2307_user_map,
+                        SDAP_OPTS_USER, &in_map);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+
+    ret = sdap_extend_map(global_talloc_context,
+                          in_map,
+                          SDAP_OPTS_USER,
+                          NULL,
+                          &out_map, &new_size);
+    fail_unless(ret == EOK, "[%s]", strerror(ret));
+    /* Attributes and sentinel */
+    fail_unless(new_size != SDAP_OPTS_USER + 1);
+    fail_unless(out_map[SDAP_OPTS_USER].name == NULL);
+
+    talloc_free(out_map);
+}
+END_TEST
+
 Suite *ipa_ldap_opt_suite (void)
 {
     Suite *s = suite_create ("ipa_ldap_opt");
@@ -300,6 +358,11 @@ Suite *ipa_ldap_opt_suite (void)
     tcase_add_test (tc_sdap_opts, test_copy_sdap_map);
     suite_add_tcase (s, tc_sdap_opts);
 
+    TCase *tc_extra_opts = tcase_create ("extra_opts");
+    tcase_add_test (tc_extra_opts, test_extra_opts);
+    tcase_add_test (tc_extra_opts, test_no_extra_opts);
+    suite_add_tcase (s, tc_extra_opts);
+
     return s;
 }
 
-- 
1.9.0

