Blame SOURCES/0037-PAM-handled-multiple-certs-in-the-responder.patch

ced1f5
From f4cac6544b5b6fb094d2088bf75f443fb74028bc Mon Sep 17 00:00:00 2001
ced1f5
From: Sumit Bose <sbose@redhat.com>
ced1f5
Date: Fri, 25 Aug 2017 12:51:09 +0200
ced1f5
Subject: [PATCH 37/47] PAM: handled multiple certs in the responder
ced1f5
MIME-Version: 1.0
ced1f5
Content-Type: text/plain; charset=UTF-8
ced1f5
Content-Transfer-Encoding: 8bit
ced1f5
ced1f5
This patch refactors the handling of the certificate and the attributes
ced1f5
to address the certificate on the Smartcard (module name, token name and
ced1f5
key id). Instead of using individual variables the values are put into a
ced1f5
new struct cert_auth_info. Since the new struct can be used as a list
ced1f5
the PAM responder can now handle multiple certificates on the Smartcard
ced1f5
and can send the needed data to pam_sss with multiple SSS_PAM_CERT_INFO
ced1f5
messages.
ced1f5
ced1f5
Unit tests are added to confirm the expected behavior.
ced1f5
ced1f5
Related to https://pagure.io/SSSD/sssd/issue/3560
ced1f5
ced1f5
Reviewed-by: Fabiano FidĂȘncio <fidencio@redhat.com>
ced1f5
Tested-by: Scott Poore <spoore@redhat.com>
ced1f5
(cherry picked from commit 0bdd8800c16f39b8fe308d20694ad905c669dff3)
ced1f5
ced1f5
The following binaries have been removed from the original patch:
ced1f5
- src/tests/cmocka/p11_nssdb_2certs/cert9.db
ced1f5
- src/tests/cmocka/p11_nssdb_2certs/key4.db
ced1f5
ced1f5
The reason for that is that we can't apply a patch which is a binary
ced1f5
file using rpm, thus removing the patches here and adding them as source
ced1f5
files in the sssd.spec seems to be the best solution.
ced1f5
---
ced1f5
 Makefile.am                                  |   2 +
ced1f5
 src/responder/pam/pamsrv.h                   |  25 ++-
ced1f5
 src/responder/pam/pamsrv_cmd.c               | 257 ++++++++++++++++++---------
ced1f5
 src/responder/pam/pamsrv_p11.c               | 181 ++++++++++++++-----
ced1f5
 src/tests/cmocka/p11_nssdb_2certs/pkcs11.txt |   4 +
ced1f5
 src/tests/cmocka/test_pam_srv.c              | 216 +++++++++++++++++++++-
ced1f5
 src/tests/whitespace_test                    |   2 +-
ced1f5
 7 files changed, 538 insertions(+), 149 deletions(-)
ced1f5
 create mode 100644 src/tests/cmocka/p11_nssdb_2certs/pkcs11.txt
ced1f5
ced1f5
diff --git a/Makefile.am b/Makefile.am
ced1f5
index bbc90d9bad4d22ca0284ea95281a487d42399c05..4ed872a532daf9b934537cc5f64ce77778121e2a 100644
ced1f5
--- a/Makefile.am
ced1f5
+++ b/Makefile.am
ced1f5
@@ -481,6 +481,8 @@ dist_noinst_DATA = \
ced1f5
     contrib/ci/sssd.supp \
ced1f5
     src/tests/cmocka/p11_nssdb/cert9.db \
ced1f5
     src/tests/cmocka/p11_nssdb/key4.db \
ced1f5
+    src/tests/cmocka/p11_nssdb_2certs/cert9.db \
ced1f5
+    src/tests/cmocka/p11_nssdb_2certs/key4.db \
ced1f5
     $(SYSTEMTAP_PROBES) \
ced1f5
     $(NULL)
ced1f5
 
ced1f5
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
ced1f5
index 896f71befbc9947a53b5eb20cba0bb3d104c4cf2..f15f7f19f1f38626288416c9f2038371c6f58b47 100644
ced1f5
--- a/src/responder/pam/pamsrv.h
ced1f5
+++ b/src/responder/pam/pamsrv.h
ced1f5
@@ -73,10 +73,8 @@ struct pam_auth_req {
ced1f5
     struct pam_auth_dp_req *dpreq_spy;
ced1f5
 
ced1f5
     struct ldb_message *user_obj;
ced1f5
-    struct ldb_result *cert_user_objs;
ced1f5
-    char *token_name;
ced1f5
-    char *module_name;
ced1f5
-    char *key_id;
ced1f5
+    struct cert_auth_info *cert_list;
ced1f5
+    struct cert_auth_info *current_cert;
ced1f5
     bool cert_auth_local;
ced1f5
 };
ced1f5
 
ced1f5
@@ -89,6 +87,16 @@ int LOCAL_pam_handler(struct pam_auth_req *preq);
ced1f5
 errno_t p11_child_init(struct pam_ctx *pctx);
ced1f5
 
ced1f5
 struct cert_auth_info;
ced1f5
+const char *sss_cai_get_cert(struct cert_auth_info *i);
ced1f5
+const char *sss_cai_get_token_name(struct cert_auth_info *i);
ced1f5
+const char *sss_cai_get_module_name(struct cert_auth_info *i);
ced1f5
+const char *sss_cai_get_key_id(struct cert_auth_info *i);
ced1f5
+struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i);
ced1f5
+struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i);
ced1f5
+void sss_cai_set_cert_user_objs(struct cert_auth_info *i,
ced1f5
+                                struct ldb_result *cert_user_objs);
ced1f5
+void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count,
ced1f5
+                         size_t *_cert_user_count);
ced1f5
 
ced1f5
 struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
ced1f5
                                        struct tevent_context *ev,
ced1f5
@@ -98,12 +106,11 @@ struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
ced1f5
                                        const char *verify_opts,
ced1f5
                                        struct pam_data *pd);
ced1f5
 errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
ced1f5
-                            char **cert, char **token_name, char **module_name,
ced1f5
-                            char **key_id);
ced1f5
+                            struct cert_auth_info **cert_list);
ced1f5
 
ced1f5
-errno_t add_pam_cert_response(struct pam_data *pd, const char *user,
ced1f5
-                              const char *token_name, const char *module_name,
ced1f5
-                              const char *key_id, enum response_type type);
ced1f5
+errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
ced1f5
+                              struct cert_auth_info *cert_info,
ced1f5
+                              enum response_type type);
ced1f5
 
ced1f5
 bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd);
ced1f5
 
ced1f5
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
ced1f5
index 51d8185650cf823da289a3398b10133065d82ae4..8b2c086e206796ad4c977495be957c56b3255e7f 100644
ced1f5
--- a/src/responder/pam/pamsrv_cmd.c
ced1f5
+++ b/src/responder/pam/pamsrv_cmd.c
ced1f5
@@ -1389,21 +1389,17 @@ done:
ced1f5
     return pam_check_user_done(preq, ret);
ced1f5
 }
ced1f5
 
ced1f5
+static errno_t pam_user_by_cert_step(struct pam_auth_req *preq);
ced1f5
 static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req);
ced1f5
 static void pam_forwarder_cert_cb(struct tevent_req *req)
