Blame SOURCES/0038-pam_sss-refactoring-use-struct-cert_auth_info.patch

ced1f5
From cee84ed12721092bc40bc02dc66ce3efbb2bac74 Mon Sep 17 00:00:00 2001
ced1f5
From: Sumit Bose <sbose@redhat.com>
ced1f5
Date: Mon, 16 Oct 2017 14:13:10 +0200
ced1f5
Subject: [PATCH 38/46] pam_sss: refactoring, use struct cert_auth_info
ced1f5
MIME-Version: 1.0
ced1f5
Content-Type: text/plain; charset=UTF-8
ced1f5
Content-Transfer-Encoding: 8bit
ced1f5
ced1f5
Similar as in the PAM responder this patch replaces the individual
ced1f5
certificate authentication related attributes by a struct which can be
ced1f5
used as a list. With the pam_sss can handle multiple SSS_PAM_CERT_INFO
ced1f5
message and place the data in individual list items.
ced1f5
ced1f5
If multiple certificates are returned before prompting for the PIN a
ced1f5
dialog to select a certificate is shown to the users. If available a GDM
ced1f5
PAM extension is used to let the user choose from a list. All coded
ced1f5
needed at runtime to check if the extension is available and handle the
ced1f5
data is provided by GDM as macros. This means that there are no
ced1f5
additional run-time requirements.
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 122830e67472390b41edc73f0cfcd5c5705b726b)
ced1f5
---
ced1f5
 contrib/sssd.spec.in         |   9 +
ced1f5
 src/external/pam.m4          |  12 ++
ced1f5
 src/sss_client/pam_message.h |   8 +-
ced1f5
 src/sss_client/pam_sss.c     | 439 ++++++++++++++++++++++++++++++++++---------
ced1f5
 4 files changed, 370 insertions(+), 98 deletions(-)
ced1f5
ced1f5
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
ced1f5
index 1ee64d5a2a64635984260fceced779f4804e8b31..d9323bf1a2d84f4219f8ab11886e5ce87b401c15 100644
ced1f5
--- a/contrib/sssd.spec.in
ced1f5
+++ b/contrib/sssd.spec.in
ced1f5
@@ -121,6 +121,12 @@
ced1f5
     %global with_kcm_option --without-kcm
ced1f5
 %endif
ced1f5
 
ced1f5
+%if (0%{?fedora} >= 27 || (0%{?rhel} >= 7 && 0%{?rhel7_minor} > 4))
ced1f5
+    %global with_gdm_pam_extensions 1
ced1f5
+%else
ced1f5
+    %global with_gdm_pam_extensions 0
ced1f5
+%endif
ced1f5
+
ced1f5
 Name: @PACKAGE_NAME@
ced1f5
 Version: @PACKAGE_VERSION@
ced1f5
 Release: 0@PRERELEASE_VERSION@%{?dist}
ced1f5
@@ -233,6 +239,9 @@ BuildRequires: libuuid-devel
ced1f5
 BuildRequires: jansson-devel
ced1f5
 BuildRequires: libcurl-devel
ced1f5
 %endif
ced1f5
+%if (0%{?with_gdm_pam_extensions} == 1)
ced1f5
+BuildRequires: gdm-devel
ced1f5
+%endif
ced1f5
 
ced1f5
 %description
ced1f5
 Provides a set of daemons to manage access to remote directories and
ced1f5
diff --git a/src/external/pam.m4 b/src/external/pam.m4
ced1f5
index 4776b6ae338409f0a2729dfc4cf5962463a40dfd..0dc7f19d0df6a4588cf893ecff6e518111462433 100644
ced1f5
--- a/src/external/pam.m4
ced1f5
+++ b/src/external/pam.m4
ced1f5
@@ -27,3 +27,15 @@ AC_CHECK_FUNCS(pam_modutil_getlogin pam_vsyslog)
ced1f5
 
ced1f5
 dnl restore LIBS
ced1f5
 LIBS="$save_LIBS"
