[SSSD] [PATCHES] PAM, NSS: allow UPN login names

Sumit Bose sbose at redhat.com
Tue Aug 26 08:50:32 UTC 2014


On Thu, Jul 24, 2014 at 03:42:38PM +0200, Pavel Březina wrote:
> On 07/22/2014 09:47 PM, Sumit Bose wrote:
> >On Tue, Jul 22, 2014 at 05:55:21PM +0200, Pavel Březina wrote:
> >>On 07/22/2014 01:27 PM, Sumit Bose wrote:
> >>>Hi,
> >>>
> >>>this series of patches should solve
> >>>https://fedorahosted.org/sssd/ticket/1749 . The solution is a bit
> >>>different than the one outline in
> >>>https://fedorahosted.org/sssd/wiki/DesignDocs/NSSWithKerberosPrincipal
> >>>but after a couple of iterations I prefer this solution because it adds
> >>>only a minimal amount of new code and automatically covers features like
> >>>mid-point refresh, because the same code path is used. If we agree on
> >>>this approach I'll update the design page accordingly.
> >>>
> >>>The outline is in the commit message of the 5th patch, I'll copy it here
> >>>for easier reference:
> >>>
> >>>With this patch the NSS and PAM responders can handle user principal
> >>>names besides the fully qualified user names.
> >>>
> >>>User principal names are build from a user name and a domain suffix
> >>>separated by an '@' sign. But the domain suffix does not necessarily has
> >>>to be the same as the configured domain name in sssd.conf of the
> >>>dynamically discovered DNS domain name of a domain. The typical use case
> >>>is an Active Directory forest with lots of different domains. To not
> >>>force the users to remember the name of the individual domain they
> >>>belong to the AD administrator can set a common domain suffix for all
> >>>users from all domains in the forest. This is typically the domain name
> >>>used for emails to make it even more easy to the users to remember it.
> >>>
> >>>Since SSSD splits name and domain part at the '@' sign and the common
> >>>domain suffix might not be resolvable by DNS or the given user is not a
> >>>member of that domain (e.g. in the case where the forest root is used as
> >>>common domain suffix) SSSD might fail to look up the user.
> >>>
> >>>With this patch the NSS and PAM responder will do an extra lookup for a
> >>>UPN if the domain part of the given name is not known or the user was
> >>>not found and the login name contained the '@' sign.
> >>>
> >>>The first patch contains the needs changes for the LDAP provider,
> >>>patches 2, 3 and 4 some related cleanup and improvements. The main
> >>>functionality is in the 5th patch.
> >>>
> >>>bye,
> >>>Sumit
> >>
> >>Hi,
> >>I went through the code and IMHO the approach you've chosen is good.
> >
> >thank you for the fast response.
> >
> >>
> >>Can you make "U" value a macro?
> >
> >sure, new version attached. (Patch 1 and 5 changed)
> >
> >bye,
> >Sumit
> 
> Hi,
> you left unused variable "res" in pam_check_user_search() introduced in
> patch 3 and its usage was removed in patch 5. I'm sending a simple patch
> that fix this, feel free to squash it into patch 5.

thank you for the review and the patch, done.

> 
> The code looks good and id and su commands with upn works, however it seems
> that group membership is not calculated correctly (only primary group is
> shown).
> 
> [client: ~]$ id test-user at ad.pb
> uid=1751601108(test-user) gid=1751600513(domain users)
> groups=1751600513(domain users),1751601106(sudo users)
> [client: ~]$ id test-user at upnad.pb
> uid=1751601108(test-user) gid=1751600513(domain users)
> groups=1751600513(domain users)
> 
> If I run id after su, the membership is correct:
> [client: ~]$ su test-user at upnad.pb
> Password:
> [client: /home/pbrezina]$ id
> uid=1751601108(test-user) gid=1751600513(domain users)
> groups=1751600513(domain users),1751601106(sudo users)
> context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
> 

Thank you for catching this. My testing focus was that the UPN can be
used at the login prompt. But since the UPN might be used in group list
related calls as well I added support in the initgroups code path as
well.

New version of the patches is attached.

bye,
Sumit
-------------- next part --------------
From 6cf165bbb46af9aeac68960a806e258dd6b78ca5 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Mon, 25 Aug 2014 11:42:14 +0200
Subject: [PATCH 1/6] sysdb_get_real_name: allow UPN as input

---
 src/db/sysdb.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/db/sysdb.c b/src/db/sysdb.c