ced1f5
 {
ced1f5
     struct pam_auth_req *preq = tevent_req_callback_data(req,
ced1f5
                                                          struct pam_auth_req);
ced1f5
-    struct cli_ctx *cctx = preq->cctx;
ced1f5
     struct pam_data *pd;
ced1f5
     errno_t ret = EOK;
ced1f5
-    char *cert;
ced1f5
-    struct pam_ctx *pctx =
ced1f5
-            talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
ced1f5
+    const char *cert;
ced1f5
 
ced1f5
-    ret = pam_check_cert_recv(req, preq, &cert, &preq->token_name,
ced1f5
-                                                &preq->module_name,
ced1f5
-                                                &preq->key_id);
ced1f5
+    ret = pam_check_cert_recv(req, preq, &preq->cert_list);
ced1f5
     talloc_free(req);
ced1f5
     if (ret != EOK) {
ced1f5
         DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n");
ced1f5
@@ -1412,6 +1408,8 @@ static void pam_forwarder_cert_cb(struct tevent_req *req)
ced1f5
 
ced1f5
     pd = preq->pd;
ced1f5
 
ced1f5
+    cert = sss_cai_get_cert(preq->cert_list);
ced1f5
+
ced1f5
     if (cert == NULL) {
ced1f5
         if (pd->logon_name == NULL) {
ced1f5
             DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
@@ -1431,21 +1429,42 @@ static void pam_forwarder_cert_cb(struct tevent_req *req)
ced1f5
         goto done;
ced1f5
     }
ced1f5
 
ced1f5
+    preq->current_cert = preq->cert_list;
ced1f5
+    ret = pam_user_by_cert_step(preq);
ced1f5
+    if (ret != EOK) {
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n");
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    return;
ced1f5
+
ced1f5
+done:
ced1f5
+    pam_check_user_done(preq, ret);
ced1f5
+}
ced1f5
+
ced1f5
+static errno_t pam_user_by_cert_step(struct pam_auth_req *preq)
ced1f5
+{
ced1f5
+    struct cli_ctx *cctx = preq->cctx;
ced1f5
+    struct tevent_req *req;
ced1f5
+    struct pam_ctx *pctx =
ced1f5
+            talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
ced1f5
+
ced1f5
+    if (preq->current_cert == NULL) {
ced1f5
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n");
ced1f5
+        return EINVAL;
ced1f5
+    }
ced1f5
 
ced1f5
     req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx,
ced1f5
                                       pctx->rctx->ncache, 0,
ced1f5
                                       preq->req_dom_type, NULL,
ced1f5
-                                      cert);
ced1f5
+                                      sss_cai_get_cert(preq->current_cert));
ced1f5
     if (req == NULL) {
ced1f5
         DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n");
ced1f5
-        ret = ENOMEM;
ced1f5
-        goto done;
ced1f5
+        return ENOMEM;
ced1f5
     }
ced1f5
+
ced1f5
     tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq);
ced1f5
-    return;
ced1f5
-
ced1f5
-done:
ced1f5
-    pam_check_user_done(preq, ret);
ced1f5
+    return EOK;
ced1f5
 }
ced1f5
 
ced1f5
 static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx,
ced1f5
@@ -1511,6 +1530,9 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
     struct pam_auth_req *preq = tevent_req_callback_data(req,
ced1f5
                                                          struct pam_auth_req);
ced1f5
     const char *cert_user = NULL;
ced1f5
+    size_t cert_count = 0;
ced1f5
+    size_t cert_user_count = 0;
ced1f5
+    struct ldb_result *cert_user_objs;
ced1f5
 
ced1f5
     ret = cache_req_recv(preq, req, &results);
ced1f5
     talloc_zfree(req);
ced1f5
@@ -1521,12 +1543,39 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
 
ced1f5
     if (ret == EOK) {
ced1f5
         ret = get_results_from_all_domains(preq, results,
ced1f5
-                                           &preq->cert_user_objs);
ced1f5
+                                           &cert_user_objs);
ced1f5
         if (ret != EOK) {
ced1f5
             DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n");
ced1f5
             goto done;
ced1f5
         }
ced1f5
 