ced1f5
+
ced1f5
+PKG_CHECK_MODULES([GDM_PAM_EXTENSIONS], [gdm-pam-extensions],
ced1f5
+                  [found_gdm_pam_extensions=yes],
ced1f5
+                  [AC_MSG_NOTICE([gdm-pam-extensions were not found. gdm support
ced1f5
+for multiple certificates will not be build.
ced1f5
+])])
ced1f5
+
ced1f5
+AC_SUBST(GDM_PAM_EXTENSIONS_CFLAGS)
ced1f5
+
ced1f5
+AS_IF([test x"$found_gdm_pam_extensions" = xyes],
ced1f5
+      [AC_DEFINE_UNQUOTED(HAVE_GDM_PAM_EXTENSIONS, 1,
ced1f5
+                          [Build with gdm-pam-extensions support])])
ced1f5
diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h
ced1f5
index f215392f6879f01a0ca12abc8807bac5fc1f1cbb..11526a80a767ff5602b194d14765ff261e8f9707 100644
ced1f5
--- a/src/sss_client/pam_message.h
ced1f5
+++ b/src/sss_client/pam_message.h
ced1f5
@@ -29,6 +29,8 @@
ced1f5
 
ced1f5
 #include "sss_client/sss_cli.h"
ced1f5
 
ced1f5
+struct cert_auth_info;
ced1f5
+
ced1f5
 struct pam_items {
ced1f5
     const char *pam_service;
ced1f5
     const char *pam_user;
ced1f5
@@ -59,11 +61,9 @@ struct pam_items {
ced1f5
     char *first_factor;
ced1f5
     bool password_prompting;
ced1f5
 
ced1f5
-    char *cert_user;
ced1f5
-    char *token_name;
ced1f5
-    char *module_name;
ced1f5
-    char *key_id;
ced1f5
     bool user_name_hint;
ced1f5
+    struct cert_auth_info *cert_list;
ced1f5
+    struct cert_auth_info *selected_cert;
ced1f5
 };
ced1f5
 
ced1f5
 int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer);
ced1f5
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
ced1f5
index 303809b9ea05b5a8709c05ae230d5f289b57de31..c147d4b3d76443d69e27eb2da042f8eebd1ae6ab 100644
ced1f5
--- a/src/sss_client/pam_sss.c
ced1f5
+++ b/src/sss_client/pam_sss.c
ced1f5
@@ -36,6 +36,10 @@
ced1f5
 #include <security/pam_modules.h>
ced1f5
 #include <security/pam_appl.h>
ced1f5
 
ced1f5
+#ifdef HAVE_GDM_PAM_EXTENSIONS
ced1f5
+#include <gdm/gdm-pam-extensions.h>
ced1f5
+#endif
ced1f5
+
ced1f5
 #include "sss_pam_compat.h"
ced1f5
 #include "sss_pam_macros.h"
ced1f5
 
ced1f5
@@ -43,6 +47,7 @@
ced1f5
 #include "pam_message.h"
ced1f5
 #include "util/atomic_io.h"
ced1f5
 #include "util/authtok-utils.h"
ced1f5
+#include "util/dlinklist.h"
ced1f5
 
ced1f5
 #include <libintl.h>
ced1f5
 #define _(STRING) dgettext (PACKAGE, STRING)
ced1f5
@@ -118,6 +123,40 @@ static void close_fd(pam_handle_t *pamh, void *ptr, int err)
ced1f5
     sss_pam_close_fd();
ced1f5
 }
ced1f5
 