index 626ec0d..3bc27c8 100644
--- a/src/db/sysdb.c
+++ b/src/db/sysdb.c
@@ -1891,6 +1891,7 @@ errno_t sysdb_get_real_name(TALLOC_CTX *mem_ctx,
     TALLOC_CTX *tmp_ctx;
     struct ldb_result *res;
     const char *cname;
+    struct ldb_message *msg;
 
     tmp_ctx = talloc_new(NULL);
     if (!tmp_ctx) {
@@ -1904,17 +1905,22 @@ errno_t sysdb_get_real_name(TALLOC_CTX *mem_ctx,
     }
 
     if (res->count == 0) {
-        /* User is not cached yet */
-        ret = ENOENT;
-        goto done;
-    } else if (res->count != 1) {
+        ret = sysdb_search_user_by_upn(tmp_ctx, domain, name, NULL, &msg);
+        if (ret != EOK) {
+            /* User cannot be found in cache */
+            DEBUG(SSSDBG_OP_FAILURE, "Cannot find user [%s] in cache\n", name);
+            goto done;
+        }
+    } else if (res->count == 1) {
+        msg = res->msgs[0];
+    } else {
         DEBUG(SSSDBG_CRIT_FAILURE,
               "sysdb_getpwnam returned count: [%d]\n", res->count);
         ret = EIO;
         goto done;
     }
 
-    cname = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL);
+    cname = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
     if (!cname) {
         DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n");
         ret = ENOENT;
-- 
1.8.3.1

-------------- next part --------------
From 3a72316d62573c8da2a26933cc8a46f297b8bf50 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Mon, 7 Jul 2014 13:40:07 +0200
Subject: [PATCH 2/6] LDAP: If extra_value is 'U' do a UPN search

Besides the name the responders always send an extra string attribute to
the backends which is so far mostly empty. Since the only difference in
the processing of a request for a user name or a user principal name is
a different search attribute in the LDAP provider this extra value can
be used to indicate the type of the name. Providers which do not support
UPN lookup can just ignore this attribute.

Related to https://fedorahosted.org/sssd/ticket/1749
---
 src/providers/data_provider.h              |  2 ++
 src/providers/ldap/ldap_id.c               | 15 +++++++++++++--
 src/providers/ldap/sdap_async.h            |  1 +
 src/providers/ldap/sdap_async_initgroups.c | 11 +++++++++--
 4 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
index ebb4fad..9af1362 100644
--- a/src/providers/data_provider.h
+++ b/src/providers/data_provider.h
@@ -147,6 +147,8 @@
  * length */
 #define DP_SEC_ID_LEN (sizeof(DP_SEC_ID) - 1)
 
+#define EXTRA_NAME_IS_UPN "U"
+
 /* AUTH related common data and functions */
 
 #define DEBUG_PAM_DATA(level, pd) do { \
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
index c788b6b..eb349f3 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -70,6 +70,7 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
                                   struct sdap_id_conn_ctx *conn,
                                   const char *name,
                                   int filter_type,
+                                  const char *extra_value,
                                   int attrs_type,
                                   bool noexist_delete)
 {
@@ -111,7 +112,11 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
                                                           sdom->dom->domain_id);
     switch (filter_type) {
     case BE_FILTER_NAME:
-        attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name;
+        if (extra_value && strcmp(extra_value, EXTRA_NAME_IS_UPN) == 0) {
+            attr_name = ctx->opts->user_map[SDAP_AT_USER_PRINC].name;
+        } else {
+            attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name;
+        }
         ret = sss_filter_sanitize(state, name, &clean_name);
         if (ret != EOK) {
             goto done;
@@ -918,6 +923,7 @@ struct groups_by_user_state {
     struct sss_domain_info *domain;
 
     const char *name;
+    const char *extra_value;
     const char **attrs;
 
     int dp_error;
@@ -935,6 +941,7 @@ static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx,
                                               struct sdap_domain *sdom,
                                               struct sdap_id_conn_ctx *conn,
                                               const char *name,
+                                              const char *extra_value,
                                               bool noexist_delete)
 {
     struct tevent_req *req;
@@ -959,6 +966,7 @@ static struct tevent_req *groups_by_user_send(TALLOC_CTX *memctx,
     }
 
     state->name = name;
+    state->extra_value = extra_value;
     state->domain = sdom->dom;
     state->sysdb = sdom->dom->sysdb;
 
@@ -1020,6 +1028,7 @@ static void groups_by_user_connect_done(struct tevent_req *subreq)
                                   state->ctx,
                                   state->conn,
                                   state->name,
+                                  state->extra_value,
                                   state->attrs);
     if (!subreq) {
         tevent_req_error(req, ENOMEM);
@@ -1320,6 +1329,7 @@ sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx,
                                 sdom, conn,
                                 ar->filter_value,
                                 ar->filter_type,
+                                ar->extra_value,
                                 ar->attr_type,
                                 noexist_delete);
         break;
@@ -1358,6 +1368,7 @@ sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx,
         subreq = groups_by_user_send(breq, be_ctx->ev, id_ctx,
                                      sdom, conn,
                                      ar->filter_value,
+                                     ar->extra_value,
                                      noexist_delete);
         break;
 
@@ -1701,7 +1712,7 @@ static void get_user_and_group_groups_done(struct tevent_req *subreq)
      * Retry with users. */
     subreq = users_get_send(req, state->ev, state->id_ctx,
                             state->sdom, state->conn,
-                            state->filter_val, state->filter_type,
+                            state->filter_val, state->filter_type, NULL,
                             state->attrs_type, state->noexist_delete);
     if (subreq == NULL) {
         DEBUG(SSSDBG_OP_FAILURE, "groups_get_send failed.\n");
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index 808254a..7bb69f2 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -134,6 +134,7 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
                                         struct sdap_id_ctx *id_ctx,
                                         struct sdap_id_conn_ctx *conn,
                                         const char *name,
+                                        const char *extra_value,
                                         const char **grp_attrs);
 int sdap_get_initgr_recv(struct tevent_req *req);
 
diff --git a/src/providers/ldap/sdap_async_initgroups.c b/src/providers/ldap/sdap_async_initgroups.c
index 2eecdf9..6c8b4e0 100644
--- a/src/providers/ldap/sdap_async_initgroups.c
+++ b/src/providers/ldap/sdap_async_initgroups.c
@@ -2616,6 +2616,7 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
                                         struct sdap_id_ctx *id_ctx,
                                         struct sdap_id_conn_ctx *conn,
                                         const char *name,
+                                        const char *extra_value,
                                         const char **grp_attrs)
 {
     struct tevent_req *req;
@@ -2623,6 +2624,7 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
     int ret;
     char *clean_name;
     bool use_id_mapping;
+    const char *search_attr;
 
     DEBUG(SSSDBG_TRACE_ALL, "Retrieving info for initgroups call\n");
 
@@ -2661,10 +2663,15 @@ struct tevent_req *sdap_get_initgr_send(TALLOC_CTX *memctx,
         return NULL;
     }
 
+    if (extra_value && strcmp(extra_value, EXTRA_NAME_IS_UPN) == 0) {
+        search_attr =  state->opts->user_map[SDAP_AT_USER_PRINC].name;
+    } else {
+        search_attr =  state->opts->user_map[SDAP_AT_USER_NAME].name;
+    }
+
     state->user_base_filter =
             talloc_asprintf(state, "(&(%s=%s)(objectclass=%s)",
-                            state->opts->user_map[SDAP_AT_USER_NAME].name,
-                            clean_name,
+                            search_attr, clean_name,
                             state->opts->user_map[SDAP_OC_USER].name);
     if (!state->user_base_filter) {
         talloc_zfree(req);
-- 
1.8.3.1

-------------- next part --------------
From 550d3d90646ff195910c404e5c4b4db0792606c4 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Jun 2014 18:29:20 +0200
Subject: [PATCH 3/6] PAM: extract checks from parsing routines

This patch saves the original name given at a login prompt and send to
the PAM responder in the logon_name member of the pam_data struct for
later use.

Additionally it separates the parsing of the data send by the PAM client
and the checks of this data.
---
 src/providers/data_provider.h    |  1 +
 src/providers/dp_pam_data_util.c |  1 +
 src/responder/pam/pamsrv_cmd.c   | 52 ++++++++++++++--------------------------
 3 files changed, 20 insertions(+), 34 deletions(-)

diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
index 9af1362..742fcca 100644
--- a/src/providers/data_provider.h
+++ b/src/providers/data_provider.h
@@ -175,6 +175,7 @@ struct pam_data {
     struct sss_auth_token *authtok;
     struct sss_auth_token *newauthtok;
     uint32_t cli_pid;
+    char *logon_name;
 
     int pam_status;
     int response_delay;
diff --git a/src/providers/dp_pam_data_util.c b/src/providers/dp_pam_data_util.c
index 705169d..313948b 100644
--- a/src/providers/dp_pam_data_util.c
+++ b/src/providers/dp_pam_data_util.c
@@ -192,6 +192,7 @@ void pam_print_data(int l, struct pam_data *pd)
     DEBUG(l, "newauthtok type: %d\n", sss_authtok_get_type(pd->newauthtok));
     DEBUG(l, "priv: %d\n", pd->priv);
     DEBUG(l, "cli_pid: %d\n", pd->cli_pid);
+    DEBUG(l, "logon name: %s\n", PAM_SAFE_ITEM(pd->logon_name));
 }
 
 int pam_add_response(struct pam_data *pd, enum response_type type,
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index d58ef64..46a44e0 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -134,15 +134,12 @@ static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd
     return EOK;
 }
 
-static int pam_parse_in_data_v2(struct sss_domain_info *domains,
-                                const char *default_domain,
-                                struct pam_data *pd,
+static int pam_parse_in_data_v2(struct pam_data *pd,
                                 uint8_t *body, size_t blen)
 {
     size_t c;
     uint32_t type;
     uint32_t size;
-    char *pam_user;
     int ret;
     uint32_t start;
     uint32_t terminator;
@@ -178,12 +175,7 @@ static int pam_parse_in_data_v2(struct sss_domain_info *domains,
 
             switch(type) {
                 case SSS_PAM_ITEM_USER:
-                    ret = extract_string(&pam_user, size, body, blen, &c);
-                    if (ret != EOK) return ret;
-
-                    ret = sss_parse_name_for_domains(pd, domains,
-                                                     default_domain, pam_user,
-                                                     &pd->domain, &pd->user);
+                    ret = extract_string(&pd->logon_name, size, body, blen, &c);
                     if (ret != EOK) return ret;
                     break;
                 case SSS_PAM_ITEM_SERVICE:
@@ -226,22 +218,16 @@ static int pam_parse_in_data_v2(struct sss_domain_info *domains,
 
     } while(c < blen);
 
-    if (pd->user == NULL || *pd->user == '\0') return EINVAL;
-
-    DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd);
-
     return EOK;
 
 }
 
-static int pam_parse_in_data_v3(struct sss_domain_info *domains,
-                                const char *default_domain,
-                                struct pam_data *pd,
+static int pam_parse_in_data_v3(struct pam_data *pd,
                                 uint8_t *body, size_t blen)
 {
     int ret;
 
-    ret = pam_parse_in_data_v2(domains, default_domain, pd, body, blen);
+    ret = pam_parse_in_data_v2(pd, body, blen);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n");
         return ret;
@@ -284,9 +270,7 @@ static int extract_authtok_v1(struct sss_auth_token *tok,
     return ret;
 }
 
-static int pam_parse_in_data(struct sss_domain_info *domains,
-                             const char *default_domain,
-                             struct pam_data *pd,
+static int pam_parse_in_data(struct pam_data *pd,
                              uint8_t *body, size_t blen)
 {
     size_t start;
@@ -300,10 +284,7 @@ static int pam_parse_in_data(struct sss_domain_info *domains,
     /* user name */
     for (start = end; end < last; end++) if (body[end] == '\0') break;
     if (body[end++] != '\0') return EINVAL;
-
-    ret = sss_parse_name_for_domains(pd, domains, default_domain,
-                                     (char *)&body[start], &pd->domain, &pd->user);
-    if (ret != EOK) return ret;
+    pd->logon_name = (char *) &body[start];
 
     for (start = end; end < last; end++) if (body[end] == '\0') break;
     if (body[end++] != '\0') return EINVAL;
@@ -743,25 +724,28 @@ errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd)
 
     switch (cctx->cli_protocol_version->version) {
         case 1:
-            ret = pam_parse_in_data(cctx->rctx->domains,
-                                    cctx->rctx->default_domain, pd,
-                                    body, blen);
+            ret = pam_parse_in_data(pd, body, blen);
             break;
         case 2:
-            ret = pam_parse_in_data_v2(cctx->rctx->domains,
-                                       cctx->rctx->default_domain, pd,
-                                       body, blen);
+            ret = pam_parse_in_data_v2(pd, body, blen);
             break;
         case 3:
-            ret = pam_parse_in_data_v3(cctx->rctx->domains,
-                                       cctx->rctx->default_domain, pd,
-                                       body, blen);
+            ret = pam_parse_in_data_v3(pd, body, blen);
             break;
         default:
             DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n",
                       cctx->cli_protocol_version->version);
             ret = EINVAL;
     }
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = sss_parse_name_for_domains(pd, cctx->rctx->domains,
+                                     cctx->rctx->default_domain, pd->logon_name,
+                                     &pd->domain, &pd->user);
+
+    DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd);
 
 done:
     return ret;
-- 
1.8.3.1

-------------- next part --------------
From 9257fdd283ec0c6128e604178f9b664ab01a8de3 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 8 Jul 2014 11:43:04 +0200
Subject: [PATCH 4/6] PAM: remove ldb_result member from pam_auth_req context

This member was used only in a single call where a local variable suits
better.
---
 src/responder/pam/pamsrv.h     |  1 -
 src/responder/pam/pamsrv_cmd.c | 11 ++++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 129419b..1e37a77 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -51,7 +51,6 @@ struct pam_auth_req {
 
     pam_dp_callback_t *callback;
 
-    struct ldb_result *res;
     bool check_provider;
     void *data;
 
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 46a44e0..eae1734 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -916,6 +916,7 @@ static int pam_check_user_search(struct pam_auth_req *preq)
     struct dp_callback_ctx *cb_ctx;
     struct pam_ctx *pctx =
             talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+    struct ldb_result *res;
 
     while (dom) {
        /* if it is a domainless search, skip domains that require fully
@@ -978,20 +979,20 @@ static int pam_check_user_search(struct pam_auth_req *preq)
             return EFAULT;
         }
 
-        ret = sysdb_getpwnam(preq, dom, name, &preq->res);
+        ret = sysdb_getpwnam(preq, dom, name, &res);
         if (ret != EOK) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Failed to make request to our cache!\n");
             return EIO;
         }
 
-        if (preq->res->count > 1) {
+        if (res->count > 1) {
             DEBUG(SSSDBG_FATAL_FAILURE,
                   "getpwnam call returned more than one result !?!\n");
             return ENOENT;
         }
 
-        if (preq->res->count == 0) {
+        if (res->count == 0) {
             if (preq->check_provider == false) {
                 /* set negative cache only if not result of cache check */
                 ret = sss_ncache_set_user(pctx->ncache, false, dom, name);
@@ -1020,7 +1021,7 @@ static int pam_check_user_search(struct pam_auth_req *preq)
 
         /* if we need to check the remote account go on */
         if (preq->check_provider) {
-            cacheExpire = ldb_msg_find_attr_as_uint64(preq->res->msgs[0],
+            cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0],
                                                       SYSDB_CACHE_EXPIRE, 0);
             if (cacheExpire < time(NULL)) {
                 break;
@@ -1031,7 +1032,7 @@ static int pam_check_user_search(struct pam_auth_req *preq)
               "Returning info for user [%s@%s]\n", name, dom->name);
 
         /* We might have searched by alias. Pass on the primary name */
-        ret = pd_set_primary_name(preq->res->msgs[0], preq->pd);
+        ret = pd_set_primary_name(res->msgs[0], preq->pd);
         if (ret != EOK) {
             DEBUG(SSSDBG_CRIT_FAILURE, "Could not canonicalize username\n");
             return ret;
-- 
1.8.3.1

-------------- next part --------------
From 03a4c5076dc0507b60fbe9a98b50682f94472831 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Mon, 7 Jul 2014 11:57:03 +0200
Subject: [PATCH 5/6] NSS: check_cache() add extra option

This patch adds a new parameter to check_cache() to allow to set the
extra value which is send to the backend during lookup requests.
---
 src/responder/nss/nsssrv_cmd.c      | 19 ++++++++++---------
 src/responder/nss/nsssrv_netgroup.c |  2 +-
 src/responder/nss/nsssrv_private.h  |  1 +
 3 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 9113998..04ff2d6 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -524,6 +524,7 @@ errno_t check_cache(struct nss_dom_ctx *dctx,
                     int req_type,
                     const char *opt_name,
                     uint32_t opt_id,
+                    const char *extra,
                     sss_dp_callback_t callback,
                     void *pvt)
 {
@@ -582,7 +583,7 @@ errno_t check_cache(struct nss_dom_ctx *dctx,
              "Performing midpoint cache update on [%s]\n", opt_name);
 
         req = sss_dp_get_account_send(cctx, cctx->rctx, dctx->domain, true,
-                                      req_type, opt_name, opt_id, NULL);
+                                      req_type, opt_name, opt_id, extra);
         if (!req) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Out of memory sending out-of-band data provider "
@@ -611,7 +612,7 @@ errno_t check_cache(struct nss_dom_ctx *dctx,
         }
 
         req = sss_dp_get_account_send(cctx, cctx->rctx, dctx->domain, true,
-                                      req_type, opt_name, opt_id, NULL);
+                                      req_type, opt_name, opt_id, extra);
         if (!req) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Out of memory sending data provider request\n");
@@ -834,7 +835,7 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx)
          * yet) then verify that the cache is uptodate */
         if (dctx->check_provider) {
             ret = check_cache(dctx, nctx, dctx->res,
-                              SSS_DP_USER, name, 0,
+                              SSS_DP_USER, name, 0, NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -1409,7 +1410,7 @@ static int nss_cmd_getpwuid_search(struct nss_dom_ctx *dctx)
          * yet) then verify that the cache is uptodate */
         if (dctx->check_provider) {
             ret = check_cache(dctx, nctx, dctx->res,
-                              SSS_DP_USER, NULL, cmdctx->id,
+                              SSS_DP_USER, NULL, cmdctx->id, NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -2810,7 +2811,7 @@ static int nss_cmd_getgrnam_search(struct nss_dom_ctx *dctx)
          * yet) then verify that the cache is uptodate */
         if (dctx->check_provider) {
             ret = check_cache(dctx, nctx, dctx->res,
-                              SSS_DP_GROUP, name, 0,
+                              SSS_DP_GROUP, name, 0, NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -2925,7 +2926,7 @@ static int nss_cmd_getgrgid_search(struct nss_dom_ctx *dctx)
          * yet) then verify that the cache is uptodate */
         if (dctx->check_provider) {
             ret = check_cache(dctx, nctx, dctx->res,
-                              SSS_DP_GROUP, NULL, cmdctx->id,
+                              SSS_DP_GROUP, NULL, cmdctx->id, NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -3827,7 +3828,7 @@ static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx)
          * yet) then verify that the cache is uptodate */
         if (dctx->check_provider) {
             ret = check_cache(dctx, nctx, dctx->res,
-                              SSS_DP_INITGROUPS, name, 0,
+                              SSS_DP_INITGROUPS, name, 0, NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -4101,7 +4102,7 @@ static errno_t nss_cmd_getsidby_search(struct nss_dom_ctx *dctx)
             }
 
             ret = check_cache(dctx, nctx, dctx->res,
-                              req_type, req_name, req_id,
+                              req_type, req_name, req_id, NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -4204,7 +4205,7 @@ static errno_t nss_cmd_getbysid_search(struct nss_dom_ctx *dctx)
      * yet) then verify that the cache is uptodate */
     if (dctx->check_provider) {
         ret = check_cache(dctx, nctx, dctx->res,
-                          SSS_DP_SECID, cmdctx->secid, 0,
+                          SSS_DP_SECID, cmdctx->secid, 0, NULL,
                           nss_cmd_getby_dp_callback,
                           dctx);
         if (ret != EOK) {
diff --git a/src/responder/nss/nsssrv_netgroup.c b/src/responder/nss/nsssrv_netgroup.c
index 5f879e2..66b7f27 100644
--- a/src/responder/nss/nsssrv_netgroup.c
+++ b/src/responder/nss/nsssrv_netgroup.c
@@ -581,7 +581,7 @@ static errno_t lookup_netgr_step(struct setent_step_ctx *step_ctx)
                               step_ctx->nctx,
                               step_ctx->dctx->res,
                               SSS_DP_NETGR,
-                              name, 0,
+                              name, 0, NULL,
                               lookup_netgr_dp_callback,
                               step_ctx);
             if (ret != EOK) {
diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h
index 2dcc07b..23bb6a8 100644
--- a/src/responder/nss/nsssrv_private.h
+++ b/src/responder/nss/nsssrv_private.h
@@ -122,6 +122,7 @@ errno_t check_cache(struct nss_dom_ctx *dctx,
                     int req_type,
                     const char *opt_name,
                     uint32_t opt_id,
+                    const char *extra,
                     sss_dp_callback_t callback,
                     void *pvt);
 
-- 
1.8.3.1

-------------- next part --------------
From d40701f1bb1f9dd591f987b8fd63b28c6ac099b8 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Jun 2014 18:30:01 +0200
Subject: [PATCH 6/6] PAM, NSS: allow UPN login names

With this patch the NSS and PAM responders can handle user principal
names besides the fully qualified user names.

User principal names are build from a user name and a domain suffix
separated by an '@' sign. But the domain suffix does not necessarily has
to be the same as the configured domain name in sssd.conf of the
dynamically discovered DNS domain name of a domain. The typical use case
is an Active Directory forest with lots of different domains. To not
force the users to remember the name of the individual domain they
belong to the AD administrator can set a common domain suffix for all
users from all domains in the forest. This is typically the domain name
used for emails to make it even more easy to the users to remember it.

Since SSSD splits name and domain part at the '@' sign and the common
domain suffix might not be resolvable by DNS or the given user is not a
member of that domain (e.g. in the case where the forest root is used as
common domain suffix) SSSD might fail to look up the user.

With this patch the NSS and PAM responder will do an extra lookup for a
UPN if the domain part of the given name is not known or the user was
not found and the login name contained the '@' sign.

Resolves https://fedorahosted.org/sssd/ticket/1749
---
 src/providers/data_provider.h      |   1 +
 src/responder/nss/nsssrv_cmd.c     | 142 +++++++++++++++++++++++++++++++++++--
 src/responder/nss/nsssrv_private.h |   1 +
 src/responder/pam/pamsrv_cmd.c     |  47 +++++++-----
 4 files changed, 168 insertions(+), 23 deletions(-)

diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
index 742fcca..b127985 100644
--- a/src/providers/data_provider.h
+++ b/src/providers/data_provider.h
@@ -176,6 +176,7 @@ struct pam_data {
     struct sss_auth_token *newauthtok;
     uint32_t cli_pid;
     char *logon_name;
+    bool name_is_upn;
 
     int pam_status;
     int response_delay;
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 04ff2d6..bf578f3 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -27,6 +27,7 @@
 #include "responder/nss/nsssrv_services.h"
 #include "responder/nss/nsssrv_mmap_cache.h"
 #include "responder/common/negcache.h"
+#include "providers/data_provider.h"
 #include "confdb/confdb.h"
 #include "db/sysdb.h"
 #include "sss_client/idmap/sss_nss_idmap.h"
@@ -728,13 +729,16 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx)
     char *name = NULL;
     struct nss_ctx *nctx;
     int ret;
+    static const char *user_attrs[] = SYSDB_PW_ATTRS;
+    struct ldb_message *msg;
 
     nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
 
     while (dom) {
        /* if it is a domainless search, skip domains that require fully
          * qualified names instead */
-        while (dom && cmdctx->check_next && dom->fqnames) {
+        while (dom && cmdctx->check_next && dom->fqnames
+                && !cmdctx->name_is_upn) {
             dom = get_next_domain(dom, false);
         }
 
@@ -791,7 +795,37 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx)
             return EIO;
         }
 
-        ret = sysdb_getpwnam(cmdctx, dom, name, &dctx->res);
+        if (cmdctx->name_is_upn) {
+            ret = sysdb_search_user_by_upn(cmdctx, dom, name, user_attrs, &msg);
+            if (ret != EOK && ret != ENOENT) {
+                DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_upn failed.\n");
+                return ret;
+            }
+
+            dctx->res = talloc_zero(cmdctx, struct ldb_result);
+            if (dctx->res == NULL) {
+                DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n");
+                return ENOMEM;
+            }
+
+            if (ret == ENOENT) {
+                dctx->res->count = 0;
+                dctx->res->msgs = NULL;
+                ret = EOK;
+            } else {
+                dctx->res->count = 1;
+                dctx->res->msgs = talloc_array(dctx->res,
+                                               struct ldb_message *, 1);
+                if (dctx->res->msgs == NULL) {
+                    DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n");
+                    return ENOMEM;
+                }
+
+                dctx->res->msgs[0] = talloc_steal(dctx->res->msgs, msg);
+            }
+        } else {
+            ret = sysdb_getpwnam(cmdctx, dom, name, &dctx->res);
+        }
         if (ret != EOK) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Failed to make request to our cache!\n");
@@ -835,7 +869,8 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx)
          * yet) then verify that the cache is uptodate */
         if (dctx->check_provider) {
             ret = check_cache(dctx, nctx, dctx->res,
-                              SSS_DP_USER, name, 0, NULL,
+                              SSS_DP_USER, name, 0,
+                              cmdctx->name_is_upn ? EXTRA_NAME_IS_UPN : NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
@@ -868,6 +903,44 @@ static errno_t nss_cmd_getbysid_search(struct nss_dom_ctx *dctx);
 static errno_t nss_cmd_getbysid_send_reply(struct nss_dom_ctx *dctx);
 static errno_t nss_cmd_getsidby_search(struct nss_dom_ctx *dctx);
 
+static int nss_cmd_assume_upn(struct nss_dom_ctx *dctx)
+{
+    int ret;
+
+    if (dctx->domain == NULL) {
+        dctx->domain = dctx->cmdctx->cctx->rctx->domains;
+        dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+        dctx->cmdctx->check_next = true;
+        dctx->cmdctx->name = talloc_strdup(dctx->cmdctx, dctx->rawname);
+        dctx->cmdctx->name_is_upn = true;
+        if (dctx->cmdctx->name == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+            return ENOMEM;
+        }
+    }
+
+    switch (dctx->cmdctx->cmd) {
+    case SSS_NSS_GETPWNAM:
+        ret = nss_cmd_getpwnam_search(dctx);
+        if (ret == EOK) {
+            ret = nss_cmd_getpw_send_reply(dctx, false);
+        }
+        break;
+    case SSS_NSS_INITGR:
+        ret = nss_cmd_initgroups_search(dctx);
+        if (ret == EOK) {
+            ret = nss_cmd_initgr_send_reply(dctx);
+        }
+        break;
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid command [%d].\n",
+                                    dctx->cmdctx->cmd);
+        ret = EINVAL;
+    }
+
+    return ret;
+}
+
 static void nss_cmd_getby_dp_callback(uint16_t err_maj, uint32_t err_min,
                                       const char *err_msg, void *ptr)
 {
@@ -1006,6 +1079,14 @@ static void nss_cmd_getby_dp_callback(uint16_t err_maj, uint32_t err_min,
     }
 
 done:
+    if (ret == ENOENT
+        && (dctx->cmdctx->cmd == SSS_NSS_GETPWNAM
+                || dctx->cmdctx->cmd == SSS_NSS_INITGR)
+        && dctx->rawname != NULL && strchr(dctx->rawname, '@') != NULL) {
+        /* assume Kerberos principal */
+        ret = nss_cmd_assume_upn(dctx);
+    }
+
     ret = nss_cmd_done(cmdctx, ret);
     if (ret) {
         NSS_CMD_FATAL_ERROR(cctx);
@@ -1211,6 +1292,11 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
         if (ret == EOK) {
             /* we have results to return */
             ret = nss_cmd_getpw_send_reply(dctx, false);
+        } else if (ret == ENOENT
+                && dctx->rawname != NULL
+                && strchr(dctx->rawname, '@') != NULL) {
+            /* assume Kerberos principal */
+            ret = nss_cmd_assume_upn(dctx);
         }
         break;
     case SSS_NSS_GETGRNAM:
@@ -1225,6 +1311,11 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx)
         if (ret == EOK) {
             /* we have results to return */
             ret = nss_cmd_initgr_send_reply(dctx);
+        } else if (ret == ENOENT
+                && dctx->rawname != NULL
+                && strchr(dctx->rawname, '@') != NULL) {
+            /* assume Kerberos principal */
+            ret = nss_cmd_assume_upn(dctx);
         }
         break;
     case SSS_NSS_GETSIDBYNAME:
@@ -1261,7 +1352,12 @@ static void nss_cmd_getbynam_done(struct tevent_req *req)
     ret = sss_parse_name_for_domains(cmdctx, cctx->rctx->domains,
                                      cctx->rctx->default_domain, rawname,
                                      &domname, &cmdctx->name);
-    if (ret != EOK) {
+    if (ret == EAGAIN && (dctx->cmdctx->cmd == SSS_NSS_GETPWNAM
+                            || dctx->cmdctx->cmd == SSS_NSS_INITGR)) {
+        /* assume Kerberos principal */
+        ret = nss_cmd_assume_upn(dctx);
+        goto done;
+    } else if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "Invalid name received [%s]\n", rawname);
         ret = ENOENT;
         goto done;
@@ -1291,6 +1387,11 @@ static void nss_cmd_getbynam_done(struct tevent_req *req)
         if (ret == EOK) {
             /* we have results to return */
             ret = nss_cmd_getpw_send_reply(dctx, false);
+        } else if (ret == ENOENT
+                    && dctx->rawname != NULL
+                    && strchr(dctx->rawname, '@') != NULL) {
+            /* assume Kerberos principal */
+            ret = nss_cmd_assume_upn(dctx);
         }
         break;
     case SSS_NSS_GETGRNAM:
@@ -1305,6 +1406,11 @@ static void nss_cmd_getbynam_done(struct tevent_req *req)
         if (ret == EOK) {
             /* we have results to return */
             ret = nss_cmd_initgr_send_reply(dctx);
+        } else if (ret == ENOENT
+                    && dctx->rawname != NULL
+                    && strchr(dctx->rawname, '@') != NULL) {
+            /* assume Kerberos principal */
+            ret = nss_cmd_assume_upn(dctx);
         }
         break;
     case SSS_NSS_GETSIDBYNAME:
@@ -3734,13 +3840,17 @@ static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx)
     char *name = NULL;
     struct nss_ctx *nctx;
     int ret;
+    static const char *user_attrs[] = SYSDB_PW_ATTRS;
+    struct ldb_message *msg;
+    const char *sysdb_name;
 
     nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
 
     while (dom) {
        /* if it is a domainless search, skip domains that require fully
          * qualified names instead */
-        while (dom && cmdctx->check_next && dom->fqnames) {
+        while (dom && cmdctx->check_next && dom->fqnames
+                && !cmdctx->name_is_upn) {
             dom = get_next_domain(dom, false);
         }
 
@@ -3797,7 +3907,24 @@ static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx)
             return EIO;
         }
 
-        ret = sysdb_initgroups(cmdctx, dom, name, &dctx->res);
+        if (cmdctx->name_is_upn) {
+            ret = sysdb_search_user_by_upn(cmdctx, dom, name, user_attrs, &msg);
+            if (ret != EOK && ret != ENOENT) {
+                DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_upn failed.\n");
+                return ret;
+            }
+
+            sysdb_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+            if (sysdb_name == NULL) {
+                DEBUG(SSSDBG_OP_FAILURE,
+                      "Sysdb entry does not have a name.\n");
+                return EINVAL;
+            }
+
+            ret = sysdb_initgroups(cmdctx, dom, sysdb_name, &dctx->res);
+        } else {
+            ret = sysdb_initgroups(cmdctx, dom, name, &dctx->res);
+        }
         if (ret != EOK) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Failed to make request to our cache! [%d][%s]\n",
@@ -3828,7 +3955,8 @@ static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx)
          * yet) then verify that the cache is uptodate */
         if (dctx->check_provider) {
             ret = check_cache(dctx, nctx, dctx->res,
-                              SSS_DP_INITGROUPS, name, 0, NULL,
+                              SSS_DP_INITGROUPS, name, 0,
+                              cmdctx->name_is_upn ? EXTRA_NAME_IS_UPN : NULL,
                               nss_cmd_getby_dp_callback,
                               dctx);
             if (ret != EOK) {
diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h
index 23bb6a8..e5f3bde 100644
--- a/src/responder/nss/nsssrv_private.h
+++ b/src/responder/nss/nsssrv_private.h
@@ -31,6 +31,7 @@ struct nss_cmd_ctx {
     struct cli_ctx *cctx;
     enum sss_cli_command cmd;
     char *name;
+    bool name_is_upn;
     uint32_t id;
     char *secid;
 
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index eae1734..561bd3d 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -882,7 +882,22 @@ static void pam_forwarder_cb(struct tevent_req *req)
     pd = preq->pd;
 
     ret = pam_forwarder_parse_data(cctx, pd);
-    if (ret != EOK) {
+    if (ret == EAGAIN) {
+        if (strchr(preq->pd->logon_name, '@') == NULL) {
+            goto done;
+        }
+        /* Assuming Kerberos principal */
+        preq->domain = preq->cctx->rctx->domains;
+        preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider);
+        preq->pd->user = talloc_strdup(preq->pd, preq->pd->logon_name);
+        if (preq->pd->user == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n");
+            ret = ENOMEM;
+            goto done;
+        }
+        preq->pd->name_is_upn = true;
+        preq->pd->domain = NULL;
+    } else if (ret != EOK) {
         ret = EINVAL;
         goto done;
     }
@@ -916,12 +931,14 @@ static int pam_check_user_search(struct pam_auth_req *preq)
     struct dp_callback_ctx *cb_ctx;
     struct pam_ctx *pctx =
             talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
-    struct ldb_result *res;
+    static const char *user_attrs[] = SYSDB_PW_ATTRS;
+    struct ldb_message *msg;
 
     while (dom) {
        /* if it is a domainless search, skip domains that require fully
          * qualified names instead */
-        while (dom && !preq->pd->domain && dom->fqnames) {
+        while (dom && !preq->pd->domain && !preq->pd->name_is_upn
+                && dom->fqnames) {
             dom = get_next_domain(dom, false);
         }
 
@@ -979,20 +996,18 @@ static int pam_check_user_search(struct pam_auth_req *preq)
             return EFAULT;
         }
 
-        ret = sysdb_getpwnam(preq, dom, name, &res);
-        if (ret != EOK) {
+        if (preq->pd->name_is_upn) {
+            ret = sysdb_search_user_by_upn(preq, dom, name, user_attrs, &msg);
+        } else {
+            ret = sysdb_search_user_by_name(preq, dom, name, user_attrs, &msg);
+        }
+        if (ret != EOK && ret != ENOENT) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Failed to make request to our cache!\n");
             return EIO;
         }
 
-        if (res->count > 1) {
-            DEBUG(SSSDBG_FATAL_FAILURE,
-                  "getpwnam call returned more than one result !?!\n");
-            return ENOENT;
-        }
-
-        if (res->count == 0) {
+        if (ret == ENOENT) {
             if (preq->check_provider == false) {
                 /* set negative cache only if not result of cache check */
                 ret = sss_ncache_set_user(pctx->ncache, false, dom, name);
@@ -1021,7 +1036,7 @@ static int pam_check_user_search(struct pam_auth_req *preq)
 
         /* if we need to check the remote account go on */
         if (preq->check_provider) {
-            cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0],
+            cacheExpire = ldb_msg_find_attr_as_uint64(msg,
                                                       SYSDB_CACHE_EXPIRE, 0);
             if (cacheExpire < time(NULL)) {
                 break;
@@ -1032,7 +1047,7 @@ static int pam_check_user_search(struct pam_auth_req *preq)
               "Returning info for user [%s@%s]\n", name, dom->name);
 
         /* We might have searched by alias. Pass on the primary name */
-        ret = pd_set_primary_name(res->msgs[0], preq->pd);
+        ret = pd_set_primary_name(msg, preq->pd);
         if (ret != EOK) {
             DEBUG(SSSDBG_CRIT_FAILURE, "Could not canonicalize username\n");
             return ret;
@@ -1054,8 +1069,8 @@ static int pam_check_user_search(struct pam_auth_req *preq)
         preq->check_provider = false;
 
         dpreq = sss_dp_get_account_send(preq, preq->cctx->rctx,
-                                        dom, false, SSS_DP_INITGROUPS,
-                                        name, 0, NULL);
+                              dom, false, SSS_DP_INITGROUPS, name, 0,
+                              preq->pd->name_is_upn ? EXTRA_NAME_IS_UPN : NULL);
         if (!dpreq) {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Out of memory sending data provider request\n");
-- 
1.8.3.1



More information about the sssd-devel mailing list