ced1f5
+        sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs);
ced1f5
+    }
ced1f5
+
ced1f5
+    preq->current_cert = sss_cai_get_next(preq->current_cert);
ced1f5
+    if (preq->current_cert != NULL) {
ced1f5
+        ret = pam_user_by_cert_step(preq);
ced1f5
+        if (ret != EOK) {
ced1f5
+            DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n");
ced1f5
+            goto done;
ced1f5
+        }
ced1f5
+        return;
ced1f5
+    }
ced1f5
+
ced1f5
+    sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count);
ced1f5
+    DEBUG(SSSDBG_TRACE_ALL,
ced1f5
+          "Found [%zu] certificates and [%zu] related users.\n",
ced1f5
+          cert_count, cert_user_count);
ced1f5
+
ced1f5
+    if (cert_user_count == 0) {
ced1f5
+        if (preq->pd->logon_name == NULL) {
ced1f5
+            DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
+                  "Missing logon name and no certificate user found.\n");
ced1f5
+            ret = ENOENT;
ced1f5
+            goto done;
ced1f5
+        }
ced1f5
+    } else {
ced1f5
+
ced1f5
         if (preq->pd->logon_name == NULL) {
ced1f5
             if (preq->pd->cmd != SSS_PAM_PREAUTH) {
ced1f5
                 DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
@@ -1535,9 +1584,39 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
                 goto done;
ced1f5
             }
ced1f5
 
ced1f5
-            if (preq->cert_user_objs->count == 1) {
ced1f5
+            if (cert_count > 1) {
ced1f5
+                for (preq->current_cert = preq->cert_list;
ced1f5
+                     preq->current_cert != NULL;
ced1f5
+                     preq->current_cert = sss_cai_get_next(preq->current_cert)) {
ced1f5
+
ced1f5
+                    ret = add_pam_cert_response(preq->pd, "",
ced1f5
+                                       preq->current_cert,
ced1f5
+                                       preq->cctx->rctx->domains->user_name_hint
ced1f5
+                                            ? SSS_PAM_CERT_INFO_WITH_HINT
ced1f5
+                                            : SSS_PAM_CERT_INFO);
ced1f5
+                    if (ret != EOK) {
ced1f5
+                        DEBUG(SSSDBG_OP_FAILURE,
ced1f5
+                              "add_pam_cert_response failed.\n");
ced1f5
+                        preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
ced1f5
+                    }
ced1f5
+                }
ced1f5
+
ced1f5
+                ret = EOK;
ced1f5
+                preq->pd->pam_status = PAM_SUCCESS;
ced1f5
+                pam_reply(preq);
ced1f5
+                goto done;
ced1f5
+            }
ced1f5
+
ced1f5
+            if (cert_user_count == 1) {
ced1f5
+                cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list);
ced1f5
+                if (cert_user_objs == NULL) {
ced1f5
+                    DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n");
ced1f5
+                    ret = ENOENT;
ced1f5
+                    goto done;
ced1f5
+                }
ced1f5
+
ced1f5
                 cert_user = ldb_msg_find_attr_as_string(
ced1f5
-                                                  preq->cert_user_objs->msgs[0],
ced1f5
+                                                  cert_user_objs->msgs[0],
ced1f5
                                                   SYSDB_NAME, NULL);
ced1f5
                 if (cert_user == NULL) {
ced1f5
                     DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
@@ -1564,9 +1643,7 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
 
ced1f5
             if (preq->cctx->rctx->domains->user_name_hint) {
ced1f5
                 ret = add_pam_cert_response(preq->pd, cert_user,
ced1f5
-                                            preq->token_name,
ced1f5
-                                            preq->module_name,
ced1f5
-                                            preq->key_id,
ced1f5
+                                            preq->cert_list,
ced1f5
                                             SSS_PAM_CERT_INFO_WITH_HINT);
ced1f5
                 preq->pd->pam_status = PAM_SUCCESS;
ced1f5
                 if (ret != EOK) {
ced1f5
@@ -1596,13 +1673,6 @@ static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req)
ced1f5
                 goto done;
ced1f5
             }
ced1f5
         }
ced1f5
-    } else {
ced1f5
-        if (preq->pd->logon_name == NULL) {
ced1f5
-            DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
-                  "Missing logon name and no certificate user found.\n");
ced1f5
-            ret = ENOENT;
ced1f5
-            goto done;
ced1f5
-        }
ced1f5
     }
ced1f5
 
ced1f5
     if (preq->user_obj == NULL) {
ced1f5
@@ -1884,7 +1954,9 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
ced1f5
     struct pam_ctx *pctx =
ced1f5
             talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
ced1f5
     const char *cert_user;
ced1f5
+    struct ldb_result *cert_user_objs;
ced1f5
     size_t c;
ced1f5
+    bool found = false;
ced1f5
 
ced1f5
     if (!preq->pd->domain) {
ced1f5
         preq->pd->domain = preq->domain->name;
ced1f5
@@ -1921,76 +1993,87 @@ static void pam_dom_forwarder(struct pam_auth_req *preq)
ced1f5
         return;
ced1f5
     }
ced1f5
 
ced1f5
-    if (may_do_cert_auth(pctx, preq->pd) && preq->cert_user_objs != NULL) {
ced1f5
+    if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) {
ced1f5
         /* Check if user matches certificate user */
ced1f5
-        for (c = 0; c < preq->cert_user_objs->count; c++) {
ced1f5
-            cert_user = ldb_msg_find_attr_as_string(
ced1f5
-                                                  preq->cert_user_objs->msgs[c],
ced1f5
-                                                  SYSDB_NAME,
ced1f5
-                                                  NULL);
ced1f5
-            if (cert_user == NULL) {
ced1f5
-                /* Even if there might be other users mapped to the
ced1f5
-                 * certificate a missing SYSDB_NAME indicates some critical
ced1f5
-                 * condition which justifies that the whole request is aborted
ced1f5
-                 * */
ced1f5
-                DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
-                      "Certificate user object has no name.\n");
ced1f5
-                preq->pd->pam_status = PAM_USER_UNKNOWN;
ced1f5
-                pam_reply(preq);
ced1f5
-                return;
ced1f5
+        found = false;
ced1f5
+        for (preq->current_cert = preq->cert_list;
ced1f5
+             preq->current_cert != NULL;
ced1f5
+             preq->current_cert = sss_cai_get_next(preq->current_cert)) {
ced1f5
+
ced1f5
+            cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert);
ced1f5
+            if (cert_user_objs == NULL) {
ced1f5
+                DEBUG(SSSDBG_OP_FAILURE,
ced1f5
+                      "Unexpteced missing certificate user, "
ced1f5
+                      "trying next certificate.\n");
ced1f5
+                continue;
ced1f5
             }
ced1f5
 
ced1f5
-            /* pam_check_user_search() calls pd_set_primary_name() is the search
ced1f5
-             * was successful, so pd->user contains the canonical sysdb name
ced1f5
-             * as well */
ced1f5
-            if (ldb_dn_compare(preq->cert_user_objs->msgs[c]->dn,
ced1f5
-                               preq->user_obj->dn) == 0) {
ced1f5
-
ced1f5
-                if (preq->pd->cmd == SSS_PAM_PREAUTH) {
ced1f5
-                    ret = sss_authtok_set_sc(preq->pd->authtok,
ced1f5
-                                             SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0,
ced1f5
-                                             preq->token_name, 0,
ced1f5
-                                             preq->module_name, 0,
ced1f5
-                                             preq->key_id, 0);
ced1f5
-                    if (ret != EOK) {
ced1f5
-                        DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_set_sc failed, "
ced1f5
-                                                 "Smartcard authentication "
ced1f5
-                                                 "detection might fail in the "
ced1f5
-                                                 "backend.\n");
ced1f5
-                    }
ced1f5
-
ced1f5
-                    ret = add_pam_cert_response(preq->pd, cert_user,
ced1f5
-                                                preq->token_name,
ced1f5
-                                                preq->module_name,
ced1f5
-                                                preq->key_id,
ced1f5
-                                                SSS_PAM_CERT_INFO);
ced1f5
-                    if (ret != EOK) {
ced1f5
-                        DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
ced1f5
-                        preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
ced1f5
-                    }
ced1f5
-                }
ced1f5
-
ced1f5
-                /* We are done if we do not have to call the backend */
ced1f5
-                if (preq->pd->cmd == SSS_PAM_AUTHENTICATE
ced1f5
-                        && preq->cert_auth_local) {
ced1f5
-                    preq->pd->pam_status = PAM_SUCCESS;
ced1f5
-                    preq->callback = pam_reply;
ced1f5
+            for (c = 0; c < cert_user_objs->count; c++) {
ced1f5
+                cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c],
ced1f5
+                                                        SYSDB_NAME, NULL);
ced1f5
+                if (cert_user == NULL) {
ced1f5
+                    /* Even if there might be other users mapped to the
ced1f5
+                     * certificate a missing SYSDB_NAME indicates some critical
ced1f5
+                     * condition which justifies that the whole request is aborted
ced1f5
+                     * */
ced1f5
+                    DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
+                          "Certificate user object has no name.\n");
ced1f5
+                    preq->pd->pam_status = PAM_USER_UNKNOWN;
ced1f5
                     pam_reply(preq);
ced1f5
                     return;
ced1f5
                 }
ced1f5
+
ced1f5
+                if (ldb_dn_compare(cert_user_objs->msgs[c]->dn,
ced1f5
+                                   preq->user_obj->dn) == 0) {
ced1f5
+                    found = true;
ced1f5
+                    if (preq->pd->cmd == SSS_PAM_PREAUTH) {
ced1f5
+                        ret = sss_authtok_set_sc(preq->pd->authtok,
ced1f5
+                                 SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0,
ced1f5
+                                 sss_cai_get_token_name(preq->current_cert), 0,
ced1f5
+                                 sss_cai_get_module_name(preq->current_cert), 0,
ced1f5
+                                 sss_cai_get_key_id(preq->current_cert), 0);
ced1f5
+                        if (ret != EOK) {
ced1f5
+                            DEBUG(SSSDBG_OP_FAILURE,
ced1f5
+                                  "sss_authtok_set_sc failed, Smartcard "
ced1f5
+                                  "authentication detection might fail in "
ced1f5
+                                  "the backend.\n");
ced1f5
+                        }
ced1f5
+
ced1f5
+                        /* FIXME: use the right cert info */
ced1f5
+                        ret = add_pam_cert_response(preq->pd, cert_user,
ced1f5
+                                                    preq->current_cert,
ced1f5
+                                                    SSS_PAM_CERT_INFO);
ced1f5
+                        if (ret != EOK) {
ced1f5
+                            DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n");
ced1f5
+                            preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL;
ced1f5
+                        }
ced1f5
+                    }
ced1f5
+
ced1f5
+                }
ced1f5
             }
ced1f5
         }
ced1f5
 
ced1f5
-        if (preq->pd->cmd == SSS_PAM_PREAUTH) {
ced1f5
-            DEBUG(SSSDBG_TRACE_FUNC,
ced1f5
-                  "User and certificate user do not match, "
ced1f5
-                  "continue with other authentication methods.\n");
ced1f5
+        if (found) {
ced1f5
+            /* We are done if we do not have to call the backend */
ced1f5
+            if (preq->pd->cmd == SSS_PAM_AUTHENTICATE
ced1f5
+                    && preq->cert_auth_local) {
ced1f5
+                preq->pd->pam_status = PAM_SUCCESS;
ced1f5
+                preq->callback = pam_reply;
ced1f5
+                pam_reply(preq);
ced1f5
+                return;
ced1f5
+            }
ced1f5
         } else {
ced1f5
-            DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
-                  "User and certificate user do not match.\n");
ced1f5
-            preq->pd->pam_status = PAM_AUTH_ERR;
ced1f5
-            pam_reply(preq);
ced1f5
-            return;
ced1f5
+            if (preq->pd->cmd == SSS_PAM_PREAUTH) {
ced1f5
+                DEBUG(SSSDBG_TRACE_FUNC,
ced1f5
+                      "User and certificate user do not match, "
ced1f5
+                      "continue with other authentication methods.\n");
ced1f5
+            } else {
ced1f5
+                DEBUG(SSSDBG_CRIT_FAILURE,
ced1f5
+                      "User and certificate user do not match.\n");
ced1f5
+                preq->pd->pam_status = PAM_AUTH_ERR;
ced1f5
+                pam_reply(preq);
ced1f5
+                return;
ced1f5
+            }
ced1f5
         }
ced1f5
     }