ced1f5
+struct cert_auth_info {
ced1f5
+    char *cert_user;
ced1f5
+    char *cert;
ced1f5
+    char *token_name;
ced1f5
+    char *module_name;
ced1f5
+    char *key_id;
ced1f5
+    struct cert_auth_info *prev;
ced1f5
+    struct cert_auth_info *next;
ced1f5
+};
ced1f5
+
ced1f5
+static void free_cai(struct cert_auth_info *cai)
ced1f5
+{
ced1f5
+    if (cai != NULL) {
ced1f5
+        free(cai->cert_user);
ced1f5
+        free(cai->cert);
ced1f5
+        free(cai->token_name);
ced1f5
+        free(cai->key_id);
ced1f5
+        free(cai);
ced1f5
+    }
ced1f5
+}
ced1f5
+
ced1f5
+static void free_cert_list(struct cert_auth_info *list)
ced1f5
+{
ced1f5
+    struct cert_auth_info *cai;
ced1f5
+    struct cert_auth_info *cai_next;
ced1f5
+
ced1f5
+    if (list != NULL) {
ced1f5
+        DLIST_FOR_EACH_SAFE(cai, cai_next, list) {
ced1f5
+            DLIST_REMOVE(list, cai);
ced1f5
+            free_cai(cai);
ced1f5
+        }
ced1f5
+    }
ced1f5
+}
ced1f5
+
ced1f5
 static void overwrite_and_free_authtoks(struct pam_items *pi)
