>From 4f0bd09258e52ae633bc91603e30edf9ce02b279 Mon Sep 17 00:00:00 2001
From: Pavel Reichl <preichl@redhat.com>
Date: Thu, 30 Apr 2015 06:43:05 -0400
Subject: [PATCH] krb5: new option krb5_map_user

New option `krb5_map_user` providing mapping of ID provider names to
Kerberos principals.

Resolves:
https://fedorahosted.org/sssd/ticket/2509
---
 src/config/SSSDConfig/__init__.py.in     |   1 +
 src/config/SSSDConfigTest.py             |   9 ++-
 src/config/etc/sssd.api.d/sssd-ad.conf   |   1 +
 src/config/etc/sssd.api.d/sssd-ipa.conf  |   1 +
 src/config/etc/sssd.api.d/sssd-krb5.conf |   1 +
 src/man/sssd-krb5.5.xml                  |  32 +++++++++
 src/providers/ad/ad_opts.h               |   1 +
 src/providers/ipa/ipa_opts.h             |   1 +
 src/providers/krb5/krb5_access.c         |   5 +-
 src/providers/krb5/krb5_auth.c           |  55 +++++++++++++--
 src/providers/krb5/krb5_auth.h           |   2 +
 src/providers/krb5/krb5_common.h         |   8 +++
 src/providers/krb5/krb5_init_shared.c    |  11 +++
 src/providers/krb5/krb5_opts.h           |   1 +
 src/providers/krb5/krb5_utils.c          | 117 +++++++++++++++++++++++++++++++
 src/providers/krb5/krb5_utils.h          |   5 ++
 src/tests/krb5_utils-tests.c             | 104 +++++++++++++++++++++++++++
 17 files changed, 344 insertions(+), 11 deletions(-)

diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index 31c9c648045f1e2b031c6f9b2196b44e9c4c4313..f58c52faf7dd3f9199bd0af4286546d4fe804a88 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -215,6 +215,7 @@ option_strings = {
     'krb5_fast_principal' : _("Selects the principal to use for FAST"),
     'krb5_canonicalize' : _("Enables principal canonicalization"),
     'krb5_use_enterprise_principal' : _("Enables enterprise principals"),
+    'krb5_map_user' : _('A mapping from user names to kerberos principal names'),
 
     # [provider/krb5/chpass]
     'krb5_kpasswd' : _('Server where the change password service is running if not on the KDC'),
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
index db16bc433cf4c47c6a15760d85b322a6655aa0c1..476e30806ceda64b14d25f5545a63785efaacf15 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -622,7 +622,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
              'krb5_fast_principal',
              'krb5_canonicalize',
              'krb5_use_enterprise_principal',
-             'krb5_use_kdcinfo'])
+             'krb5_use_kdcinfo',
+             'krb5_map_user'])
 
         options = domain.list_options()
 
@@ -782,7 +783,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
             'krb5_fast_principal',
             'krb5_canonicalize',
             'krb5_use_enterprise_principal',
-            'krb5_use_kdcinfo']
+            'krb5_use_kdcinfo',
+            'krb5_map_user']
 
         self.assertTrue(type(options) == dict,
                         "Options should be a dictionary")
@@ -983,7 +985,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
              'krb5_fast_principal',
              'krb5_canonicalize',
              'krb5_use_enterprise_principal',
-             'krb5_use_kdcinfo'])
+             'krb5_use_kdcinfo',
+             'krb5_map_user'])
 
         options = domain.list_options()
 
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf
index 5a5ea0c36b07d7497c1caa4208c7270d6de6dcc9..c46f3a7cb50db519d113e15f425c7f746d34ad81 100644
--- a/src/config/etc/sssd.api.d/sssd-ad.conf
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
@@ -139,6 +139,7 @@ krb5_renew_interval = str, None, false
 krb5_use_fast = str, None, false
 krb5_fast_principal = str, None, false
 krb5_use_enterprise_principal = bool, None, false
+krb5_map_user = str, None, false
 
 [provider/ad/access]
 
diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf
index 230bdd7df3e7512eab9096c136624cdd7923ed96..6bae609fa9ff57e70c195b858eeea4eca680f62f 100644
--- a/src/config/etc/sssd.api.d/sssd-ipa.conf
+++ b/src/config/etc/sssd.api.d/sssd-ipa.conf
@@ -155,6 +155,7 @@ krb5_renew_interval = str, None, false
 krb5_use_fast = str, None, false
 krb5_fast_principal = str, None, false
 krb5_use_enterprise_principal = bool, None, false
+krb5_map_user = str, None, false
 
 [provider/ipa/access]
 ipa_hbac_refresh = int, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-krb5.conf b/src/config/etc/sssd.api.d/sssd-krb5.conf
index e65ed01b688078aff090ff53b91779595fd6f465..b7423b74f7b6845d235cc523a8e249d6a74d69ab 100644
--- a/src/config/etc/sssd.api.d/sssd-krb5.conf
+++ b/src/config/etc/sssd.api.d/sssd-krb5.conf
@@ -21,6 +21,7 @@ krb5_use_fast = str, None, false
 krb5_fast_principal = str, None, false
 krb5_canonicalize = bool, None, false
 krb5_use_enterprise_principal = bool, None, false
+krb5_map_user = str, None, false
 
 [provider/krb5/access]
 
diff --git a/src/man/sssd-krb5.5.xml b/src/man/sssd-krb5.5.xml
index 8d5bbeed6ce6ec6bcb2db09895ca045905338639..a01d9ef4a7112798169416d4f52ab33cf3b86e71 100644
--- a/src/man/sssd-krb5.5.xml
+++ b/src/man/sssd-krb5.5.xml
@@ -516,6 +516,38 @@
                     </listitem>
                 </varlistentry>
 
+                <varlistentry>
+                    <term>krb5_map_user (string)</term>
+                    <listitem>
+                        <para>
+                             The list of mappings is given as a comma-separated
+                             list of pairs `username:primary` where `username`
+                             is a UNIX user name and `primary` is a user part of
+                             a kerberos principal. This mapping is used when
+                             user is authenticating using
+                             `auth_provider = krb5`.
+                        </para>
+
+                        <para>
+                             example:
+<programlisting>
+krb5_realm = REALM
+krb5_map_user = joe:juser,dick:richard
+</programlisting>
+                        </para>
+                        <para>
+                            `joe` and `vince` are UNIX user names and `juser`
+                            and `rraines` are primaries of kerberos principals.
+                            For user `joe` resp. `dick` SSSD will try to kinit
+                            as `juser@REALM` resp. `richard@REALM`
+                        </para>
+
+                        <para>
+                            Default: not set
+                        </para>
+                    </listitem>
+                </varlistentry>
+
             </variablelist>
         </para>
     </refsect1>