ced1f5
 
ced1f5
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
ced1f5
index ff32d1e726808caa36ca7cca557220866ef1a9ab..57c8e1e464f4262f2d78f869c52ca48bd469d90a 100644
ced1f5
--- a/src/responder/pam/pamsrv_p11.c
ced1f5
+++ b/src/responder/pam/pamsrv_p11.c
ced1f5
@@ -40,10 +40,80 @@ struct cert_auth_info {
ced1f5
     char *token_name;
ced1f5
     char *module_name;
ced1f5
     char *key_id;
ced1f5
+    struct ldb_result *cert_user_objs;
ced1f5
     struct cert_auth_info *prev;
ced1f5
     struct cert_auth_info *next;
ced1f5
 };
ced1f5
 
ced1f5
+const char *sss_cai_get_cert(struct cert_auth_info *i)
ced1f5
+{
ced1f5
+    return i != NULL ? i->cert : NULL;
ced1f5
+}
ced1f5
+
ced1f5
+const char *sss_cai_get_token_name(struct cert_auth_info *i)
ced1f5
+{
ced1f5
+    return i != NULL ? i->token_name : NULL;
ced1f5
+}
ced1f5
+
ced1f5
+const char *sss_cai_get_module_name(struct cert_auth_info *i)
ced1f5
+{
ced1f5
+    return i != NULL ? i->module_name : NULL;
ced1f5
+}
ced1f5
+
ced1f5
+const char *sss_cai_get_key_id(struct cert_auth_info *i)
ced1f5
+{
ced1f5
+    return i != NULL ? i->key_id : NULL;
ced1f5
+}
ced1f5
+
ced1f5
+struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i)
ced1f5
+{
ced1f5
+    return i != NULL ? i->next : NULL;
ced1f5
+}
ced1f5
+
ced1f5
+struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i)
ced1f5
+{
ced1f5
+    return i != NULL ? i->cert_user_objs : NULL;
ced1f5
+}
ced1f5
+
ced1f5
+void sss_cai_set_cert_user_objs(struct cert_auth_info *i,
ced1f5
+                                struct ldb_result *cert_user_objs)
ced1f5
+{
ced1f5
+    if (i->cert_user_objs != NULL) {
ced1f5
+        talloc_free(i->cert_user_objs);
ced1f5
+    }
ced1f5
+    i->cert_user_objs = talloc_steal(i, cert_user_objs);
ced1f5
+}
ced1f5
+
ced1f5
+void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count,
ced1f5
+                         size_t *_cert_user_count)
ced1f5
+{
ced1f5
+    struct cert_auth_info *c;
ced1f5
+    struct cert_auth_info *tmp;
ced1f5
+    size_t cert_count = 0;
ced1f5
+    size_t cert_user_count = 0;
ced1f5
+    struct ldb_result *user_objs;
ced1f5
+
ced1f5
+    DLIST_FOR_EACH_SAFE(c, tmp, *list) {
ced1f5
+        user_objs = sss_cai_get_cert_user_objs(c);
ced1f5
+        if (user_objs != NULL) {
ced1f5
+            cert_count++;
ced1f5
+            cert_user_count += user_objs->count;
ced1f5
+        } else {
ced1f5
+            DLIST_REMOVE(*list, c);
ced1f5
+        }
ced1f5
+    }
ced1f5
+
ced1f5
+    if (_cert_count != NULL) {
ced1f5
+        *_cert_count = cert_count;
ced1f5
+    }
ced1f5
+
ced1f5
+    if (_cert_user_count != NULL) {
ced1f5
+        *_cert_user_count = cert_user_count;
ced1f5
+    }
ced1f5
+
ced1f5
+    return;
ced1f5
+}
ced1f5
+
ced1f5
 errno_t p11_child_init(struct pam_ctx *pctx)