ced1f5
 {
ced1f5
     if (pi->pam_authtok != NULL) {
ced1f5
@@ -158,17 +197,9 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
ced1f5
     free(pi->otp_challenge);
ced1f5
     pi->otp_challenge = NULL;
ced1f5
 
ced1f5
-    free(pi->cert_user);
ced1f5
-    pi->cert_user = NULL;
ced1f5
-
ced1f5
-    free(pi->token_name);
ced1f5
-    pi->token_name = NULL;
ced1f5
-
ced1f5
-    free(pi->module_name);
ced1f5
-    pi->module_name = NULL;
ced1f5
-
ced1f5
-    free(pi->key_id);
ced1f5
-    pi->key_id = NULL;
ced1f5
+    free_cert_list(pi->cert_list);
ced1f5
+    pi->cert_list = NULL;
ced1f5
+    pi->selected_cert = NULL;
ced1f5
 }
ced1f5
 
ced1f5
 static int null_strcmp(const char *s1, const char *s2) {
ced1f5
@@ -821,6 +852,90 @@ static int eval_user_info_response(pam_handle_t *pamh, size_t buflen,
ced1f5
     return ret;
ced1f5
 }
ced1f5
 
ced1f5
+static int parse_cert_info(struct pam_items *pi, uint8_t *buf, size_t len,
ced1f5
+                           size_t *p, const char **cert_user)
ced1f5
+{
ced1f5
+    struct cert_auth_info *cai = NULL;
ced1f5
+    size_t offset;
ced1f5
+    int ret;
ced1f5
+
ced1f5
+    if (buf[*p + (len - 1)] != '\0') {
ced1f5
+        D(("cert info does not end with \\0."));
ced1f5
+        return EINVAL;
ced1f5
+    }
ced1f5
+
ced1f5
+    cai = calloc(1, sizeof(struct cert_auth_info));
ced1f5
+    if (cai == NULL) {
ced1f5
+        return ENOMEM;
ced1f5
+    }
ced1f5
+
ced1f5
+    cai->cert_user = strdup((char *) &buf[*p]);
ced1f5
+    if (cai->cert_user == NULL) {
ced1f5
+        D(("strdup failed"));
ced1f5
+        ret = ENOMEM;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+    if (cert_user != NULL) {
ced1f5
+        *cert_user = cai->cert_user;
ced1f5
+    }
ced1f5
+
ced1f5
+    offset = strlen(cai->cert_user) + 1;
ced1f5
+    if (offset >= len) {
ced1f5
+        D(("Cert message size mismatch"));
ced1f5
+        ret = EINVAL;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    cai->token_name = strdup((char *) &buf[*p + offset]);
ced1f5
+    if (cai->token_name == NULL) {
ced1f5
+        D(("strdup failed"));
ced1f5
+        ret = ENOMEM;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    offset += strlen(cai->token_name) + 1;
ced1f5
+    if (offset >= len) {
ced1f5
+        D(("Cert message size mismatch"));
ced1f5
+        ret = EINVAL;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    cai->module_name = strdup((char *) &buf[*p + offset]);
ced1f5
+    if (cai->module_name == NULL) {
ced1f5
+        D(("strdup failed"));
ced1f5
+        ret = ENOMEM;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    offset += strlen(cai->module_name) + 1;
ced1f5
+    if (offset >= len) {
ced1f5
+        D(("Cert message size mismatch"));
ced1f5
+        ret = EINVAL;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    cai->key_id = strdup((char *) &buf[*p + offset]);
ced1f5
+    if (cai->key_id == NULL) {
ced1f5
+        D(("strdup failed"));
ced1f5
+        ret = ENOMEM;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s]",
ced1f5
+       cai->cert_user, cai->token_name, cai->module_name,
ced1f5
+       cai->key_id));
ced1f5
+
ced1f5
+    DLIST_ADD(pi->cert_list, cai);
ced1f5
+    ret = 0;
ced1f5
+
ced1f5
+done:
ced1f5
+    if (ret != 0) {
ced1f5
+        free_cai(cai);
ced1f5
+    }
ced1f5
+
ced1f5
+    return ret;
ced1f5
+}
ced1f5
+
ced1f5
 static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
ced1f5
                          struct pam_items *pi)
ced1f5
 {
ced1f5
@@ -832,6 +947,7 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
ced1f5
     int32_t len;
ced1f5
     int32_t pam_status;
ced1f5
     size_t offset;
ced1f5
+    const char *cert_user;
ced1f5
 
ced1f5
     if (buflen < (2*sizeof(int32_t))) {
ced1f5
         D(("response buffer is too small"));
ced1f5
@@ -988,27 +1104,21 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
ced1f5
                     break;
ced1f5
                 }
ced1f5
 
ced1f5
-                free(pi->cert_user);
ced1f5
-                pi->cert_user = strdup((char *) &buf[p]);
ced1f5
-                if (pi->cert_user == NULL) {
ced1f5
-                    D(("strdup failed"));
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-
ced1f5
-                if (type == SSS_PAM_CERT_INFO && *pi->cert_user == '\0') {
ced1f5
-                    D(("Invalid CERT message"));
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-
ced1f5
                 if (type == SSS_PAM_CERT_INFO_WITH_HINT) {
ced1f5
                     pi->user_name_hint = true;
ced1f5
                 } else {
ced1f5
                     pi->user_name_hint = false;
ced1f5
                 }
ced1f5
 
ced1f5
+                ret = parse_cert_info(pi, buf, len, &p, &cert_user);
ced1f5
+                if (ret != 0) {
ced1f5
+                    D(("Failed to parse cert info"));
ced1f5
+                    break;
ced1f5
+                }
ced1f5
+
ced1f5
                 if ((pi->pam_user == NULL || *(pi->pam_user) == '\0')
ced1f5
-                        && *pi->cert_user != '\0') {
ced1f5
-                    ret = pam_set_item(pamh, PAM_USER, pi->cert_user);
ced1f5
+                        && *cert_user != '\0') {
ced1f5
+                    ret = pam_set_item(pamh, PAM_USER, cert_user);
ced1f5
                     if (ret != PAM_SUCCESS) {
ced1f5
                         D(("Failed to set PAM_USER during "
ced1f5
                            "Smartcard authentication [%s]",
ced1f5
@@ -1027,59 +1137,6 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
ced1f5
 
ced1f5
                     pi->pam_user_size = strlen(pi->pam_user) + 1;
ced1f5
                 }
ced1f5
-
ced1f5
-                offset = strlen(pi->cert_user) + 1;
ced1f5
-                if (offset >= len) {
ced1f5
-                    D(("Cert message size mismatch"));
ced1f5
-                    free(pi->cert_user);
ced1f5
-                    pi->cert_user = NULL;
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-                free(pi->token_name);
ced1f5
-                pi->token_name = strdup((char *) &buf[p + offset]);
ced1f5
-                if (pi->token_name == NULL) {
ced1f5
-                    D(("strdup failed"));
ced1f5
-                    free(pi->cert_user);
ced1f5
-                    pi->cert_user = NULL;
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-
ced1f5
-                offset += strlen(pi->token_name) + 1;
ced1f5
-                if (offset >= len) {
ced1f5
-                    D(("Cert message size mismatch"));
ced1f5
-                    free(pi->cert_user);
ced1f5
-                    pi->cert_user = NULL;
ced1f5
-                    free(pi->token_name);
ced1f5
-                    pi->token_name = NULL;
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-                free(pi->module_name);
ced1f5
-                pi->module_name = strdup((char *) &buf[p + offset]);
ced1f5
-                if (pi->module_name == NULL) {
ced1f5
-                    D(("strdup failed"));
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-
ced1f5
-                offset += strlen(pi->module_name) + 1;
ced1f5
-                if (offset >= len) {
ced1f5
-                    D(("Cert message size mismatch"));
ced1f5
-                    free(pi->cert_user);
ced1f5
-                    pi->cert_user = NULL;
ced1f5
-                    free(pi->token_name);
ced1f5
-                    pi->token_name = NULL;
ced1f5
-                    free(pi->module_name);
ced1f5
-                    pi->module_name = NULL;
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-                free(pi->key_id);
ced1f5
-                pi->key_id = strdup((char *) &buf[p + offset]);
ced1f5
-                if (pi->key_id == NULL) {
ced1f5
-                    D(("strdup failed"));
ced1f5
-                    break;
ced1f5
-                }
ced1f5
-                D(("cert user: [%s] token name: [%s] module: [%s] key id: [%s]",
ced1f5
-                    pi->cert_user, pi->token_name, pi->module_name,
ced1f5
-                    pi->key_id));
ced1f5
                 break;
ced1f5
             case SSS_PASSWORD_PROMPTING:
ced1f5
                 D(("Password prompting available."));
ced1f5
@@ -1175,10 +1232,8 @@ static int get_pam_items(pam_handle_t *pamh, uint32_t flags,
ced1f5
     pi->otp_challenge = NULL;
ced1f5
     pi->password_prompting = false;
ced1f5
 
ced1f5
-    pi->cert_user = NULL;
ced1f5
-    pi->token_name = NULL;
ced1f5
-    pi->module_name = NULL;
ced1f5
-    pi->key_id = NULL;
ced1f5
+    pi->cert_list = NULL;
ced1f5
+    pi->selected_cert = NULL;
ced1f5
 
ced1f5
     return PAM_SUCCESS;
ced1f5
 }
ced1f5
@@ -1484,6 +1539,184 @@ done:
ced1f5
 
ced1f5
 #define SC_PROMPT_FMT "PIN for %s"
ced1f5
 
ced1f5
+#ifndef discard_const
ced1f5
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
ced1f5
+#endif
ced1f5
+
ced1f5
+#define CERT_SEL_PROMPT_FMT "Certificate: %s"
ced1f5
+#define SEL_TITLE discard_const("Please select a certificate")
ced1f5
+
ced1f5
+static int prompt_multi_cert_gdm(pam_handle_t *pamh, struct pam_items *pi)
ced1f5
+{
ced1f5
+#ifdef HAVE_GDM_PAM_EXTENSIONS
ced1f5
+    int ret;
ced1f5
+    size_t cert_count = 0;
ced1f5
+    size_t c;
ced1f5
+    const struct pam_conv *conv;
ced1f5
+    struct cert_auth_info *cai;
ced1f5
+    GdmPamExtensionChoiceListRequest *request = NULL;
ced1f5
+    GdmPamExtensionChoiceListResponse *response = NULL;
ced1f5
+    struct pam_message prompt_message;
ced1f5
+    const struct pam_message *prompt_messages[1];
ced1f5
+    struct pam_response *reply = NULL;
ced1f5
+    char *prompt;
ced1f5
+
ced1f5
+    if (!GDM_PAM_EXTENSION_SUPPORTED(GDM_PAM_EXTENSION_CHOICE_LIST)) {
ced1f5
+        return ENOTSUP;
ced1f5
+    }
ced1f5
+
ced1f5
+    if (pi->cert_list == NULL) {
ced1f5
+        return EINVAL;
ced1f5
+    }
ced1f5
+
ced1f5
+    DLIST_FOR_EACH(cai, pi->cert_list) {
ced1f5
+        cert_count++;
ced1f5
+    }
ced1f5
+
ced1f5
+    ret = pam_get_item(pamh, PAM_CONV, (const void **)&conv;;
ced1f5
+    if (ret != PAM_SUCCESS) {
ced1f5
+        ret = EIO;
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+
ced1f5
+    request = calloc(1, GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_SIZE(cert_count));
ced1f5
+    if (request == NULL) {
ced1f5
+        ret = ENOMEM;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+    GDM_PAM_EXTENSION_CHOICE_LIST_REQUEST_INIT(request, SEL_TITLE, cert_count);
ced1f5
+
ced1f5
+    c = 0;
ced1f5
+    DLIST_FOR_EACH(cai, pi->cert_list) {
ced1f5
+        ret = asprintf(&prompt, CERT_SEL_PROMPT_FMT, cai->key_id);
ced1f5
+        if (ret == -1) {
ced1f5
+            ret = ENOMEM;
ced1f5
+            goto done;
ced1f5
+        }
ced1f5
+        request->list.items[c].key = cai->key_id;
ced1f5
+        request->list.items[c++].text = prompt;
ced1f5
+    }
ced1f5
+
ced1f5
+    GDM_PAM_EXTENSION_MESSAGE_TO_BINARY_PROMPT_MESSAGE(request,
ced1f5
+                                                       &prompt_message);
ced1f5
+    prompt_messages[0] = &prompt_message;
ced1f5
+
ced1f5
+    ret = conv->conv(1, prompt_messages, &reply, conv->appdata_ptr);
ced1f5
+    if (ret != PAM_SUCCESS) {
ced1f5
+        ret = EIO;
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    ret = EIO;
ced1f5
+    response = GDM_PAM_EXTENSION_REPLY_TO_CHOICE_LIST_RESPONSE(reply);
ced1f5
+    if (response->key == NULL) {
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    DLIST_FOR_EACH(cai, pi->cert_list) {
ced1f5
+        if (strcmp(response->key, cai->key_id) == 0) {
ced1f5
+            pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id);
ced1f5
+            pi->selected_cert = cai;
ced1f5
+            ret = 0;
ced1f5
+            break;
ced1f5
+        }
ced1f5
+    }
ced1f5
+
ced1f5
+done:
ced1f5
+    if (request != NULL) {
ced1f5
+        for (c = 0; c < cert_count; c++) {
ced1f5
+            free(discard_const(request->list.items[c++].text));
ced1f5
+        }
ced1f5
+        free(request);
ced1f5
+    }
ced1f5
+    free(response);
ced1f5
+
ced1f5
+    return ret;
ced1f5
+#else
ced1f5
+    return ENOTSUP;
ced1f5
+#endif
ced1f5
+}
ced1f5
+
ced1f5
+#define TEXT_CERT_SEL_PROMPT_FMT "%s[%zu] Certificate: %s\n"
ced1f5
+#define TEXT_SEL_TITLE discard_const("Please select a certificate by typing " \
ced1f5
+                                     "the corresponding number\n")
ced1f5
+static int prompt_multi_cert(pam_handle_t *pamh, struct pam_items *pi)
ced1f5
+{
ced1f5
+    int ret;
ced1f5
+    size_t cert_count = 0;
ced1f5
+    size_t tries = 0;
ced1f5
+    long int resp = -1;
ced1f5
+    struct cert_auth_info *cai;
ced1f5
+    char *prompt;
ced1f5
+    char *tmp;
ced1f5
+    char *answer;
ced1f5
+    char *ep;
ced1f5
+
ced1f5
+    /* First check if gdm extension is supported */
ced1f5
+    ret = prompt_multi_cert_gdm(pamh, pi);
ced1f5
+    if (ret != ENOTSUP) {
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+
ced1f5
+    if (pi->cert_list == NULL) {
ced1f5
+        return EINVAL;
ced1f5
+    }
ced1f5
+
ced1f5
+    prompt = strdup(TEXT_SEL_TITLE);
ced1f5
+    if (prompt == NULL) {
ced1f5
+        return ENOMEM;
ced1f5
+    }
ced1f5
+
ced1f5
+    DLIST_FOR_EACH(cai, pi->cert_list) {
ced1f5
+        cert_count++;
ced1f5
+        ret = asprintf(&tmp, TEXT_CERT_SEL_PROMPT_FMT, prompt, cert_count,
ced1f5
+                                                       cai->key_id);
ced1f5
+        free(prompt);
ced1f5
+        if (ret == -1) {
ced1f5
+            return ENOMEM;
ced1f5
+        }
ced1f5
+
ced1f5
+        prompt = tmp;
ced1f5
+    }
ced1f5
+
ced1f5
+    do {
ced1f5
+        ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_ON, prompt, NULL,
ced1f5
+                                  &answer);
ced1f5
+        if (ret != PAM_SUCCESS) {
ced1f5
+            D(("do_pam_conversation failed."));
ced1f5
+            break;
ced1f5
+        }
ced1f5
+
ced1f5
+        errno = 0;
ced1f5
+        resp = strtol(answer, &ep, 10);
ced1f5
+        if (errno == 0 && *ep == '\0' && resp > 0 && resp <= cert_count) {
ced1f5
+            /* do not free answer ealier because ep is pointing to it */
ced1f5
+            free(answer);
ced1f5
+            break;
ced1f5
+        }
ced1f5
+        free(answer);
ced1f5
+        resp = -1;
ced1f5
+    } while (++tries < 5);
ced1f5
+    free(prompt);
ced1f5
+
ced1f5
+    pi->selected_cert = NULL;
ced1f5
+    ret = ENOENT;
ced1f5
+    if (resp > 0 && resp <= cert_count) {
ced1f5
+        cert_count = 0;
ced1f5
+        DLIST_FOR_EACH(cai, pi->cert_list) {
ced1f5
+            cert_count++;
ced1f5
+            if (resp == cert_count) {
ced1f5
+                pam_info(pamh, "Certificate ‘%s’ selected", cai->key_id);
ced1f5
+                pi->selected_cert = cai;
ced1f5
+                ret = 0;
ced1f5
+                break;
ced1f5
+            }
ced1f5
+        }
ced1f5
+    }
ced1f5
+
ced1f5
+    return ret;
ced1f5
+}
ced1f5
+
ced1f5
 static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
ced1f5
 {
ced1f5
     int ret;
ced1f5
@@ -1495,19 +1728,20 @@ static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
ced1f5
     const struct pam_message *mesg[2] = { NULL, NULL };
ced1f5
     struct pam_message m[2] = { { 0 }, { 0 } };
ced1f5
     struct pam_response *resp = NULL;
ced1f5
+    struct cert_auth_info *cai = pi->selected_cert;
ced1f5
 
ced1f5
-    if (pi->token_name == NULL || *pi->token_name == '\0') {
ced1f5
+    if (cai == NULL || cai->token_name == NULL || *cai->token_name == '\0') {
ced1f5
         return EINVAL;
ced1f5
     }
ced1f5
 
ced1f5
-    size = sizeof(SC_PROMPT_FMT) + strlen(pi->token_name);
ced1f5
+    size = sizeof(SC_PROMPT_FMT) + strlen(cai->token_name);
ced1f5
     prompt = malloc(size);
ced1f5
     if (prompt == NULL) {
ced1f5
         D(("malloc failed."));
ced1f5
         return ENOMEM;
ced1f5
     }
ced1f5
 
ced1f5
-    ret = snprintf(prompt, size, SC_PROMPT_FMT, pi->token_name);
ced1f5
+    ret = snprintf(prompt, size, SC_PROMPT_FMT, cai->token_name);
ced1f5
     if (ret < 0 || ret >= size) {
ced1f5
         D(("snprintf failed."));
ced1f5
         free(prompt);
ced1f5
@@ -1604,9 +1838,9 @@ static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
ced1f5
         pi->pam_authtok_size=0;
ced1f5
     } else {
ced1f5
 
ced1f5
-        ret = sss_auth_pack_sc_blob(answer, 0, pi->token_name, 0,
ced1f5
-                                    pi->module_name, 0,
ced1f5
-                                    pi->key_id, 0,
ced1f5
+        ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0,
ced1f5
+                                    cai->module_name, 0,
ced1f5
+                                    cai->key_id, 0,
ced1f5
                                     NULL, 0, &needed_size);
ced1f5
         if (ret != EAGAIN) {
ced1f5
             D(("sss_auth_pack_sc_blob failed."));
ced1f5
@@ -1621,9 +1855,9 @@ static int prompt_sc_pin(pam_handle_t *pamh, struct pam_items *pi)
ced1f5
             goto done;
ced1f5
         }
ced1f5
 
ced1f5
-        ret = sss_auth_pack_sc_blob(answer, 0, pi->token_name, 0,
ced1f5
-                                    pi->module_name, 0,
ced1f5
-                                    pi->key_id, 0,
ced1f5
+        ret = sss_auth_pack_sc_blob(answer, 0, cai->token_name, 0,
ced1f5
+                                    cai->module_name, 0,
ced1f5
+                                    cai->key_id, 0,
ced1f5
                                     (uint8_t *) pi->pam_authtok, needed_size,
ced1f5
                                     &needed_size);
ced1f5
         if (ret != EOK) {
ced1f5
@@ -1786,7 +2020,17 @@ static int get_authtok_for_authentication(pam_handle_t *pamh,
ced1f5
                 ret = prompt_2fa(pamh, pi, _("First Factor: "),
ced1f5
                                  _("Second Factor: "));
ced1f5
             }
ced1f5
-        } else if (pi->token_name != NULL && *(pi->token_name) != '\0') {
ced1f5
+        } else if (pi->cert_list != NULL) {
ced1f5
+            if (pi->cert_list->next == NULL) {
ced1f5
+                /* Only one certificate */
ced1f5
+                pi->selected_cert = pi->cert_list;
ced1f5
+            } else {
ced1f5
+                ret = prompt_multi_cert(pamh, pi);
ced1f5
+                if (ret != 0) {
ced1f5
+                    D(("Failed to select certificate"));
ced1f5
+                    return PAM_AUTHTOK_ERR;
ced1f5
+                }
ced1f5
+            }
ced1f5
             ret = prompt_sc_pin(pamh, pi);
ced1f5
         } else {
ced1f5
             ret = prompt_password(pamh, pi, _("Password: "));
ced1f5
@@ -1905,14 +2149,21 @@ static int check_login_token_name(pam_handle_t *pamh, struct pam_items *pi,
ced1f5
     char *prompt = NULL;
ced1f5
     size_t size;
ced1f5
     char *answer = NULL;
ced1f5
+    /* TODO: check multiple cert case */
ced1f5
+    struct cert_auth_info *cai = pi->cert_list;
ced1f5
+
ced1f5
+    if (cai == NULL) {
ced1f5
+        D(("No certificate information available"));
ced1f5
+        return EINVAL;
ced1f5
+    }
ced1f5
 
ced1f5
     login_token_name = getenv("PKCS11_LOGIN_TOKEN_NAME");
ced1f5
     if (login_token_name == NULL) {
ced1f5
         return PAM_SUCCESS;
ced1f5
     }
ced1f5
 
ced1f5
-    while (pi->token_name == NULL
ced1f5
-            || strcmp(login_token_name, pi->token_name) != 0) {
ced1f5
+    while (cai->token_name == NULL
ced1f5
+               || strcmp(login_token_name, cai->token_name) != 0) {
ced1f5
         size = sizeof(SC_ENTER_FMT) + strlen(login_token_name);
ced1f5
         prompt = malloc(size);
ced1f5
         if (prompt == NULL) {
ced1f5
-- 
ced1f5
2.13.6
ced1f5