diff --git a/src/providers/ad/ad_opts.h b/src/providers/ad/ad_opts.h
index 15b140434fec815aeee989e24cc1b7930f040add..6e859447f927ef683d53bf08d25d658764581348 100644
--- a/src/providers/ad/ad_opts.h
+++ b/src/providers/ad/ad_opts.h
@@ -168,6 +168,7 @@ struct dp_option ad_def_krb5_opts[] = {
     { "krb5_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
     { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h
index 8a0764265521e86ca86249e4b62f0f967bc44189..34e9e167eb46f290d017b5af817571122b359b4f 100644
--- a/src/providers/ipa/ipa_opts.h
+++ b/src/providers/ipa/ipa_opts.h
@@ -310,6 +310,7 @@ struct dp_option ipa_def_krb5_opts[] = {
     { "krb5_canonicalize", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
     { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/krb5/krb5_access.c b/src/providers/krb5/krb5_access.c
index 7fda2a37922a537f7fe53d629c4e0cb4df1bd4da..6e9ebd4e0c76e96c38078c5b41d2ed55d584c710 100644
--- a/src/providers/krb5/krb5_access.c
+++ b/src/providers/krb5/krb5_access.c
@@ -105,9 +105,8 @@ struct tevent_req *krb5_access_send(TALLOC_CTX *mem_ctx,
         goto done;
         break;
     case 1:
-        ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx,
-                                be_ctx->domain, pd->user, pd->domain,
-                                &state->kr->upn);
+        ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx, be_ctx->domain,
+                                state->kr->user, pd->domain, &state->kr->upn);
         if (ret != EOK) {
             DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n");
             goto done;
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 148b08fdf860e92d00be0582eb73a822113f3880..a488867bf3c92741f99d711db3abd7eb1c4361dd 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -36,6 +36,7 @@
 #include "util/find_uid.h"
 #include "util/auth_utils.h"
 #include "db/sysdb.h"
+#include "util/sss_utf8.h"
 #include "util/child_common.h"
 #include "providers/krb5/krb5_auth.h"
 #include "providers/krb5/krb5_utils.h"
@@ -175,15 +176,44 @@ static int krb5_cleanup(void *ptr)
     return EOK;
 }
 
+static errno_t
+get_krb_primary(struct map_id_name_to_krb_primary *name_to_primary,
+                char *id_prov_name, const char **_krb_primary)
+{
+    errno_t ret;
+    int i = 0;
+
+    while(name_to_primary != NULL &&
+          name_to_primary[i].id_name != NULL &&
+          name_to_primary[i].krb_primary != NULL) {
+        ret = sss_utf8_case_eq((const uint8_t*)name_to_primary[i].id_name,
+                               (const uint8_t*)id_prov_name);
+        if (ret == EOK) {
+            *_krb_primary = name_to_primary[i].krb_primary;
+            goto done;
+        }
+        i++;
+    }
+
+    /* Handle also the case of name_to_primary being NULL */
+    ret = ENOENT;
+
+done:
+    return ret;
+}
+
 errno_t krb5_setup(TALLOC_CTX *mem_ctx, struct pam_data *pd,
                    struct krb5_ctx *krb5_ctx, struct krb5child_req **krb5_req)
 {
     struct krb5child_req *kr = NULL;
+    const char *mapped_name;
+    errno_t ret;
 
     kr = talloc_zero(mem_ctx, struct krb5child_req);
     if (kr == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
-        return ENOMEM;
+        ret = ENOMEM;
+        goto done;
     }
     kr->is_offline = false;
     talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
@@ -191,9 +221,25 @@ errno_t krb5_setup(TALLOC_CTX *mem_ctx, struct pam_data *pd,
     kr->pd = pd;
     kr->krb5_ctx = krb5_ctx;
 
+    ret = get_krb_primary(krb5_ctx->name_to_primary,
+                          pd->user, &mapped_name);
+    if (ret == EOK) {
+        DEBUG(SSSDBG_TRACE_FUNC, "Setting mapped name to: %s\n", mapped_name);
+        kr->user = mapped_name;
+    } else if (ret == ENOENT) {
+        DEBUG(SSSDBG_TRACE_ALL, "No mapping for: %s\n", pd->user);
+        kr->user = pd->user;
+    } else {
+        DEBUG(SSSDBG_CRIT_FAILURE, "get_krb_primary failed - %s:[%d]\n",
+              sss_strerror(ret), ret);
+        goto done;
+    }
+
     *krb5_req = kr;
+    ret = EOK;
 
-    return EOK;
+done:
+    return ret;
 }
 
 
@@ -535,9 +581,8 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
         break;
 
     case 1:
-        ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx,
-                                be_ctx->domain, pd->user, pd->domain,
-                                &kr->upn);
+        ret = find_or_guess_upn(state, res->msgs[0], krb5_ctx, be_ctx->domain,
+                                kr->user, pd->domain, &kr->upn);
         if (ret != EOK) {
             DEBUG(SSSDBG_OP_FAILURE, "find_or_guess_upn failed.\n");
             goto done;
diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h
index c9325291627c40e509dc804714b662942fb1d07a..509e08375688b10d4a0c99311d454ec4a028a571 100644
--- a/src/providers/krb5/krb5_auth.h
+++ b/src/providers/krb5/krb5_auth.h
@@ -55,6 +55,8 @@ struct krb5child_req {
     bool valid_tgt;
     bool upn_from_different_realm;
     bool send_pac;
+
+    const char *user;
 };
 
 errno_t krb5_setup(TALLOC_CTX *mem_ctx, struct pam_data *pd,
diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h
index 81e64688a6d93a4b3ecf41d75d1bf6166b4619ce..8f6fb229ac2ecf83ceec67d3b0817ddd57b93543 100644
--- a/src/providers/krb5/krb5_common.h
+++ b/src/providers/krb5/krb5_common.h
@@ -67,6 +67,7 @@ enum krb5_opts {
     KRB5_CANONICALIZE,
     KRB5_USE_ENTERPRISE_PRINCIPAL,
     KRB5_USE_KDCINFO,
+    KRB5_MAP_USER,
 
     KRB5_OPTS
 };
@@ -89,6 +90,11 @@ enum krb5_config_type {
     K5C_IPA_SERVER
 };
 
+struct map_id_name_to_krb_primary {
+    const char *id_name;
+    const char* krb_primary;
+};
+
 struct krb5_ctx {
     /* opts taken from kinit */
     /* in seconds */
@@ -128,6 +134,8 @@ struct krb5_ctx {
     hash_table_t *wait_queue_hash;
 
     enum krb5_config_type config_type;
+
+    struct map_id_name_to_krb_primary *name_to_primary;
 };
 
 struct remove_info_files_ctx {
diff --git a/src/providers/krb5/krb5_init_shared.c b/src/providers/krb5/krb5_init_shared.c
index 3b4bf096ef49d0f2d369fda20462b7fd56d61127..767291c0b953ea3f227f64a7e21f191262424cf5 100644
--- a/src/providers/krb5/krb5_init_shared.c
+++ b/src/providers/krb5/krb5_init_shared.c
@@ -24,6 +24,7 @@
 
 #include "providers/krb5/krb5_common.h"
 #include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
 #include "providers/krb5/krb5_init_shared.h"
 
 errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx,
@@ -90,6 +91,16 @@ errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx,
         goto done;
     }
 
+    ret = parse_krb5_map_user(krb5_auth_ctx,
+                              dp_opt_get_cstring(krb5_auth_ctx->opts,
+                                                 KRB5_MAP_USER),
+                              &krb5_auth_ctx->name_to_primary);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "parse_krb5_map_user failed: %s:[%d]\n",
+              sss_strerror(ret), ret);
+        goto done;
+    }
+
     ret = EOK;
 
 done:
diff --git a/src/providers/krb5/krb5_opts.h b/src/providers/krb5/krb5_opts.h
index db62cc3b23d8a5eadd3df97a102fb84128b740da..50d701b8ba431719dbf67799230ec345cdee30a9 100644
--- a/src/providers/krb5/krb5_opts.h
+++ b/src/providers/krb5/krb5_opts.h
@@ -45,6 +45,7 @@ struct dp_option default_krb5_opts[] = {
     { "krb5_canonicalize", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "krb5_use_enterprise_principal", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
     { "krb5_use_kdcinfo", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
+    { "krb5_map_user", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/krb5/krb5_utils.c b/src/providers/krb5/krb5_utils.c
index de2d94503744b80b0a3365efb227cd05434579ff..de0edd1505e0c0eec87a3111fcb076931a0d9cb9 100644
--- a/src/providers/krb5/krb5_utils.c
+++ b/src/providers/krb5/krb5_utils.c
@@ -465,3 +465,120 @@ errno_t get_domain_or_subdomain(struct be_ctx *be_ctx,
 
     return EOK;
 }
+
+static errno_t split_tuple(TALLOC_CTX *mem_ctx, const char *tuple,
+                           const char **_first, const char **_second)
+{
+    errno_t ret;
+    char **list;
+    int n;
+    talloc(mem_ctx, int);
+
+    ret = split_on_separator(mem_ctx, tuple, ':', true, true, &list, &n);
+
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "split_on_separator failed - %s:[%d]\n",
+              sss_strerror(ret), ret);
+        goto done;
+    } else if (n != 2) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "split_on_separator failed.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    *_first = list[0];
+    *_second = list[1];
+
+done:
+    return ret;
+}
+
+static errno_t
+fill_name_to_primary_map(TALLOC_CTX *mem_ctx, char **map,
+                       struct map_id_name_to_krb_primary *name_to_primary,
+                       int size)
+{
+    int i;
+    errno_t ret;
+
+    for (i = 0; i < size; i++) {
+        ret = split_tuple(mem_ctx, map[i],
+                          &name_to_primary[i].id_name,
+                          &name_to_primary[i].krb_primary);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "split_tuple failed - %s:[%d]\n", sss_strerror(ret), ret);
+            goto done;
+        }
+    }
+
+    ret = EOK;
+
+done:
+    return ret;
+}
+
+errno_t
+parse_krb5_map_user(TALLOC_CTX *mem_ctx, const char *krb5_map_user,
+                    struct map_id_name_to_krb_primary **_name_to_primary)
+{
+    int size;
+    char **map;
+    errno_t ret;
+    TALLOC_CTX *tmp_ctx;
+    struct map_id_name_to_krb_primary *name_to_primary;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (krb5_map_user == NULL || strlen(krb5_map_user) == 0) {
+        DEBUG(SSSDBG_FUNC_DATA, "Warning: krb5_map_user is empty!\n");
+        size = 0;
+        ret = EOK;
+        goto done;
+    }
+
+    ret = split_on_separator(tmp_ctx, krb5_map_user, ',', true, true,
+                             &map, &size);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to parse krb5_map_user!\n");
+        goto done;
+    }
+
+    name_to_primary = talloc_zero_array(tmp_ctx,
+                                        struct map_id_name_to_krb_primary,
+                                        size + 1);
+    if (name_to_primary == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+    /* sentinel */
+    name_to_primary[size].id_name = NULL;
+    name_to_primary[size].krb_primary = NULL;
+
+    ret = fill_name_to_primary_map(name_to_primary, map, name_to_primary,
+                                   size);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "fill_name_to_primary_map failed: %s:[%d]\n",
+              sss_strerror(ret), ret);
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        if (size == 0) {
+            *_name_to_primary = NULL;
+        } else {
+            *_name_to_primary = talloc_steal(mem_ctx, name_to_primary);
+        }
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h
index 0155905b5bc7469d09aecbd51cae0e8cc61b3952..75b93c30ef5be5d16f2ce73f44abef674c6e98ff 100644
--- a/src/providers/krb5/krb5_utils.h
+++ b/src/providers/krb5/krb5_utils.h
@@ -49,4 +49,9 @@ char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
 errno_t get_domain_or_subdomain(struct be_ctx *be_ctx,
                                 char *domain_name,
                                 struct sss_domain_info **dom);
+
+errno_t
+parse_krb5_map_user(TALLOC_CTX *mem_ctx, const char *krb5_map_user,
+                    struct map_id_name_to_krb_primary **_name_to_primary);
+
 #endif /* __KRB5_UTILS_H__ */
diff --git a/src/tests/krb5_utils-tests.c b/src/tests/krb5_utils-tests.c
index 409c0f01d2cce9c24a648306007b9fa7f5bc8372..dd319a317d46c5e207badf2c995d6f1fef20348c 100644
--- a/src/tests/krb5_utils-tests.c
+++ b/src/tests/krb5_utils-tests.c
@@ -29,6 +29,7 @@
 #include "providers/krb5/krb5_utils.h"
 #include "providers/krb5/krb5_ccache.h"
 #include "providers/krb5/krb5_auth.h"
+#include "util/sss_utf8.h"
 #include "tests/common.h"
 
 #define TESTS_PATH "tests_krb5_utils"
@@ -574,6 +575,108 @@ START_TEST(test_compare_principal_realm)
 }
 END_TEST
 
+static void
+compare_map_id_name_to_krb_primary(struct map_id_name_to_krb_primary *a,
+                                   const char **str,
+                                   size_t len)
+{
+    int i = 0;
+    errno_t ret;
+
+    while (a[i].id_name != NULL && a[i].krb_primary != NULL) {
+        fail_unless(i < len);
+        ret = sss_utf8_case_eq((const uint8_t*)a[i].id_name,
+                               (const uint8_t*)str[i*2]);
+        fail_unless(ret == EOK,
+                    "%s does not match %s", a[i].id_name, str[i*2]);
+
+        ret = sss_utf8_case_eq((const uint8_t*)a[i].krb_primary,
+                               (const uint8_t*)str[i*2+1]);
+        fail_unless(ret == EOK, "%s does not match %s",
+                    a[i].krb_primary, str[i*2+1]);
+        i++;
+    }
+    fail_unless(len == i, "%u != %u", len, i);
+
+}
+
+START_TEST(test_parse_krb5_map_user)
+{
+    errno_t ret;
+    TALLOC_CTX *mem_ctx;
+    struct map_id_name_to_krb_primary *name_to_primary;
+
+    mem_ctx = talloc_new(NULL);
+
+    /* empty input */
+    {
+        check_leaks_push(mem_ctx);
+        ret = parse_krb5_map_user(mem_ctx, NULL, &name_to_primary);
+        fail_unless(ret == EOK);
+        fail_unless(name_to_primary == NULL);
+
+        ret = parse_krb5_map_user(mem_ctx, "", &name_to_primary);
+        fail_unless(ret == EOK);
+        fail_unless(name_to_primary == NULL);
+
+        ret = parse_krb5_map_user(mem_ctx, ",", &name_to_primary);
+        fail_unless(ret == EOK);
+        fail_unless(name_to_primary == NULL);
+
+        ret = parse_krb5_map_user(mem_ctx, ",,", &name_to_primary);
+        fail_unless(ret == EOK);
+        fail_unless(name_to_primary == NULL);
+
+        check_leaks_pop(mem_ctx);
+    }
+    /* valid input */
+    {
+        check_leaks_push(mem_ctx);
+        const char *p = "pája:preichl,joe:juser,jdoe:ßlack";
+        const char *p2 = " pája  : preichl , joe:\njuser,jdoe\t:   ßlack ";
+        const char *expected[] = {"pája", "preichl", "joe", "juser", "jdoe", "ßlack"};
+        ret = parse_krb5_map_user(mem_ctx, p, &name_to_primary);
+        fail_unless(ret == EOK);
+        compare_map_id_name_to_krb_primary(name_to_primary, expected,
+                                         sizeof(expected)/sizeof(const char*)/2);
+        talloc_free(name_to_primary);
+
+        ret = parse_krb5_map_user(mem_ctx, p2, &name_to_primary);
+        fail_unless(ret == EOK);
+        compare_map_id_name_to_krb_primary(name_to_primary,  expected,
+                                         sizeof(expected)/sizeof(const char*)/2);
+        talloc_free(name_to_primary);
+        check_leaks_pop(mem_ctx);
+    }
+    /* invalid input */
+    {
+        check_leaks_push(mem_ctx);
+
+        ret = parse_krb5_map_user(mem_ctx, ":", &name_to_primary);
+        fail_unless(ret == EINVAL);
+
+        ret = parse_krb5_map_user(mem_ctx, "joe:", &name_to_primary);
+        fail_unless(ret == EINVAL);
+
+        ret = parse_krb5_map_user(mem_ctx, ":joe", &name_to_primary);
+        fail_unless(ret == EINVAL);
+
+        ret = parse_krb5_map_user(mem_ctx, "joe:,", &name_to_primary);
+        fail_unless(ret == EINVAL);
+
+        ret = parse_krb5_map_user(mem_ctx, ",joe", &name_to_primary);
+        fail_unless(ret == EINVAL);
+
+        ret = parse_krb5_map_user(mem_ctx, "joe:j:user", &name_to_primary);
+        fail_unless(ret == EINVAL);
+
+        check_leaks_pop(mem_ctx);
+    }
+
+    talloc_free(mem_ctx);
+}
+END_TEST
+
 Suite *krb5_utils_suite (void)
 {
     Suite *s = suite_create ("krb5_utils");
@@ -612,6 +715,7 @@ Suite *krb5_utils_suite (void)
 
     TCase *tc_krb5_helpers = tcase_create("Helper functions");
     tcase_add_test(tc_krb5_helpers, test_compare_principal_realm);
+    tcase_add_test(tc_krb5_helpers, test_parse_krb5_map_user);
     suite_add_tcase(s, tc_krb5_helpers);
 
     return s;
-- 
2.1.0