ced1f5
 {
ced1f5
     return child_debug_init(P11_CHILD_LOG_FILE, &pctx->p11_child_debug_fd);
ced1f5
@@ -566,39 +636,71 @@ static void p11_child_timeout(struct tevent_context *ev,
ced1f5
 }
ced1f5
 
ced1f5
 errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
ced1f5
-                            char **cert, char **token_name, char **module_name,
ced1f5
-                            char **key_id)
ced1f5
+                            struct cert_auth_info **cert_list)
ced1f5
 {
ced1f5
+    struct cert_auth_info *tmp_cert_auth_info;
ced1f5
     struct pam_check_cert_state *state =
ced1f5
                               tevent_req_data(req, struct pam_check_cert_state);
ced1f5
 
ced1f5
     TEVENT_REQ_RETURN_ON_ERROR(req);
ced1f5
 
ced1f5
-    if (state->cert_list == NULL) {
ced1f5
-        *token_name = NULL;
ced1f5
-        *cert = NULL;
ced1f5
-        *module_name = NULL;
ced1f5
-        *key_id = NULL;
ced1f5
+    if (cert_list != NULL) {
ced1f5
+        DLIST_FOR_EACH(tmp_cert_auth_info, state->cert_list) {
ced1f5
+            talloc_steal(mem_ctx, tmp_cert_auth_info);
ced1f5
+        }
ced1f5
+
ced1f5
+        *cert_list = state->cert_list;
ced1f5
     }
ced1f5
 
ced1f5
-    if (cert != NULL) {
ced1f5
-        *cert = (state->cert_list == NULL) ? NULL
ced1f5
-                                : talloc_steal(mem_ctx, state->cert_list->cert);
ced1f5
+    return EOK;
ced1f5
+}
ced1f5
+
ced1f5
+static errno_t pack_cert_data(TALLOC_CTX *mem_ctx, const char *sysdb_username,
ced1f5
+                              struct cert_auth_info *cert_info,
ced1f5
+                              uint8_t **_msg, size_t *_msg_len)
ced1f5
+{
ced1f5
+    uint8_t *msg = NULL;
ced1f5
+    size_t msg_len;
ced1f5
+    const char *token_name;
ced1f5
+    const char *module_name;
ced1f5
+    const char *key_id;
ced1f5
+    size_t user_len;
ced1f5
+    size_t token_len;
ced1f5
+    size_t module_len;
ced1f5
+    size_t key_id_len;
ced1f5
+    const char *username = "";
ced1f5
+
ced1f5
+    if (sysdb_username != NULL) {
ced1f5
+        username = sysdb_username;
ced1f5
     }
ced1f5
 
ced1f5
-    if (token_name != NULL) {
ced1f5
-        *token_name = (state->cert_list == NULL) ? NULL
ced1f5
-                          : talloc_steal(mem_ctx, state->cert_list->token_name);
ced1f5
+    token_name = sss_cai_get_token_name(cert_info);
ced1f5
+    module_name = sss_cai_get_module_name(cert_info);
ced1f5
+    key_id = sss_cai_get_key_id(cert_info);
ced1f5
+
ced1f5
+    user_len = strlen(username) + 1;
ced1f5
+    token_len = strlen(token_name) + 1;
ced1f5
+    module_len = strlen(module_name) + 1;
ced1f5
+    key_id_len = strlen(key_id) + 1;
ced1f5
+    msg_len = user_len + token_len + module_len + key_id_len;
ced1f5
+
ced1f5
+    msg = talloc_zero_size(mem_ctx, msg_len);
ced1f5
+    if (msg == NULL) {
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n");
ced1f5
+        return ENOMEM;
ced1f5
     }
ced1f5
 
ced1f5
-    if (module_name != NULL) {
ced1f5
-        *module_name = (state->cert_list == NULL) ? NULL
ced1f5
-                         : talloc_steal(mem_ctx, state->cert_list->module_name);
ced1f5
+    memcpy(msg, username, user_len);
ced1f5
+    memcpy(msg + user_len, token_name, token_len);
ced1f5
+    memcpy(msg + user_len + token_len, module_name, module_len);
ced1f5
+    memcpy(msg + user_len + token_len + module_len, key_id, key_id_len);
ced1f5
+
ced1f5
+    if (_msg != NULL) {
ced1f5
+        *_msg = msg;
ced1f5
     }
ced1f5
 
ced1f5
-    if (key_id != NULL) {
ced1f5
-        *key_id = (state->cert_list == NULL) ? NULL
ced1f5
-                              : talloc_steal(mem_ctx, state->cert_list->key_id);
ced1f5
+    if (_msg_len != NULL) {
ced1f5
+        *_msg_len = msg_len;
ced1f5
     }
ced1f5
 
ced1f5
     return EOK;
ced1f5
@@ -613,18 +715,13 @@ errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
ced1f5
 #define PKCS11_LOGIN_TOKEN_ENV_NAME "PKCS11_LOGIN_TOKEN_NAME"
ced1f5
 
ced1f5
 errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
ced1f5
-                              const char *token_name, const char *module_name,
ced1f5
-                              const char *key_id, enum response_type type)
ced1f5
+                              struct cert_auth_info *cert_info,
ced1f5
+                              enum response_type type)
ced1f5
 {
ced1f5
     uint8_t *msg = NULL;
ced1f5
     char *env = NULL;
ced1f5
-    size_t user_len;
ced1f5
     size_t msg_len;
ced1f5
-    size_t slot_len;
ced1f5
-    size_t module_len;
ced1f5
-    size_t key_id_len;
ced1f5
     int ret;
ced1f5
-    const char *username = "";
ced1f5
 
ced1f5
     if (type != SSS_PAM_CERT_INFO && type != SSS_PAM_CERT_INFO_WITH_HINT) {
ced1f5
         DEBUG(SSSDBG_CRIT_FAILURE, "Invalid response type [%d].\n", type);
ced1f5
@@ -632,26 +729,14 @@ errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
ced1f5
     }
ced1f5
 
ced1f5
     if ((type == SSS_PAM_CERT_INFO && sysdb_username == NULL)
ced1f5
-            || token_name == NULL || module_name == NULL || key_id == NULL) {
ced1f5
+            || cert_info == NULL
ced1f5
+            || sss_cai_get_token_name(cert_info) == NULL
ced1f5
+            || sss_cai_get_module_name(cert_info) == NULL
ced1f5
+            || sss_cai_get_key_id(cert_info) == NULL) {
ced1f5
         DEBUG(SSSDBG_CRIT_FAILURE, "Missing mandatory user or slot name.\n");
ced1f5
         return EINVAL;
ced1f5
     }
ced1f5
 
ced1f5
-    if (sysdb_username != NULL) {
ced1f5
-        username = sysdb_username;
ced1f5
-    }
ced1f5
-    user_len = strlen(username) + 1;
ced1f5
-    slot_len = strlen(token_name) + 1;
ced1f5
-    module_len = strlen(module_name) + 1;
ced1f5
-    key_id_len = strlen(key_id) + 1;
ced1f5
-    msg_len = user_len + slot_len + module_len + key_id_len;
ced1f5
-
ced1f5
-    msg = talloc_zero_size(pd, msg_len);
ced1f5
-    if (msg == NULL) {
ced1f5
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n");
ced1f5
-        return ENOMEM;
ced1f5
-    }
ced1f5
-
ced1f5
     /* sysdb_username is a fully-qualified name which is used by pam_sss when
ced1f5
      * prompting the user for the PIN and as login name if it wasn't set by
ced1f5
      * the PAM caller but has to be determined based on the inserted
ced1f5
@@ -659,10 +744,12 @@ errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
ced1f5
      * re_expression config option was set in a way that user@domain cannot be
ced1f5
      * handled anymore some more logic has to be added here. But for the time
ced1f5
      * being I think using sysdb_username is fine. */
ced1f5
-    memcpy(msg, username, user_len);
ced1f5
-    memcpy(msg + user_len, token_name, slot_len);
ced1f5
-    memcpy(msg + user_len + slot_len, module_name, module_len);
ced1f5
-    memcpy(msg + user_len + slot_len + module_len, key_id, key_id_len);
ced1f5
+
ced1f5
+    ret = pack_cert_data(pd, sysdb_username, cert_info, &msg, &msg_len);
ced1f5
+    if (ret != EOK) {
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE, "pack_cert_data failed.\n");
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
 
ced1f5
     ret = pam_add_response(pd, type, msg_len, msg);
ced1f5
     talloc_free(msg);
ced1f5
@@ -674,7 +761,7 @@ errno_t add_pam_cert_response(struct pam_data *pd, const char *sysdb_username,
ced1f5
 
ced1f5
     if (strcmp(pd->service, "gdm-smartcard") == 0) {
ced1f5
         env = talloc_asprintf(pd, "%s=%s", PKCS11_LOGIN_TOKEN_ENV_NAME,
ced1f5
-                              token_name);
ced1f5
+                              sss_cai_get_token_name(cert_info));
ced1f5
         if (env == NULL) {
ced1f5
             DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
ced1f5
             return ENOMEM;
ced1f5
diff --git a/src/tests/cmocka/p11_nssdb_2certs/pkcs11.txt b/src/tests/cmocka/p11_nssdb_2certs/pkcs11.txt
ced1f5
new file mode 100644
ced1f5
index 0000000000000000000000000000000000000000..73f5279c338dffe25ad2fad8c9cafae2f3c4cdfe
ced1f5
--- /dev/null
ced1f5
+++ b/src/tests/cmocka/p11_nssdb_2certs/pkcs11.txt
ced1f5
@@ -0,0 +1,4 @@
ced1f5
+library=
ced1f5
+name=NSS Internal PKCS #11 Module
ced1f5
+parameters=configdir='sql:../src/tests/cmocka/p11_nssdb' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription=''
ced1f5
+NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30})
ced1f5
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
ced1f5
index 351067eb664431cda159f73590de772920504380..7f0ed706512ffe0866c0e1fb7e6baa16bec942d8 100644
ced1f5
--- a/src/tests/cmocka/test_pam_srv.c
ced1f5
+++ b/src/tests/cmocka/test_pam_srv.c
ced1f5
@@ -47,6 +47,9 @@
ced1f5
 #define NSS_DB_PATH TESTS_PATH
ced1f5
 #define NSS_DB "sql:"NSS_DB_PATH
ced1f5
 
ced1f5
+#define NSS_DB_PATH_2CERTS TESTS_PATH "_2certs"
ced1f5
+#define NSS_DB_2CERTS "sql:"NSS_DB_PATH_2CERTS
ced1f5
+
ced1f5
 #define TEST_TOKEN_NAME "SSSD Test Token"
ced1f5
 #define TEST_MODULE_NAME "NSS-Internal"
ced1f5
 #define TEST_KEY_ID "A5EF7DEE625CA5996C8D1BA7D036708161FD49E7"
ced1f5
@@ -74,6 +77,28 @@
ced1f5
 "8Z+9gqZhCa7FEKJOPNR9RVtJs0qUUutMZrp1zpyx0GTmXQBA7LbgPxy8L68uymEQ" \
ced1f5
 "XyQBwOYRORlnfGyu+Yc9c3E0Wx8Tlznz0lqPR9g="
ced1f5
 
ced1f5
+#define TEST2_KEY_ID "C8D60E009EB195D01A7083EE1D5419251AA87C2C"
ced1f5
+#define TEST_TOKEN_2ND_CERT \
ced1f5
+"MIIDazCCAlOgAwIBAgIBBzANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQKDAlJUEEu" \
ced1f5
+"REVWRUwxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNjA1MjMx" \
ced1f5
+"NDEzMDFaFw0xODA1MTMxNDEzMDFaMCUxEjAQBgNVBAoMCUlQQS5ERVZFTDEPMA0G" \
ced1f5
+"A1UEAwwGSVBBIFJBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3abE" \
ced1f5
+"8LmIc6QN16VVxsMlN/rrCOoZKyyJolSzpP4+K66t+KZUiW/1j1MZogjyYyD39U1F" \
ced1f5
+"zpa2H+pID74XYrdiqP7sp+uE9/k2XOv/nN3FobXDt+fSINLDriCmxNhUZqpgo2uq" \
ced1f5
+"Mmka+yx2iJZwkntEoJTcd3aynoa2Sa2ZZbkMBy5p6/pUQKwnD6scOwe6mUDppIBK" \
ced1f5
+"+ZZRm+u/NDdIRFI5wfKLRR1r/ONaJA9nz1TxSEsgLsjG/1m+Zbb6lGG4pePIFkQ9" \
ced1f5
+"Iotpi64obBh93oIxzQR29lBG/FMjQVHlPIbx+xuGx11Vtp5pAomgFz0HRrj0leI7" \
ced1f5
+"bROE+jnC/VGPLQD2aQIDAQABo4GWMIGTMB8GA1UdIwQYMBaAFPci/0Km5D/L5z7Y" \
ced1f5
+"qwEc7E1/GwgcMEEGCCsGAQUFBwEBBDUwMzAxBggrBgEFBQcwAYYlaHR0cDovL2lw" \
ced1f5
+"YS1kZXZlbC5pcGEuZGV2ZWw6ODAvY2Evb2NzcDAOBgNVHQ8BAf8EBAMCBPAwHQYD" \
ced1f5
+"VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBg" \
ced1f5
+"4Sppx2C3eXPJ4Pd9XElkQPOaBReXf1vV0uk/GlK+rG+aAqAkA2Lryx5PK/iAuzAU" \
ced1f5
+"M6JUpELuQYgqugoCgBXMgsMlpAO/0C3CFq4ZH3KgIsRlRngKPrt6RG0UPMRD1CE2" \
ced1f5
+"tSVkwUWvyK83lDiu2BbWDXyMyz5eZOlp7uHusf5BKvob8jEndHj1YzaNTmVSsDM5" \
ced1f5
+"kiIwf8qgFhsO1HCq08PtAnbVHhqkcvnmIJN98eNWNfTKodDmFVbN8gB0wK+WB5ii" \
ced1f5
+"WVOw7+3/zF1QgqnYX3t+kPLRryip/wvTZkzXWwMNj/W6UHgjNF/4gWGoBgCHu+u3" \
ced1f5
+"EvjMmbVSrEkesibpGQS5"
ced1f5
+
ced1f5
 
ced1f5
 static char CACHED_AUTH_TIMEOUT_STR[] = "4";
ced1f5
 static const int CACHED_AUTH_TIMEOUT = 4;
ced1f5
@@ -111,6 +136,13 @@ static errno_t setup_nss_db(void)
ced1f5
         return ret;
ced1f5
     }
ced1f5
 
ced1f5
+    ret = mkdir(NSS_DB_PATH_2CERTS, 0775);
ced1f5
+    if (ret != EOK) {
ced1f5
+        DEBUG(SSSDBG_FATAL_FAILURE,
ced1f5
+              "Failed to create " NSS_DB_PATH_2CERTS ".\n");
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+
ced1f5
     child_pid = fork();
ced1f5
     if (child_pid == 0) { /* child */
ced1f5
         ret = execlp("certutil", "certutil", "-N", "--empty-password", "-d",
ced1f5
@@ -127,6 +159,22 @@ static errno_t setup_nss_db(void)
ced1f5
         return ret;
ced1f5
     }
ced1f5
 
ced1f5
+    child_pid = fork();
ced1f5
+    if (child_pid == 0) { /* child */
ced1f5
+        ret = execlp("certutil", "certutil", "-N", "--empty-password", "-d",
ced1f5
+                     NSS_DB_2CERTS, NULL);
ced1f5
+        if (ret == -1) {
ced1f5
+            DEBUG(SSSDBG_FATAL_FAILURE, "execl() failed.\n");
ced1f5
+            exit(-1);
ced1f5
+        }
ced1f5
+    } else if (child_pid > 0) {
ced1f5
+        wait(&status);
ced1f5
+    } else {
ced1f5
+        ret = errno;
ced1f5
+        DEBUG(SSSDBG_FATAL_FAILURE, "fork() failed\n");
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+
ced1f5
     fp = fopen(NSS_DB_PATH"/pkcs11.txt", "w");
ced1f5
     if (fp == NULL) {
ced1f5
         DEBUG(SSSDBG_FATAL_FAILURE, "fopen() failed.\n");
ced1f5
@@ -148,6 +196,27 @@ static errno_t setup_nss_db(void)
ced1f5
         return ret;
ced1f5
     }
ced1f5
 
ced1f5
+    fp = fopen(NSS_DB_PATH_2CERTS"/pkcs11.txt", "w");
ced1f5
+    if (fp == NULL) {
ced1f5
+        DEBUG(SSSDBG_FATAL_FAILURE, "fopen() failed.\n");
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+    ret = fprintf(fp, "library=libsoftokn3.so\nname=soft\n");
ced1f5
+    if (ret < 0) {
ced1f5
+        DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n");
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+    ret = fprintf(fp, "parameters=configdir='sql:%s/src/tests/cmocka/p11_nssdb_2certs' dbSlotDescription='SSSD Test Slot' dbTokenDescription='SSSD Test Token' secmod='secmod.db' flags=readOnly \n\n", ABS_SRC_DIR);
ced1f5
+    if (ret < 0) {
ced1f5
+        DEBUG(SSSDBG_FATAL_FAILURE, "fprintf() failed.\n");
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+    ret = fclose(fp);
ced1f5
+    if (ret != 0) {
ced1f5
+        DEBUG(SSSDBG_FATAL_FAILURE, "fclose() failed.\n");
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+
ced1f5
     return EOK;
ced1f5
 }
ced1f5
 
ced1f5
@@ -174,6 +243,26 @@ static void cleanup_nss_db(void)
ced1f5
     if (ret != EOK) {
ced1f5
         DEBUG(SSSDBG_OP_FAILURE, "Failed to remove " NSS_DB_PATH "\n");
ced1f5
     }
ced1f5
+
ced1f5
+    ret = unlink(NSS_DB_PATH_2CERTS"/cert9.db");
ced1f5
+    if (ret != EOK) {
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove cert9.db.\n");
ced1f5
+    }
ced1f5
+
ced1f5
+    ret = unlink(NSS_DB_PATH_2CERTS"/key4.db");
ced1f5
+    if (ret != EOK) {
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove key4.db.\n");
ced1f5
+    }
ced1f5
+
ced1f5
+    ret = unlink(NSS_DB_PATH_2CERTS"/pkcs11.txt");
ced1f5
+    if (ret != EOK) {
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove pkcs11.db.\n");
ced1f5
+    }
ced1f5
+
ced1f5
+    ret = rmdir(NSS_DB_PATH_2CERTS);
ced1f5
+    if (ret != EOK) {
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to remove " NSS_DB_PATH "\n");
ced1f5
+    }
ced1f5
 }
ced1f5
 
ced1f5
 struct pam_ctx *mock_pctx(TALLOC_CTX *mem_ctx)
ced1f5
@@ -749,7 +838,8 @@ static int test_pam_cert_check_gdm_smartcard(uint32_t status, uint8_t *body,
ced1f5
 }
ced1f5
 
ced1f5
 static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
ced1f5
-                                  enum response_type type, const char *name)
ced1f5
+                                  enum response_type type, const char *name,
ced1f5
+                                  const char *name2)
ced1f5
 {
ced1f5
     size_t rp = 0;
ced1f5
     uint32_t val;
ced1f5
@@ -763,7 +853,11 @@ static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
ced1f5
     if (name == NULL || *name == '\0') {
ced1f5
         assert_int_equal(val, 1);
ced1f5
     } else {
ced1f5
-        assert_int_equal(val, 2);
ced1f5
+        if (name2 == NULL || *name2 == '\0') {
ced1f5
+            assert_int_equal(val, 2);
ced1f5
+        } else {
ced1f5
+            assert_int_equal(val, 3);
ced1f5
+        }
ced1f5
 
ced1f5
         SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
ced1f5
         assert_int_equal(val, SSS_PAM_DOMAIN_NAME);
ced1f5
@@ -801,6 +895,33 @@ static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
ced1f5
     assert_string_equal(body + rp, TEST_KEY_ID);
ced1f5
     rp += sizeof(TEST_KEY_ID);
ced1f5
 
ced1f5
+    if (name2 != NULL && *name2 != '\0') {
ced1f5
+        SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
ced1f5
+        assert_int_equal(val, type);
ced1f5
+
ced1f5
+        SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
ced1f5
+        assert_int_equal(val, (strlen(name) + 1
ced1f5
+                                    + sizeof(TEST_TOKEN_NAME)
ced1f5
+                                    + sizeof(TEST_MODULE_NAME)
ced1f5
+                                    + sizeof(TEST2_KEY_ID)));
ced1f5
+
ced1f5
+        assert_int_equal(*(body + rp + strlen(name)), 0);
ced1f5
+        assert_string_equal(body + rp, name);
ced1f5
+        rp += strlen(name) + 1;
ced1f5
+
ced1f5
+        assert_int_equal(*(body + rp + sizeof(TEST_TOKEN_NAME) - 1), 0);
ced1f5
+        assert_string_equal(body + rp, TEST_TOKEN_NAME);
ced1f5
+        rp += sizeof(TEST_TOKEN_NAME);
ced1f5
+
ced1f5
+        assert_int_equal(*(body + rp + sizeof(TEST_MODULE_NAME) - 1), 0);
ced1f5
+        assert_string_equal(body + rp, TEST_MODULE_NAME);
ced1f5
+        rp += sizeof(TEST_MODULE_NAME);
ced1f5
+
ced1f5
+        assert_int_equal(*(body + rp + sizeof(TEST2_KEY_ID) - 1), 0);
ced1f5
+        assert_string_equal(body + rp, TEST2_KEY_ID);
ced1f5
+        rp += sizeof(TEST2_KEY_ID);
ced1f5
+    }
ced1f5
+
ced1f5
     assert_int_equal(rp, blen);
ced1f5
 
ced1f5
     return EOK;
ced1f5
@@ -809,7 +930,8 @@ static int test_pam_cert_check_ex(uint32_t status, uint8_t *body, size_t blen,
ced1f5
 static int test_pam_cert_check(uint32_t status, uint8_t *body, size_t blen)
ced1f5
 {
ced1f5
     return test_pam_cert_check_ex(status, body, blen,
ced1f5
-                                  SSS_PAM_CERT_INFO, "pamuser@"TEST_DOM_NAME);
ced1f5
+                                  SSS_PAM_CERT_INFO, "pamuser@"TEST_DOM_NAME,
ced1f5
+                                  NULL);
ced1f5
 }
ced1f5
 
ced1f5
 static int test_pam_cert_check_with_hint(uint32_t status, uint8_t *body,
ced1f5
@@ -817,14 +939,22 @@ static int test_pam_cert_check_with_hint(uint32_t status, uint8_t *body,
ced1f5
 {
ced1f5
     return test_pam_cert_check_ex(status, body, blen,
ced1f5
                                   SSS_PAM_CERT_INFO_WITH_HINT,
ced1f5
-                                  "pamuser@"TEST_DOM_NAME);
ced1f5
+                                  "pamuser@"TEST_DOM_NAME, NULL);
ced1f5
 }
ced1f5
 
ced1f5
 static int test_pam_cert_check_with_hint_no_user(uint32_t status, uint8_t *body,
ced1f5
                                                  size_t blen)
ced1f5
 {
ced1f5
     return test_pam_cert_check_ex(status, body, blen,
ced1f5
-                                  SSS_PAM_CERT_INFO_WITH_HINT, "");
ced1f5
+                                  SSS_PAM_CERT_INFO_WITH_HINT, "", NULL);
ced1f5
+}
ced1f5
+
ced1f5
+static int test_pam_cert_check_2certs(uint32_t status, uint8_t *body,
ced1f5
+                                      size_t blen)
ced1f5
+{
ced1f5
+    return test_pam_cert_check_ex(status, body, blen,
ced1f5
+                                  SSS_PAM_CERT_INFO, "pamuser@"TEST_DOM_NAME,
ced1f5
+                                  "pamuser@"TEST_DOM_NAME);
ced1f5
 }
ced1f5
 
ced1f5
 static int test_pam_offline_chauthtok_check(uint32_t status,
ced1f5
@@ -1737,6 +1867,33 @@ static int test_lookup_by_cert_cb(void *pvt)
ced1f5
 
ced1f5
     return EOK;
ced1f5
 }
ced1f5
+static int test_lookup_by_cert_cb_2nd_cert_same_user(void *pvt)
ced1f5
+{
ced1f5
+    int ret;
ced1f5
+    struct sysdb_attrs *attrs;
ced1f5
+    unsigned char *der = NULL;
ced1f5
+    size_t der_size;
ced1f5
+
ced1f5
+    test_lookup_by_cert_cb(pvt);
ced1f5
+
ced1f5
+    attrs = sysdb_new_attrs(pam_test_ctx);
ced1f5
+    assert_non_null(attrs);
ced1f5
+
ced1f5
+    der = sss_base64_decode(pam_test_ctx, TEST_TOKEN_2ND_CERT, &der_size);
ced1f5
+    assert_non_null(der);
ced1f5
+
ced1f5
+    ret = sysdb_attrs_add_mem(attrs, SYSDB_USER_MAPPED_CERT, der, der_size);
ced1f5
+    talloc_free(der);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    ret = sysdb_set_user_attr(pam_test_ctx->tctx->dom,
ced1f5
+                              pam_test_ctx->pam_user_fqdn,
ced1f5
+                              attrs,
ced1f5
+                              LDB_FLAG_MOD_ADD);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    return EOK;
ced1f5
+}
ced1f5
 
ced1f5
 static int test_lookup_by_cert_double_cb(void *pvt)
ced1f5
 {
ced1f5
@@ -2094,6 +2251,51 @@ void test_pam_cert_auth_double_cert(void **state)
ced1f5
     assert_int_equal(ret, EOK);
ced1f5
 }
ced1f5
 
ced1f5
+void test_pam_cert_preauth_2certs_one_mapping(void **state)
ced1f5
+{
ced1f5
+    int ret;
ced1f5
+
ced1f5
+    set_cert_auth_param(pam_test_ctx->pctx, NSS_DB_2CERTS);
ced1f5
+
ced1f5
+    mock_input_pam_cert(pam_test_ctx, "pamuser", NULL, NULL,
ced1f5
+                        test_lookup_by_cert_cb, TEST_TOKEN_CERT, false);
ced1f5
+
ced1f5
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
ced1f5
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
ced1f5
+
ced1f5
+    set_cmd_cb(test_pam_cert_check);
ced1f5
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
ced1f5
+                          pam_test_ctx->pam_cmds);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    /* Wait until the test finishes with EOK */
ced1f5
+    ret = test_ev_loop(pam_test_ctx->tctx);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+}
ced1f5
+
ced1f5
+void test_pam_cert_preauth_2certs_two_mappings(void **state)
ced1f5
+{
ced1f5
+    int ret;
ced1f5
+
ced1f5
+    set_cert_auth_param(pam_test_ctx->pctx, NSS_DB_2CERTS);
ced1f5
+
ced1f5
+    mock_input_pam_cert(pam_test_ctx, "pamuser", NULL, NULL,
ced1f5
+                        test_lookup_by_cert_cb_2nd_cert_same_user,
ced1f5
+                        TEST_TOKEN_CERT, false);
ced1f5
+
ced1f5
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
ced1f5
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
ced1f5
+
ced1f5
+    set_cmd_cb(test_pam_cert_check_2certs);
ced1f5
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
ced1f5
+                          pam_test_ctx->pam_cmds);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    /* Wait until the test finishes with EOK */
ced1f5
+    ret = test_ev_loop(pam_test_ctx->tctx);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+}
ced1f5
+
ced1f5
 void test_filter_response(void **state)
ced1f5
 {
ced1f5
     int ret;
ced1f5
@@ -2523,6 +2725,10 @@ int main(int argc, const char *argv[])
ced1f5
                                         pam_test_teardown),
ced1f5
         cmocka_unit_test_setup_teardown(test_pam_cert_auth_double_cert,
ced1f5
                                         pam_test_setup, pam_test_teardown),
ced1f5
+        cmocka_unit_test_setup_teardown(test_pam_cert_preauth_2certs_one_mapping,
ced1f5
+                                        pam_test_setup, pam_test_teardown),
ced1f5
+        cmocka_unit_test_setup_teardown(test_pam_cert_preauth_2certs_two_mappings,
ced1f5
+                                        pam_test_setup, pam_test_teardown),
ced1f5
 #endif /* HAVE_NSS */
ced1f5
 
ced1f5
         cmocka_unit_test_setup_teardown(test_filter_response,
ced1f5
diff --git a/src/tests/whitespace_test b/src/tests/whitespace_test
ced1f5
index 799e35358b1d5ae4b10c4405068fb507cb234b6f..f055ed4c255db4001194844f45a9df7cda774b38 100755
ced1f5
--- a/src/tests/whitespace_test
ced1f5
+++ b/src/tests/whitespace_test
ced1f5
@@ -39,7 +39,7 @@ fi
ced1f5
 declare found_file=false
ced1f5
 while read file; do
ced1f5
     [[ $file == "src/config/testconfigs/noparse.api.conf" ]] && continue
ced1f5
-    [[ $file =~ ^src/tests/cmocka/p11_nssdb/.*db ]] && continue
ced1f5
+    [[ $file =~ ^src/tests/cmocka/p11_nssdb.*/.*db ]] && continue
ced1f5
     test `tail -c 1 $ABS_TOP_SRCDIR/$file` && \
ced1f5
         echo "Missing new line at the eof: $file" && \
ced1f5
         found_file=true
ced1f5
-- 
ced1f5
2.13.6
ced1f5