URL: https://github.com/SSSD/sssd/pull/792
Author: sumit-bose
 Title: #792: PAM: add initial prompting configuration
Action: synchronized

To pull the PR as Git branch:
git remote add ghsssd https://github.com/SSSD/sssd
git fetch ghsssd pull/792/head:pr792
git checkout pr792
From 8fe2527e4b8176bca7366d590045c4cd4de0bf2e Mon Sep 17 00:00:00 2001
From: Sumit Bose <sb...@redhat.com>
Date: Wed, 27 Mar 2019 09:02:27 +0100
Subject: [PATCH 1/5] pam: introduce prompt_config struct

prompt_config is the internal struct to control the prompting of
pam_sss. To make it easy to change internal details when more options
are added it should be opaque and only accessed by getters and setter.

Related to https://pagure.io/SSSD/sssd/issue/3264
---
 Makefile.am                            |  17 +
 src/sss_client/pam_sss_prompt_config.c | 547 +++++++++++++++++++++++++
 src/sss_client/sss_cli.h               |  29 ++
 src/tests/cmocka/test_prompt_config.c  | 215 ++++++++++
 4 files changed, 808 insertions(+)
 create mode 100644 src/sss_client/pam_sss_prompt_config.c
 create mode 100644 src/tests/cmocka/test_prompt_config.c

diff --git a/Makefile.am b/Makefile.am
index 5317516668..72866cdb0c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -241,6 +241,7 @@ if HAVE_CMOCKA
         test-negcache \
         negcache_2-tests \
         test-authtok \
+        test_prompt_config \
         sss_nss_idmap-tests \
         deskprofile_utils-tests \
         dyndns-tests \
@@ -2910,6 +2911,21 @@ test_authtok_LDADD = \
     libsss_debug.la \
     $(NULL)
 
+test_prompt_config_SOURCES = \
+    src/tests/cmocka/test_prompt_config.c \
+    src/sss_client/pam_sss_prompt_config.c \
+    $(NULL)
+test_prompt_config_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(POPT_CFLAGS) \
+    $(NULL)
+test_prompt_config_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(POPT_LIBS) \
+    libsss_debug.la \
+    $(TALLOC_LIBS) \
+    $(NULL)
+
 sss_nss_idmap_tests_SOURCES = \
     src/tests/cmocka/sss_nss_idmap-tests.c
 sss_nss_idmap_tests_CFLAGS = \
@@ -4089,6 +4105,7 @@ endif
 pamlib_LTLIBRARIES = pam_sss.la
 pam_sss_la_SOURCES = \
     src/sss_client/pam_sss.c \
+    src/sss_client/pam_sss_prompt_config.c \
     src/sss_client/pam_message.c \
     src/sss_client/common.c \
     src/sss_client/sss_cli.h \
diff --git a/src/sss_client/pam_sss_prompt_config.c b/src/sss_client/pam_sss_prompt_config.c
new file mode 100644
index 0000000000..35094b4068
--- /dev/null
+++ b/src/sss_client/pam_sss_prompt_config.c
@@ -0,0 +1,547 @@
+/*
+    Authors:
+        Sumit Bose <sb...@redhat.com>
+
+    Copyright (C) 2019 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU Lesser General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include <stdlib.h>
+#include <errno.h>
+
+#include "sss_cli.h"
+
+#include <libintl.h>
+#define _(STRING) dgettext (PACKAGE, STRING)
+
+struct prompt_config_password {
+    char *prompt;
+};
+
+struct prompt_config_2fa {
+    char *prompt_1st;
+    char *prompt_2nd;
+};
+
+struct prompt_config_2fa_single {
+    char *prompt;
+};
+
+struct prompt_config_sc_pin {
+    char *prompt; /* Currently not used */
+};
+
+struct prompt_config {
+    enum prompt_config_type type;
+    union {
+        struct prompt_config_password password;
+        struct prompt_config_2fa two_fa;
+        struct prompt_config_2fa_single two_fa_single;
+        struct prompt_config_sc_pin sc_pin;
+    } data;
+};
+
+enum prompt_config_type pc_get_type(struct prompt_config *pc)
+{
+    if (pc != NULL && pc->type > PC_TYPE_INVALID && pc->type < PC_TYPE_LAST) {
+        return pc->type;
+    }
+    return PC_TYPE_INVALID;
+}
+
+const char *pc_get_password_prompt(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_PASSWORD) {
+        return pc->data.password.prompt;
+    }
+    return NULL;
+}
+
+const char *pc_get_2fa_1st_prompt(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA) {
+        return pc->data.two_fa.prompt_1st;
+    }
+    return NULL;
+}
+
+const char *pc_get_2fa_2nd_prompt(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA) {
+        return pc->data.two_fa.prompt_2nd;
+    }
+    return NULL;
+}
+
+const char *pc_get_2fa_single_prompt(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA_SINGLE) {
+        return pc->data.two_fa_single.prompt;
+    }
+    return NULL;
+}
+
+static void pc_free_password(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_PASSWORD) {
+        free(pc->data.password.prompt);
+    }
+    return;
+}
+
+static void pc_free_2fa(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA) {
+        free(pc->data.two_fa.prompt_1st);
+        free(pc->data.two_fa.prompt_2nd);
+    }
+    return;
+}
+
+static void pc_free_2fa_single(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_2FA_SINGLE) {
+        free(pc->data.two_fa_single.prompt);
+    }
+    return;
+}
+
+static void pc_free_sc_pin(struct prompt_config *pc)
+{
+    if (pc != NULL && pc_get_type(pc) == PC_TYPE_SC_PIN) {
+        free(pc->data.sc_pin.prompt);
+    }
+    return;
+}
+
+void pc_list_free(struct prompt_config **pc_list)
+{
+    size_t c;
+
+    if (pc_list == NULL) {
+        return;
+    }
+
+    for (c = 0; pc_list[c] != NULL; c++) {
+        switch (pc_list[c]->type) {
+        case PC_TYPE_PASSWORD:
+            pc_free_password(pc_list[c]);
+            break;
+        case PC_TYPE_2FA:
+            pc_free_2fa(pc_list[c]);
+            break;
+        case PC_TYPE_2FA_SINGLE:
+            pc_free_2fa_single(pc_list[c]);
+            break;
+        case PC_TYPE_SC_PIN:
+            pc_free_sc_pin(pc_list[c]);
+            break;
+        default:
+            return;
+        }
+        free(pc_list[c]);
+    }
+    free(pc_list);
+}
+
+static errno_t pc_list_add_pc(struct prompt_config ***pc_list,
+                              struct prompt_config *pc)
+{
+    size_t c = 0;
+    struct prompt_config **pcl;
+
+    for (c = 0; *pc_list != NULL && (*pc_list)[c] != NULL; c++); /* just counting */
+
+    pcl = realloc(*pc_list, (c + 2) * sizeof(struct prompt_config *));
+    if (pcl == NULL) {
+        return ENOMEM;
+    }
+    pcl[c] = pc;
+    pcl[c + 1] = NULL;
+
+    *pc_list = pcl;
+
+    return EOK;
+}
+
+#define DEFAULT_PASSWORD_PROMPT _("Password: ")
+#define DEFAULT_2FA_SINGLE_PROMPT _("Password + Token value: ")
+#define DEFAULT_2FA_PROMPT_1ST _("First Factor: ")
+#define DEFAULT_2FA_PROMPT_2ND _("Second Factor: ")
+
+errno_t pc_list_add_password(struct prompt_config ***pc_list,
+                             const char *prompt)
+{
+    struct prompt_config *pc;
+    int ret;
+
+    if (pc_list == NULL) {
+        return EINVAL;
+    }
+
+    pc = calloc(1, sizeof(struct prompt_config));
+    if (pc == NULL) {
+        return ENOMEM;
+    }
+
+    pc->type = PC_TYPE_PASSWORD;
+    pc->data.password.prompt = strdup(prompt != NULL ? prompt
+                                                     : DEFAULT_PASSWORD_PROMPT);
+    if (pc->data.password.prompt == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = pc_list_add_pc(pc_list, pc);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        free(pc->data.password.prompt);
+        free(pc);
+    }
+
+    return ret;
+}
+
+errno_t pc_list_add_2fa(struct prompt_config ***pc_list,
+                        const char *prompt_1st, const char *prompt_2nd)
+{
+    struct prompt_config *pc;
+    int ret;
+
+    if (pc_list == NULL) {
+        return EINVAL;
+    }
+
+    pc = calloc(1, sizeof(struct prompt_config));
+    if (pc == NULL) {
+        return ENOMEM;
+    }
+
+    pc->type = PC_TYPE_2FA;
+    pc->data.two_fa.prompt_1st = strdup(prompt_1st != NULL ? prompt_1st
+                                                   : DEFAULT_2FA_PROMPT_1ST);
+    if (pc->data.two_fa.prompt_1st == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+    pc->data.two_fa.prompt_2nd = strdup(prompt_2nd != NULL ? prompt_2nd
+                                                   : DEFAULT_2FA_PROMPT_2ND);
+    if (pc->data.two_fa.prompt_2nd == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = pc_list_add_pc(pc_list, pc);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        free(pc->data.two_fa.prompt_1st);
+        free(pc->data.two_fa.prompt_2nd);
+        free(pc);
+    }
+
+    return ret;
+}
+
+errno_t pc_list_add_2fa_single(struct prompt_config ***pc_list,
+                               const char *prompt)
+{
+    struct prompt_config *pc;
+    int ret;
+
+    if (pc_list == NULL) {
+        return EINVAL;
+    }
+
+    pc = calloc(1, sizeof(struct prompt_config));
+    if (pc == NULL) {
+        return ENOMEM;
+    }
+
+    pc->type = PC_TYPE_2FA_SINGLE;
+    pc->data.two_fa_single.prompt = strdup(prompt != NULL ? prompt
+                                                   : DEFAULT_2FA_SINGLE_PROMPT);
+    if (pc->data.two_fa_single.prompt == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = pc_list_add_pc(pc_list, pc);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        free(pc->data.two_fa_single.prompt);
+        free(pc);
+    }
+
+    return ret;
+}
+
+errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
+                                       uint8_t **data)
+{
+    size_t c;
+    size_t l = 0;
+    uint8_t *d = NULL;
+    uint32_t uint32_val;
+    size_t rp;
+
+    if (pc_list == NULL || *pc_list == NULL) {
+        return ENOENT;
+    }
+
+    l += sizeof(uint32_t);
+    for (c = 0; pc_list[c] != NULL; c++) {
+        l += sizeof(uint32_t);
+        switch (pc_list[c]->type) {
+        case PC_TYPE_PASSWORD:
+            l += sizeof(uint32_t);
+            l += strlen(pc_list[c]->data.password.prompt);
+            break;
+        case PC_TYPE_2FA:
+            l += sizeof(uint32_t);
+            l += strlen(pc_list[c]->data.two_fa.prompt_1st);
+            l += sizeof(uint32_t);
+            l += strlen(pc_list[c]->data.two_fa.prompt_2nd);
+            break;
+        case PC_TYPE_2FA_SINGLE:
+            l += sizeof(uint32_t);
+            l += strlen(pc_list[c]->data.two_fa_single.prompt);
+            break;
+        case PC_TYPE_SC_PIN:
+            break;
+        default:
+            return EINVAL;
+        }
+    }
+
+    d = malloc(l * sizeof(uint8_t));
+    if (d == NULL) {
+        return ENOMEM;
+    }
+
+    rp = 0;
+    uint32_val = c;
+    SAFEALIGN_COPY_UINT32(&d[rp], &uint32_val, &rp);
+
+    for (c = 0; pc_list[c] != NULL; c++) {
+        uint32_val = pc_list[c]->type;
+        SAFEALIGN_COPY_UINT32(&d[rp], &uint32_val, &rp);
+
+        switch (pc_list[c]->type) {
+        case PC_TYPE_PASSWORD:
+            SAFEALIGN_SET_UINT32(&d[rp],
+                                 strlen(pc_list[c]->data.password.prompt), &rp);
+            safealign_memcpy(&d[rp], pc_list[c]->data.password.prompt,
+                             strlen(pc_list[c]->data.password.prompt), &rp);
+            break;
+        case PC_TYPE_2FA:
+            SAFEALIGN_SET_UINT32(&d[rp],
+                                 strlen(pc_list[c]->data.two_fa.prompt_1st),
+                                 &rp);
+            safealign_memcpy(&d[rp], pc_list[c]->data.two_fa.prompt_1st,
+                             strlen(pc_list[c]->data.two_fa.prompt_1st), &rp);
+            SAFEALIGN_SET_UINT32(&d[rp],
+                                 strlen(pc_list[c]->data.two_fa.prompt_2nd),
+                                 &rp);
+            safealign_memcpy(&d[rp], pc_list[c]->data.two_fa.prompt_2nd,
+                             strlen(pc_list[c]->data.two_fa.prompt_2nd), &rp);
+            break;
+        case PC_TYPE_2FA_SINGLE:
+            SAFEALIGN_SET_UINT32(&d[rp],
+                                 strlen(pc_list[c]->data.two_fa_single.prompt),
+                                 &rp);
+            safealign_memcpy(&d[rp], pc_list[c]->data.two_fa_single.prompt,
+                             strlen(pc_list[c]->data.two_fa_single.prompt),
+                             &rp);
+            break;
+        case PC_TYPE_SC_PIN:
+            break;
+        default:
+            free(d);
+            return EINVAL;
+        }
+    }
+
+    if (rp != l) {
+        free(d);
+        return EFAULT;
+    }
+
+    *data = d;
+    *len = l;
+
+    return EOK;
+}
+
+errno_t pc_list_from_response(int size, uint8_t *buf,
+                              struct prompt_config ***pc_list)
+{
+    int ret;
+    uint32_t count;
+    uint32_t type;
+    uint32_t l;
+    size_t rp;
+    size_t c;
+    struct prompt_config **pl = NULL;
+    char *str;
+    char *str2;
+
+    if (buf == NULL || size < 3 * sizeof(uint32_t)) {
+        return EINVAL;
+    }
+
+    rp = 0;
+    SAFEALIGN_COPY_UINT32_CHECK(&count, buf + rp, size, &rp);
+
+    for (c = 0; c < count; c++) {
+        /* Since we already know size < 3 * sizeof(uint32_t) this check should
+         * be safe and without over- or underflow. */
+        if (rp > size - sizeof(uint32_t)) {
+            ret = EINVAL;
+            goto done;
+        }
+        SAFEALIGN_COPY_UINT32(&type, buf + rp, &rp);
+
+        switch (type) {
+        case PC_TYPE_PASSWORD:
+            if (rp > size - sizeof(uint32_t)) {
+                ret = EINVAL;
+                goto done;
+            }
+            SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+            if (l > size || rp > size - l) {
+                ret = EINVAL;
+                goto done;
+            }
+            str = strndup((char *) buf + rp, l);
+            if (str == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            rp += l;
+
+            ret = pc_list_add_password(&pl, str);
+            free(str);
+            if (ret != EOK) {
+                goto done;
+            }
+            break;
+        case PC_TYPE_2FA:
+            if (rp > size - sizeof(uint32_t)) {
+                ret = EINVAL;
+                goto done;
+            }
+            SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+            if (l > size || rp > size - l) {
+                ret = EINVAL;
+                goto done;
+            }
+            str = strndup((char *) buf + rp, l);
+            if (str == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            rp += l;
+
+            if (rp > size - sizeof(uint32_t)) {
+                free(str);
+                ret = EINVAL;
+                goto done;
+            }
+            SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+            if (l > size || rp > size - l) {
+                free(str);
+                ret = EINVAL;
+                goto done;
+            }
+            str2 = strndup((char *) buf + rp, l);
+            if (str2 == NULL) {
+                free(str);
+                ret = ENOMEM;
+                goto done;
+            }
+            rp += l;
+
+            ret = pc_list_add_2fa(&pl, str, str2);
+            free(str);
+            free(str2);
+            if (ret != EOK) {
+                goto done;
+            }
+            break;
+        case PC_TYPE_2FA_SINGLE:
+            if (rp > size - sizeof(uint32_t)) {
+                ret = EINVAL;
+                goto done;
+            }
+            SAFEALIGN_COPY_UINT32(&l, buf + rp, &rp);
+
+            if (l > size || rp > size - l) {
+                ret = EINVAL;
+                goto done;
+            }
+            str = strndup((char *) buf + rp, l);
+            if (str == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+            rp += l;
+
+            ret = pc_list_add_2fa_single(&pl, str);
+            free(str);
+            if (ret != EOK) {
+                goto done;
+            }
+            break;
+        case PC_TYPE_SC_PIN:
+            break;
+        default:
+            ret = EINVAL;
+            goto done;
+        }
+    }
+
+    *pc_list = pl;
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        pc_list_free(pl);
+    }
+
+    return ret;
+}
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 31b4e50f7a..599cf842de 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -573,6 +573,35 @@ enum user_info_type {
  * @}
  */ /* end of group sss_pam_cli */
 
+
+enum prompt_config_type {
+    PC_TYPE_INVALID = 0,
+    PC_TYPE_PASSWORD,
+    PC_TYPE_2FA,
+    PC_TYPE_2FA_SINGLE,
+    PC_TYPE_SC_PIN,
+    PC_TYPE_LAST
+};
+
+struct prompt_config;
+
+enum prompt_config_type pc_get_type(struct prompt_config *pc);
+const char *pc_get_password_prompt(struct prompt_config *pc);
+const char *pc_get_2fa_1st_prompt(struct prompt_config *pc);
+const char *pc_get_2fa_2nd_prompt(struct prompt_config *pc);
+const char *pc_get_2fa_single_prompt(struct prompt_config *pc);
+void pc_list_free(struct prompt_config **pc_list);
+errno_t pc_list_add_password(struct prompt_config ***pc_list,
+                             const char *prompt);
+errno_t pc_list_add_2fa(struct prompt_config ***pc_list,
+                        const char *prompt_1st, const char *prompt_2nd);
+errno_t pc_list_add_2fa_single(struct prompt_config ***pc_list,
+                               const char *prompt);
+errno_t pam_get_response_prompt_config(struct prompt_config **pc_list, int *len,
+                                       uint8_t **data);
+errno_t pc_list_from_response(int size, uint8_t *buf,
+                              struct prompt_config ***pc_list);
+
 enum sss_netgr_rep_type {
     SSS_NETGR_REP_TRIPLE = 1,
     SSS_NETGR_REP_GROUP
diff --git a/src/tests/cmocka/test_prompt_config.c b/src/tests/cmocka/test_prompt_config.c
new file mode 100644
index 0000000000..0b761ae4c3
--- /dev/null
+++ b/src/tests/cmocka/test_prompt_config.c
@@ -0,0 +1,215 @@
+/*
+    SSSD
+
+    prompt config - Utilities tests
+
+    Authors:
+        Sumit bose <sb...@redhat.com>
+
+    Copyright (C) 2019 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+
+#include "sss_client/sss_cli.h"
+
+void test_pc_list_add_password(void **state)
+{
+    int ret;
+    struct prompt_config **pc_list = NULL;
+
+    ret = pc_list_add_password(&pc_list, "Hello");
+    assert_int_equal(ret, EOK);
+    assert_non_null(pc_list);
+    assert_non_null(pc_list[0]);
+    assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[0]));
+    assert_string_equal("Hello", pc_get_password_prompt(pc_list[0]));
+    assert_null(pc_list[1]);
+
+    ret = pc_list_add_password(&pc_list, "Hello2");
+    assert_int_equal(ret, EOK);
+    assert_non_null(pc_list);
+    assert_non_null(pc_list[0]);
+    assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[0]));
+    assert_string_equal("Hello", pc_get_password_prompt(pc_list[0]));
+    assert_non_null(pc_list[1]);
+    assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[1]));
+    assert_string_equal("Hello2", pc_get_password_prompt(pc_list[1]));
+    assert_null(pc_list[2]);
+
+    pc_list_free(pc_list);
+}
+
+void test_pc_list_add_2fa_single(void **state)
+{
+    int ret;
+    struct prompt_config **pc_list = NULL;
+
+    ret = pc_list_add_2fa_single(&pc_list, "Hello");
+    assert_int_equal(ret, EOK);
+    assert_non_null(pc_list);
+    assert_non_null(pc_list[0]);
+    assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[0]));
+    assert_string_equal("Hello", pc_get_2fa_single_prompt(pc_list[0]));
+    assert_null(pc_list[1]);
+
+    ret = pc_list_add_2fa_single(&pc_list, "Hello2");
+    assert_int_equal(ret, EOK);
+    assert_non_null(pc_list);
+    assert_non_null(pc_list[0]);
+    assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[0]));
+    assert_string_equal("Hello", pc_get_2fa_single_prompt(pc_list[0]));
+    assert_non_null(pc_list[1]);
+    assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[1]));
+    assert_string_equal("Hello2", pc_get_2fa_single_prompt(pc_list[1]));
+    assert_null(pc_list[2]);
+
+    pc_list_free(pc_list);
+}
+
+void test_pc_list_add_2fa(void **state)
+{
+    int ret;
+    struct prompt_config **pc_list = NULL;
+
+    ret = pc_list_add_2fa(&pc_list, "Hello", "Good Bye");
+    assert_int_equal(ret, EOK);
+    assert_non_null(pc_list);
+    assert_non_null(pc_list[0]);
+    assert_int_equal(PC_TYPE_2FA, pc_get_type(pc_list[0]));
+    assert_string_equal("Hello", pc_get_2fa_1st_prompt(pc_list[0]));
+    assert_string_equal("Good Bye", pc_get_2fa_2nd_prompt(pc_list[0]));
+    assert_null(pc_list[1]);
+
+    pc_list_free(pc_list);
+}
+
+void test_pam_get_response_prompt_config(void **state)
+{
+    int ret;
+    struct prompt_config **pc_list = NULL;
+    int len;
+    uint8_t *data;
+
+    ret = pc_list_add_password(&pc_list, "password");
+    assert_int_equal(ret, EOK);
+
+    ret = pc_list_add_2fa(&pc_list, "first", "second");
+    assert_int_equal(ret, EOK);
+
+    ret = pc_list_add_2fa_single(&pc_list, "single");
+    assert_int_equal(ret, EOK);
+
+    ret = pam_get_response_prompt_config(pc_list, &len, &data);
+    pc_list_free(pc_list);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(len, 57);
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    assert_memory_equal(data, "\3\0\0\0\1\0\0\0\10\0\0\0" "password\2\0\0\0\5\0\0\0" "first\6\0\0\0" "second\3\0\0\0\6\0\0\0" "single", len);
+#else
+    assert_memory_equal(data, "\0\0\0\3\0\0\0\1\0\0\0\10" "password\0\0\0\2\0\0\0\5" "first\0\0\0\6" "second\0\0\0\3\0\0\0\6" "single", len);
+#endif
+
+    free(data);
+}
+
+void test_pc_list_from_response(void **state)
+{
+    int ret;
+    struct prompt_config **pc_list = NULL;
+    int len;
+    uint8_t *data;
+
+    ret = pc_list_add_password(&pc_list, "password");
+    assert_int_equal(ret, EOK);
+
+    ret = pc_list_add_2fa(&pc_list, "first", "second");
+    assert_int_equal(ret, EOK);
+
+    ret = pc_list_add_2fa_single(&pc_list, "single");
+    assert_int_equal(ret, EOK);
+
+    ret = pam_get_response_prompt_config(pc_list, &len, &data);
+    pc_list_free(pc_list);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(len, 57);
+
+    pc_list = NULL;
+
+    ret = pc_list_from_response(len, data, &pc_list);
+    free(data);
+    assert_int_equal(ret, EOK);
+    assert_non_null(pc_list);
+
+    assert_non_null(pc_list[0]);
+    assert_int_equal(PC_TYPE_PASSWORD, pc_get_type(pc_list[0]));
+    assert_string_equal("password", pc_get_password_prompt(pc_list[0]));
+
+    assert_non_null(pc_list[1]);
+    assert_int_equal(PC_TYPE_2FA, pc_get_type(pc_list[1]));
+    assert_string_equal("first", pc_get_2fa_1st_prompt(pc_list[1]));
+    assert_string_equal("second", pc_get_2fa_2nd_prompt(pc_list[1]));
+
+    assert_non_null(pc_list[2]);
+    assert_int_equal(PC_TYPE_2FA_SINGLE, pc_get_type(pc_list[2]));
+    assert_string_equal("single", pc_get_2fa_single_prompt(pc_list[2]));
+
+    assert_null(pc_list[3]);
+
+    pc_list_free(pc_list);
+}
+
+int main(int argc, const char *argv[])
+{
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test(test_pc_list_add_password),
+        cmocka_unit_test(test_pc_list_add_2fa_single),
+        cmocka_unit_test(test_pc_list_add_2fa),
+        cmocka_unit_test(test_pam_get_response_prompt_config),
+        cmocka_unit_test(test_pc_list_from_response),
+    };
+
+    /* Set debug level to invalid value so we can decide if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}

From 8c45c826903db0924b61f19e0d7c658653088309 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sb...@redhat.com>
Date: Wed, 27 Mar 2019 09:04:53 +0100
Subject: [PATCH 2/5] authtok: add dedicated type for 2fa with single string

Currently the password type is used to send two-factor authentication
credentials entered in a single string to the backend, This is
unreliable and only works properly if password authentication is not
available for the user as well.

To support 2FA credentials in a single string better a new authtok type
is added.

Related to https://pagure.io/SSSD/sssd/issue/3264
---
 src/providers/krb5/krb5_auth.c          |  1 +
 src/providers/krb5/krb5_child.c         | 13 +++++++
 src/providers/krb5/krb5_child_handler.c |  4 +++
 src/responder/pam/pamsrv_cmd.c          |  1 +
 src/sss_client/sss_cli.h                |  3 ++
 src/tests/cmocka/test_authtok.c         | 45 +++++++++++++++++++++++++
 src/util/authtok.c                      | 42 +++++++++++++++++++++++
 src/util/authtok.h                      | 35 +++++++++++++++++++
 8 files changed, 144 insertions(+)

diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index d40d2afed5..9a92504349 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -495,6 +495,7 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
         case SSS_PAM_CHAUTHTOK:
             if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD
                     && authtok_type != SSS_AUTHTOK_TYPE_2FA
+                    && authtok_type != SSS_AUTHTOK_TYPE_2FA_SINGLE
                     && authtok_type != SSS_AUTHTOK_TYPE_SC_PIN
                     && authtok_type != SSS_AUTHTOK_TYPE_SC_KEYPAD) {
                 /* handle empty password gracefully */
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 7ad411914a..ae63b14d18 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -504,6 +504,15 @@ static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
             return ret;
         }
 
+        return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin);
+        break;
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+        ret = sss_authtok_get_2fa_single(auth_tok, &pwd, &len);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n");
+            return ret;
+        }
+
         return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin);
         break;
     case SSS_AUTHTOK_TYPE_2FA:
@@ -2108,6 +2117,7 @@ static errno_t tgt_req_child(struct krb5_req *kr)
     /* No password is needed for pre-auth or if we have 2FA or SC */
     if (kr->pd->cmd != SSS_PAM_PREAUTH
             && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA
+            && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA_SINGLE
             && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN
             && sss_authtok_get_type(kr->pd->authtok)
                                                 != SSS_AUTHTOK_TYPE_SC_KEYPAD) {
@@ -2366,6 +2376,9 @@ static errno_t unpack_authtok(struct sss_auth_token *tok,
     case SSS_AUTHTOK_TYPE_CCFILE:
         ret = sss_authtok_set_ccfile(tok, (char *)(buf + *p), 0);
         break;
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+        ret = sss_authtok_set_2fa_single(tok, (char *)(buf + *p), 0);
+        break;
     case SSS_AUTHTOK_TYPE_2FA:
     case SSS_AUTHTOK_TYPE_SC_PIN:
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
index 352ff980dd..b7fb54499e 100644
--- a/src/providers/krb5/krb5_child_handler.c
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -79,6 +79,10 @@ static errno_t pack_authtok(struct io_buffer *buf, size_t *rp,
         ret = sss_authtok_get_ccfile(tok, &data, &len);
         auth_token_length = len + 1;
         break;
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+        ret = sss_authtok_get_2fa_single(tok, &data, &len);
+        auth_token_length = len + 1;
+        break;
     case SSS_AUTHTOK_TYPE_2FA:
     case SSS_AUTHTOK_TYPE_SC_PIN:
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 3b4869ecea..6c04cb157d 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -160,6 +160,7 @@ static int extract_authtok_v2(struct sss_auth_token *tok,
         }
         break;
     case SSS_AUTHTOK_TYPE_2FA:
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
     case SSS_AUTHTOK_TYPE_SC_PIN:
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
         ret = sss_authtok_set(tok, auth_token_type,
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 599cf842de..477e4d5cce 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -340,6 +340,9 @@ enum sss_authtok_type {
                                           * Smart Card authentication is used
                                           * and that the PIN will be entered
                                           * at the card reader. */
+    SSS_AUTHTOK_TYPE_2FA_SINGLE = 0x0006, /**< Authentication token has two
+                                           * factors in a single string, it may
+                                           * or may no contain a trailing \\0 */
 };
 
 /**
diff --git a/src/tests/cmocka/test_authtok.c b/src/tests/cmocka/test_authtok.c
index 9422f96bcf..84e209783e 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -652,6 +652,49 @@ void test_sss_authtok_sc_pin(void **state)
     assert_int_equal(ret, EFAULT);
 }
 
+/* Test when type has value SSS_AUTHTOK_TYPE_2FA_SINGLE */
+static void test_sss_authtok_2fa_single(void **state)
+{
+    size_t len;
+    errno_t ret;
+    char *data;
+    size_t ret_len;
+    const char *pwd;
+    struct test_state *ts;
+    enum sss_authtok_type type;
+
+    ts = talloc_get_type_abort(*state, struct test_state);
+    data = talloc_strdup(ts, "1stfacto2ndfactor");
+    assert_non_null(data);
+
+    len = strlen(data) + 1;
+    type = SSS_AUTHTOK_TYPE_2FA_SINGLE;
+    ret = sss_authtok_set(ts->authtoken, type, (const uint8_t *)data, len);
+
+    assert_int_equal(ret, EOK);
+    assert_int_equal(type, sss_authtok_get_type(ts->authtoken));
+    assert_int_equal(len, sss_authtok_get_size(ts->authtoken));
+    assert_string_equal(data, sss_authtok_get_data(ts->authtoken));
+
+    ret = sss_authtok_get_2fa_single(ts->authtoken, &pwd, &ret_len);
+
+    assert_int_equal(ret, EOK);
+    assert_string_equal(data, pwd);
+    assert_int_equal(len - 1, ret_len);
+
+    ret = sss_authtok_set_2fa_single(ts->authtoken, data, len);
+    assert_int_equal(ret, EOK);
+
+    ret = sss_authtok_get_2fa_single(ts->authtoken, &pwd, &ret_len);
+    assert_int_equal(ret, EOK);
+    assert_string_equal(data, pwd);
+    assert_int_equal(len - 1, ret_len);
+
+    talloc_free(data);
+    sss_authtok_set_empty(ts->authtoken);
+}
+
+
 int main(int argc, const char *argv[])
 {
     poptContext pc;
@@ -687,6 +730,8 @@ int main(int argc, const char *argv[])
                                         setup, teardown),
         cmocka_unit_test_setup_teardown(test_sss_authtok_sc_blobs,
                                         setup, teardown),
+        cmocka_unit_test_setup_teardown(test_sss_authtok_2fa_single,
+                                        setup, teardown),
     };
 
     /* Set debug level to invalid value so we can decide if -d 0 was used. */
diff --git a/src/util/authtok.c b/src/util/authtok.c
index c2f78be329..0cac245985 100644
--- a/src/util/authtok.c
+++ b/src/util/authtok.c
@@ -41,6 +41,7 @@ size_t sss_authtok_get_size(struct sss_auth_token *tok)
     case SSS_AUTHTOK_TYPE_2FA:
     case SSS_AUTHTOK_TYPE_SC_PIN:
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
         return tok->length;
     case SSS_AUTHTOK_TYPE_EMPTY:
         return 0;
@@ -76,6 +77,7 @@ errno_t sss_authtok_get_password(struct sss_auth_token *tok,
     case SSS_AUTHTOK_TYPE_2FA:
     case SSS_AUTHTOK_TYPE_SC_PIN:
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
         return EACCES;
     }
 
@@ -101,6 +103,33 @@ errno_t sss_authtok_get_ccfile(struct sss_auth_token *tok,
     case SSS_AUTHTOK_TYPE_2FA:
     case SSS_AUTHTOK_TYPE_SC_PIN:
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+        return EACCES;
+    }
+
+    return EINVAL;
+}
+
+errno_t sss_authtok_get_2fa_single(struct sss_auth_token *tok,
+                                   const char **str, size_t *len)
+{
+    if (!tok) {
+        return EINVAL;
+    }
+    switch (tok->type) {
+    case SSS_AUTHTOK_TYPE_EMPTY:
+        return ENOENT;
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+        *str = (const char *)tok->data;
+        if (len) {
+            *len = tok->length - 1;
+        }
+        return EOK;
+    case SSS_AUTHTOK_TYPE_PASSWORD:
+    case SSS_AUTHTOK_TYPE_2FA:
+    case SSS_AUTHTOK_TYPE_SC_PIN:
+    case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+    case SSS_AUTHTOK_TYPE_CCFILE:
         return EACCES;
     }
 
@@ -151,6 +180,7 @@ void sss_authtok_set_empty(struct sss_auth_token *tok)
     case SSS_AUTHTOK_TYPE_PASSWORD:
     case SSS_AUTHTOK_TYPE_2FA:
     case SSS_AUTHTOK_TYPE_SC_PIN:
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
         safezero(tok->data, tok->length);
         break;
     case SSS_AUTHTOK_TYPE_CCFILE:
@@ -181,6 +211,15 @@ errno_t sss_authtok_set_ccfile(struct sss_auth_token *tok,
                                   "ccfile", ccfile, len);
 }
 
+errno_t sss_authtok_set_2fa_single(struct sss_auth_token *tok,
+                                   const char *str, size_t len)
+{
+    sss_authtok_set_empty(tok);
+
+    return sss_authtok_set_string(tok, SSS_AUTHTOK_TYPE_2FA_SINGLE,
+                                  "2fa_single", str, len);
+}
+
 static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok,
                                              const uint8_t *data, size_t len);
 
@@ -199,6 +238,8 @@ errno_t sss_authtok_set(struct sss_auth_token *tok,
         return sss_authtok_set_sc_from_blob(tok, data, len);
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
         return sss_authtok_set_sc_from_blob(tok, data, len);
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
+        return sss_authtok_set_2fa_single(tok, (const char *) data, len);
     case SSS_AUTHTOK_TYPE_EMPTY:
         sss_authtok_set_empty(tok);
         return EOK;
@@ -566,6 +607,7 @@ errno_t sss_authtok_get_sc_pin(struct sss_auth_token *tok, const char **_pin,
     case SSS_AUTHTOK_TYPE_CCFILE:
     case SSS_AUTHTOK_TYPE_2FA:
     case SSS_AUTHTOK_TYPE_SC_KEYPAD:
+    case SSS_AUTHTOK_TYPE_2FA_SINGLE:
         return EACCES;
     }
 
diff --git a/src/util/authtok.h b/src/util/authtok.h
index a55e89fd28..dae3ff6b17 100644
--- a/src/util/authtok.h
+++ b/src/util/authtok.h
@@ -348,4 +348,39 @@ errno_t sss_authtok_get_sc(struct sss_auth_token *tok,
                            const char **_token_name, size_t *_token_name_len,
                            const char **_module_name, size_t *_module_name_len,
                            const char **_key_id, size_t *_key_id_len);
+
+
+/**
+ * @brief Returns a const string if the auth token is of type
+          SSS_AUTHTOK_TYPE_2FA_SINGLE, otherwise it returns an error
+ *
+ * @param tok    A pointer to an sss_auth_token
+ * @param pwd    A pointer to a const char *, that will point to a null
+ *               terminated string
+ * @param len    The length of the credential string
+ *
+ * @return       EOK on success
+ *               ENOENT if the token is empty
+ *               EACCESS if the token is not a password token
+ */
+errno_t sss_authtok_get_2fa_single(struct sss_auth_token *tok,
+                                   const char **str, size_t *len);
+
+/**
+ * @brief Set a 2FA credentials in a single strings  into an auth token,
+ *        replacing any previous data
+ *
+ * @param tok        A pointer to an sss_auth_token structure to change, also
+ *                   used as a memory context to allocate the internal data.
+ * @param password   A string where the two authentication factors are
+ *                   concatenated together
+ * @param len        The length of the string or, if 0 is passed,
+ *                   then strlen(password) will be used internally.
+ *
+ * @return       EOK on success
+ *               ENOMEM on error
+ */
+errno_t sss_authtok_set_2fa_single(struct sss_auth_token *tok,
+                                   const char *str, size_t len);
+
 #endif /*  __AUTHTOK_H__ */

From 544f41fd400926c172c3399bb0587d383db44455 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sb...@redhat.com>
Date: Wed, 27 Mar 2019 09:48:42 +0100
Subject: [PATCH 3/5] pam_sss: use configured prompting

If the responds of SSSD's PAM responder contains a prompt_config
structure use the content to prompt the user for credentials.

Related to https://pagure.io/SSSD/sssd/issue/3264
---
 src/sss_client/pam_message.h |   2 +
 src/sss_client/pam_sss.c     | 136 +++++++++++++++++++++++++++++------
 src/sss_client/sss_cli.h     |   3 +
 3 files changed, 119 insertions(+), 22 deletions(-)

diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h
index 50fedcd82d..e37e2f082b 100644
--- a/src/sss_client/pam_message.h
+++ b/src/sss_client/pam_message.h
@@ -65,6 +65,8 @@ struct pam_items {
     bool user_name_hint;
     struct cert_auth_info *cert_list;
     struct cert_auth_info *selected_cert;
+
+    struct prompt_config **pc;
 };
 
 int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer);
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 9d51aefc61..6bcda23da5 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -197,6 +197,9 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
     free_cert_list(pi->cert_list);
     pi->cert_list = NULL;
     pi->selected_cert = NULL;
+
+    pc_list_free(pi->pc);
+    pi->pc = NULL;
 }
 
 static int null_strcmp(const char *s1, const char *s2) {
@@ -1155,6 +1158,16 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
                 D(("Password prompting available."));
                 pi->password_prompting = true;
                 break;
+            case SSS_PAM_PROMPT_CONFIG:
+                if (pi->pc == NULL) {
+                    ret = pc_list_from_response(len, &buf[p], &pi->pc);
+                    if (ret != EOK) {
+                        D(("Failed to parse prompting data, using defaults"));
+                        pc_list_free(pi->pc);
+                        pi->pc = NULL;
+                    }
+                }
+                break;
             default:
                 D(("Unknown response type [%d]", type));
         }
@@ -1248,6 +1261,8 @@ static int get_pam_items(pam_handle_t *pamh, uint32_t flags,
     pi->cert_list = NULL;
     pi->selected_cert = NULL;
 
+    pi->pc = NULL;
+
     pi->flags = flags;
 
     return PAM_SUCCESS;
@@ -1563,6 +1578,37 @@ static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi,
     return ret;
 }
 
+static int prompt_2fa_single(pam_handle_t *pamh, struct pam_items *pi,
+                             const char *prompt)
+{
+    int ret;
+    char *answer = NULL;
+
+    ret = do_pam_conversation(pamh, PAM_PROMPT_ECHO_OFF, prompt, NULL, &answer);
+    if (ret != PAM_SUCCESS) {
+        D(("do_pam_conversation failed."));
+        return ret;
+    }
+
+    if (answer == NULL) {
+        pi->pam_authtok = NULL;
+        pi->pam_authtok_type = SSS_AUTHTOK_TYPE_EMPTY;
+        pi->pam_authtok_size=0;
+    } else {
+        pi->pam_authtok = strdup(answer);
+        _pam_overwrite((void *)answer);
+        free(answer);
+        answer=NULL;
+        if (pi->pam_authtok == NULL) {
+            return PAM_BUF_ERR;
+        }
+        pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA_SINGLE;
+        pi->pam_authtok_size=strlen(pi->pam_authtok);
+    }
+
+    return PAM_SUCCESS;
+}
+
 #define SC_PROMPT_FMT "PIN for %s"
 
 #ifndef discard_const
@@ -2023,6 +2069,48 @@ static void eval_argv(pam_handle_t *pamh, int argc, const char **argv,
     return;
 }
 
+static int prompt_by_config(pam_handle_t *pamh, struct pam_items *pi)
+{
+    size_t c;
+    int ret;
+
+    if (pi->pc == NULL || *pi->pc == NULL) {
+        return EINVAL;
+    }
+
+    for (c = 0; pi->pc[c] != NULL; c++) {
+        switch (pc_get_type(pi->pc[c])) {
+        case PC_TYPE_PASSWORD:
+            ret = prompt_password(pamh, pi, pc_get_password_prompt(pi->pc[c]));
+            break;
+        case PC_TYPE_2FA:
+            ret = prompt_2fa(pamh, pi, pc_get_2fa_1st_prompt(pi->pc[c]),
+                             pc_get_2fa_2nd_prompt(pi->pc[c]));
+            break;
+        case PC_TYPE_2FA_SINGLE:
+            ret = prompt_2fa_single(pamh, pi,
+                                    pc_get_2fa_single_prompt(pi->pc[c]));
+            break;
+        case PC_TYPE_SC_PIN:
+            ret = prompt_sc_pin(pamh, pi);
+            /* Todo: add extra string option */
+            break;
+        default:
+            ret = EINVAL;
+        }
+
+        /* If not credential where given try the next type otherwise we are
+         * done. */
+        if (ret == PAM_SUCCESS && pi->pam_authtok_size == 0) {
+            continue;
+        }
+
+        break;
+    }
+
+    return ret;
+}
+
 static int get_authtok_for_authentication(pam_handle_t *pamh,
                                           struct pam_items *pi,
                                           uint32_t flags)
@@ -2041,30 +2129,34 @@ static int get_authtok_for_authentication(pam_handle_t *pamh,
         }
         pi->pam_authtok_size = strlen(pi->pam_authtok);
     } else {
-        if (flags & PAM_CLI_FLAGS_USE_2FA
-                || (pi->otp_vendor != NULL && pi->otp_token_id != NULL
-                        && pi->otp_challenge != NULL)) {
-            if (pi->password_prompting) {
-                ret = prompt_2fa(pamh, pi, _("First Factor: "),
-                                 _("Second Factor (optional): "));
-            } else {
-                ret = prompt_2fa(pamh, pi, _("First Factor: "),
-                                 _("Second Factor: "));
-            }
-        } else if (pi->cert_list != NULL) {
-            if (pi->cert_list->next == NULL) {
-                /* Only one certificate */
-                pi->selected_cert = pi->cert_list;
-            } else {
-                ret = prompt_multi_cert(pamh, pi);
-                if (ret != 0) {
-                    D(("Failed to select certificate"));
-                    return PAM_AUTHTOK_ERR;
+        if (pi->pc != NULL) {
+            ret = prompt_by_config(pamh, pi);
+        } else {
+            if (flags & PAM_CLI_FLAGS_USE_2FA
+                    || (pi->otp_vendor != NULL && pi->otp_token_id != NULL
+                            && pi->otp_challenge != NULL)) {
+                if (pi->password_prompting) {
+                    ret = prompt_2fa(pamh, pi, _("First Factor: "),
+                                     _("Second Factor (optional): "));
+                } else {
+                    ret = prompt_2fa(pamh, pi, _("First Factor: "),
+                                     _("Second Factor: "));
                 }
+            } else if (pi->cert_list != NULL) {
+                if (pi->cert_list->next == NULL) {
+                    /* Only one certificate */
+                    pi->selected_cert = pi->cert_list;
+                } else {
+                    ret = prompt_multi_cert(pamh, pi);
+                    if (ret != 0) {
+                        D(("Failed to select certificate"));
+                        return PAM_AUTHTOK_ERR;
+                    }
+                }
+                ret = prompt_sc_pin(pamh, pi);
+            } else {
+                ret = prompt_password(pamh, pi, _("Password: "));
             }
-            ret = prompt_sc_pin(pamh, pi);
-        } else {
-            ret = prompt_password(pamh, pi, _("Password: "));
         }
         if (ret != PAM_SUCCESS) {
             D(("failed to get password from user"));
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 477e4d5cce..0b41d31309 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -481,6 +481,9 @@ enum response_type {
     SSS_PAM_CERT_INFO_WITH_HINT, /**< Same as SSS_PAM_CERT_INFO but user name
                                   * might be missing and should be prompted
                                   * for. */
+    SSS_PAM_PROMPT_CONFIG, /**< Contains data which controls which credentials
+                            * are expected and how the user is prompted for
+                            * them. */
 };
 
 /**

From 4833c00ac4f3f811b7bcbbb1c5ac584365fce947 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sb...@redhat.com>
Date: Wed, 27 Mar 2019 21:05:06 +0100
Subject: [PATCH 4/5] PAM: add initial prompting configuration

Add new section for sssd.conf to allow more flexible prompting during
authentication.

Related to https://pagure.io/SSSD/sssd/issue/3264
---
 Makefile.am                              |   7 +
 src/confdb/confdb.h                      |   9 +
 src/man/sssd.conf.5.xml                  |  66 ++++++
 src/responder/pam/pam_prompting_config.c | 275 +++++++++++++++++++++++
 src/responder/pam/pamsrv.c               |  16 +-
 src/responder/pam/pamsrv.h               |   6 +
 src/responder/pam/pamsrv_cmd.c           |   8 +
 7 files changed, 386 insertions(+), 1 deletion(-)
 create mode 100644 src/responder/pam/pam_prompting_config.c

diff --git a/Makefile.am b/Makefile.am
index 72866cdb0c..17d6446073 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1589,8 +1589,13 @@ sssd_pam_SOURCES = \
     src/responder/pam/pamsrv_cmd.c \
     src/responder/pam/pamsrv_p11.c \
     src/responder/pam/pamsrv_dp.c \
+    src/responder/pam/pam_prompting_config.c \
+    src/sss_client/pam_sss_prompt_config.c \
     src/responder/pam/pam_helpers.c \
     $(SSSD_RESPONDER_OBJ)
+sssd_pam_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
 sssd_pam_LDADD = \
     $(LIBADD_DL) \
     $(TDB_LIBS) \
@@ -2721,6 +2726,8 @@ pam_srv_tests_SOURCES = \
     src/responder/pam/pam_helpers.c \
     src/responder/pam/pamsrv_dp.c \
     src/responder/pam/pam_LOCAL_domain.c \
+    src/responder/pam/pam_prompting_config.c \
+    src/sss_client/pam_sss_prompt_config.c \
     $(NULL)
 pam_srv_tests_CFLAGS = \
     -U SSSD_LIBEXEC_PATH -DSSSD_LIBEXEC_PATH=\"$(abs_builddir)\" \
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 338656b094..7c4ee2d7ab 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -275,6 +275,15 @@
 #define CONFDB_CERTMAP_DOMAINS "domains"
 #define CONFDB_CERTMAP_PRIORITY "priority"
 
+/* Prompting */
+#define CONFDB_PC_CONF_ENTRY "config/prompting"
+#define CONFDB_PC_TYPE_PASSWORD "password"
+#define CONFDB_PC_PASSWORD_PROMPT "password_prompt"
+#define CONFDB_PC_TYPE_2FA "2fa"
+#define CONFDB_PC_2FA_SINGLE_PROMPT "single_prompt"
+#define CONFDB_PC_2FA_1ST_PROMPT "first_prompt"
+#define CONFDB_PC_2FA_2ND_PROMPT "second_prompt"
+#define CONFDB_PC_TYPE_CERT_AUTH "cert_auth"
 
 struct confdb_ctx;
 struct config_file_ctx;
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 699c5ffff1..c12fef7314 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -3579,6 +3579,72 @@ ldap_user_extra_attrs = phone:telephoneNumber
         </para>
     </refsect1>
 
+    <refsect1 id='prompting_configuration'>
+        <title>PROMPTING CONFIGURATION SECTION</title>
+        <para>
+            If a special file
+            (<filename>/var/lib/sss/pubconf/pam_preauth_available</filename>)
+            exists SSSD's PAM module pam_sss will ask SSSD to figure out which
+            authentication methods are available for the user trying to log in.
+            Based on the results pam_sss will prompt the user for appropriate
+            credentials.
+        </para>
+        <para>
+            With the growing number of authentication methods and the
+            possibility that there are multiple ones for a single user the
+            heuristic used by pam_sss to select the prompting might not be
+            suitable for all use cases. To following options should provide a
+            better flexibility here.
+        </para>
+        <para>
+            Each supported authentication method has it's own configuration
+            sub-section under <quote>[prompting/...]</quote>. Currently there
+            are:
+        <variablelist>
+            <varlistentry>
+                <term>[prompting/password]</term>
+                <listitem>
+                    <para>to configure password prompting, allowed options are:
+                    <variablelist><varlistentry><term>password_prompt</term>
+                        <listitem><para>to change the string of the password
+                        prompt</para></listitem></varlistentry></variablelist>
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+        <variablelist>
+            <varlistentry>
+                <term>[prompting/2fa]</term>
+                <listitem>
+                    <para>to configure two-factor authentication prompting,
+                    allowed options are:
+                    <variablelist><varlistentry><term>first_prompt</term>
+                        <listitem><para>to change the string of the prompt for
+                        the first factor </para></listitem>
+                        </varlistentry>
+                        <varlistentry><term>second_prompt</term>
+                        <listitem><para>to change the string of the prompt for
+                        the second factor </para></listitem>
+                        </varlistentry>
+                        <varlistentry><term>single_prompt</term>
+                        <listitem><para>boolean value, if True there will be
+                        only a single prompt using the value of first_prompt
+                        where it is expected that both factor are entered as a
+                        single string</para></listitem>
+                        </varlistentry>
+                    </variablelist>
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+        </para>
+        <para>
+            It is possible to add a sub-section for specific PAM services like
+            e.g. <quote>[prompting/password/sshd]</quote> to individual change
+            the prompting for this service.
+        </para>
+    </refsect1>
+
     <refsect1 id='example'>
         <title>EXAMPLES</title>
         <para>
diff --git a/src/responder/pam/pam_prompting_config.c b/src/responder/pam/pam_prompting_config.c
new file mode 100644
index 0000000000..dc10e501c4
--- /dev/null
+++ b/src/responder/pam/pam_prompting_config.c
@@ -0,0 +1,275 @@
+/*
+   SSSD
+
+   PAM Responder - helpers for PAM prompting configuration
+
+   Copyright (C) Sumit Bose <sb...@redhat.com> 2019
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "util/util.h"
+#include "util/sss_pam_data.h"
+#include "confdb/confdb.h"
+#include "sss_client/sss_cli.h"
+#include "responder/pam/pamsrv.h"
+
+typedef errno_t (pam_set_prompting_fn_t)(TALLOC_CTX *, struct confdb_ctx *,
+                                         const char *,
+                                         struct prompt_config ***);
+
+
+static errno_t pam_set_password_prompting_options(TALLOC_CTX *tmp_ctx,
+                                                struct confdb_ctx *cdb,
+                                                const char *section,
+                                                struct prompt_config ***pc_list)
+{
+    int ret;
+    char *value = NULL;
+
+    ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSWORD_PROMPT,
+                            NULL, &value);
+    if (ret == EOK && value != NULL) {
+        ret = pc_list_add_password(pc_list, value);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_password failed.\n");
+        }
+        return ret;
+    }
+
+    return ENOENT;
+}
+
+static errno_t pam_set_2fa_prompting_options(TALLOC_CTX *tmp_ctx,
+                                             struct confdb_ctx *cdb,
+                                             const char *section,
+                                             struct prompt_config ***pc_list)
+{
+    bool single_2fa_prompt = false;
+    char *first_prompt = NULL;
+    char *second_prompt = NULL;
+    int ret;
+
+
+    ret = confdb_get_bool(cdb, section, CONFDB_PC_2FA_SINGLE_PROMPT, false,
+                          &single_2fa_prompt);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults");
+    }
+    ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_1ST_PROMPT,
+                            NULL, &first_prompt);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults");
+    }
+
+    if (single_2fa_prompt) {
+        ret = pc_list_add_2fa_single(pc_list, first_prompt);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa_single failed.\n");
+        }
+        return ret;
+    } else {
+        ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_2ND_PROMPT,
+                                NULL, &second_prompt);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "confdb_get_string failed, using defaults");
+        }
+
+        ret = pc_list_add_2fa(pc_list, first_prompt, second_prompt);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa failed.\n");
+        }
+        return ret;
+    }
+
+    return ENOENT;
+}
+
+static errno_t pam_set_prompting_options(struct confdb_ctx *cdb,
+                                         const char *service_name,
+                                         char **sections,
+                                         int num_sections,
+                                         const char *section_path,
+                                         pam_set_prompting_fn_t *setter,
+                                         struct prompt_config ***pc_list)
+{
+    char *dummy;
+    size_t c;
+    bool global = false;
+    bool specific = false;
+    char *section = NULL;
+    int ret;
+    char *last;
+    TALLOC_CTX *tmp_ctx = NULL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+
+    dummy = talloc_asprintf(tmp_ctx, "%s/%s", section_path,
+                                              service_name);
+    for (c = 0; c < num_sections; c++) {
+        if (strcmp(sections[c], CONFDB_PC_TYPE_PASSWORD) == 0) {
+            global = true;
+        }
+        if (dummy != NULL && strcmp(sections[c], dummy) == 0) {
+            specific = true;
+        }
+    }
+
+    section = talloc_asprintf(tmp_ctx, "%s/%s", CONFDB_PC_CONF_ENTRY, dummy);
+    if (section == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = ENOENT;
+    if (specific) {
+        ret = setter(tmp_ctx, cdb, section, pc_list);
+    }
+    if (global && ret == ENOENT) {
+        last = strrchr(section, '/');
+        if (last != NULL) {
+            *last = '\0';
+            ret = setter(tmp_ctx, cdb, section, pc_list);
+        }
+    }
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_OP_FAILURE, "setter failed.\n");
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd)
+{
+    int ret;
+    struct response_data *resp;
+    bool password_auth = false;
+    bool otp_auth = false;
+    bool cert_auth = false;
+    struct prompt_config **pc_list = NULL;
+    int resp_len;
+    uint8_t *resp_data = NULL;
+
+    if (pctx->num_prompting_config_sections == 0) {
+        DEBUG(SSSDBG_TRACE_ALL, "No prompting configuration found.\n");
+        return EOK;
+    }
+
+    resp = pd->resp_list;
+    while (resp != NULL) {
+        switch (resp->type) {
+        case SSS_PAM_OTP_INFO:
+            otp_auth = true;
+            break;
+        case SSS_PAM_CERT_INFO:
+            cert_auth = true;
+            break;
+        case SSS_PASSWORD_PROMPTING:
+            password_auth = true;
+            break;
+        case SSS_CERT_AUTH_PROMPTING:
+            /* currently not used */
+            break;
+        default:
+            break;
+        }
+        resp = resp->next;
+    }
+
+    if (!password_auth && !otp_auth && !cert_auth) {
+        /* If the backend cannot determine which authentication types are
+         * available the default would be to prompt for a password. */
+        password_auth = true;
+    }
+
+    DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service "
+                            "[%s]:%s%s%s\n", pd->user, pd->service,
+                            password_auth ? " password": "",
+                            otp_auth ? " two-factor" : "",
+                            cert_auth ? " smartcard" : "");
+
+    if (cert_auth) {
+        /* If certificate based authentication is possilbe, i.e. a Smartcard
+         * or similar with the mapped certificate is available we currently
+         * prefer this authentication type unconditionally. If other types
+         * should be used the Smartcard can be removed during authentication.
+         * Since there currently are no specific options for cert_auth we are
+         * done. */
+        ret = EOK;
+        goto done;
+    }
+
+    /* If OTP and password auth are possible we currently prefer OTP. */
+    if (otp_auth) {
+        ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service,
+                                        pctx->prompting_config_sections,
+                                        pctx->num_prompting_config_sections,
+                                        CONFDB_PC_TYPE_2FA,
+                                        pam_set_2fa_prompting_options,
+                                        &pc_list);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "pam_set_prompting_options failed.\n");
+            goto done;
+        }
+    }
+
+    if (password_auth) {
+        ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service,
+                                        pctx->prompting_config_sections,
+                                        pctx->num_prompting_config_sections,
+                                        CONFDB_PC_TYPE_PASSWORD,
+                                        pam_set_password_prompting_options,
+                                        &pc_list);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "pam_set_prompting_options failed.\n");
+            goto done;
+        }
+    }
+
+    if (pc_list != NULL) {
+        ret = pam_get_response_prompt_config(pc_list, &resp_len, &resp_data);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "pam_get_response_prompt_config failed.\n");
+            goto done;
+        }
+
+        ret = pam_add_response(pd, SSS_PAM_PROMPT_CONFIG, resp_len, resp_data);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "pam_add_response failed.\n");
+            goto done;
+        }
+    }
+
+    ret = EOK;
+done:
+    free(resp_data);
+    pc_list_free(pc_list);
+
+    return ret;
+}
diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c
index 6cfc5117b0..38db6fc9b0 100644
--- a/src/responder/pam/pamsrv.c
+++ b/src/responder/pam/pamsrv.c
@@ -254,6 +254,16 @@ static int pam_process_init(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
+    /* Check if there is a prompting configuration */
+    pctx->prompting_config_sections = NULL;
+    pctx->num_prompting_config_sections = 0;
+    ret = confdb_get_sub_sections(pctx, pctx->rctx->cdb, CONFDB_PC_CONF_ENTRY,
+                                  &pctx->prompting_config_sections,
+                                  &pctx->num_prompting_config_sections);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_OP_FAILURE, "confdb_get_sub_sections failed, not fatal.\n");
+    }
+
     /* Check if certificate based authentication is enabled */
     ret = confdb_get_bool(pctx->rctx->cdb,
                           CONFDB_PAM_CONF_ENTRY,
@@ -285,11 +295,15 @@ static int pam_process_init(TALLOC_CTX *mem_ctx,
             goto done;
         }
 
+    }
+
+    if (pctx->cert_auth || pctx->num_prompting_config_sections != 0) {
         ret = create_preauth_indicator();
         if (ret != EOK) {
             DEBUG(SSSDBG_OP_FAILURE,
                   "Failed to create pre-authentication indicator file, "
-                  "Smartcard authentication might not work as expected.\n");
+                  "Smartcard authentication or configured prompting might "
+                  "not work as expected.\n");
         }
     }
 
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 3a927bb39b..99c6e10a7d 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -51,6 +51,9 @@ struct pam_ctx {
     char *nss_db;
     struct sss_certmap_ctx *sss_certmap_ctx;
     char **smartcard_services;
+
+    char **prompting_config_sections;
+    int num_prompting_config_sections;
 };
 
 struct pam_auth_req {
@@ -126,4 +129,7 @@ pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain,
 errno_t filter_responses(struct confdb_ctx *cdb,
                          struct response_data *resp_list,
                          struct pam_data *pd);
+
+errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd);
+
 #endif /* __PAMSRV_H__ */
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 6c04cb157d..aabb58f64a 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1008,6 +1008,14 @@ static void pam_reply(struct pam_auth_req *preq)
         }
     }
 
+    if (pd->cmd == SSS_PAM_PREAUTH) {
+        ret = pam_eval_prompting_config(pctx, pd);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "Failed to add prompting information, "
+                                     "using defaults.\n");
+        }
+    }
+
     /*
      * Export non-overridden shell to tlog-rec-session when opening the session
      */

From d9464701d88320e31117969a8433b6e0d45d07fa Mon Sep 17 00:00:00 2001
From: Sumit Bose <sb...@redhat.com>
Date: Fri, 29 Mar 2019 10:38:50 +0100
Subject: [PATCH 5/5] intg: add test for password prompt configuration

Related to Related to https://pagure.io/SSSD/sssd/issue/3264
---
 src/tests/intg/Makefile.am           |   9 +-
 src/tests/intg/test_pam_responder.py | 130 +++++++++++++++++++++++++++
 2 files changed, 138 insertions(+), 1 deletion(-)

diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index 25093b5036..2aa1566e35 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -113,6 +113,13 @@ pam_sss_service:
 	echo "password required       $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
 	echo "session  required       $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
 
+pam_sss_alt_service:
+	$(MKDIR_P) $(PAM_SERVICE_DIR)
+	echo "auth     required       $(DESTDIR)$(pammoddir)/pam_sss.so"  > $(PAM_SERVICE_DIR)/$@
+	echo "account  required       $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+	echo "password required       $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+	echo "session  required       $(DESTDIR)$(pammoddir)/pam_sss.so" >> $(PAM_SERVICE_DIR)/$@
+
 pam_sss_sc_required:
 	$(MKDIR_P) $(PAM_SERVICE_DIR)
 	echo "auth     required       $(DESTDIR)$(pammoddir)/pam_sss.so require_cert_auth retry=1"  > $(PAM_SERVICE_DIR)/$@
@@ -141,7 +148,7 @@ PAM_CERT_DB_PATH="$(abs_builddir)/../test_CA/SSSD_test_CA.pem"
 SOFTHSM2_CONF="$(abs_builddir)/../test_CA/softhsm2_one.conf"
 endif
 
-intgcheck-installed: config.py passwd group pam_sss_service pam_sss_sc_required pam_sss_try_sc
+intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service pam_sss_sc_required pam_sss_try_sc
 	pipepath="$(DESTDIR)$(pipepath)"; \
 	if test $${#pipepath} -gt 80; then \
 	    echo "error: Pipe directory path too long," \
diff --git a/src/tests/intg/test_pam_responder.py b/src/tests/intg/test_pam_responder.py
index d1ad9affd0..8e1fcf126e 100644
--- a/src/tests/intg/test_pam_responder.py
+++ b/src/tests/intg/test_pam_responder.py
@@ -30,12 +30,82 @@
 import shutil
 
 import config
+import intg.ds_openldap
 
 import pytest
 
 from intg.util import unindent
 from intg.files_ops import passwd_ops_setup
 
+LDAP_BASE_DN = "dc=example,dc=com"
+
+
+@pytest.fixture(scope="module")
+def ad_inst(request):
+    """Fake AD server instance fixture"""
+    instance = intg.ds_openldap.FakeAD(
+        config.PREFIX, 10389, LDAP_BASE_DN,
+        "cn=admin", "Secret123"
+    )
+
+    try:
+        instance.setup()
+    except:
+        instance.teardown()
+        raise
+    request.addfinalizer(instance.teardown)
+    return instance
+
+
+@pytest.fixture(scope="module")
+def ldap_conn(request, ad_inst):
+    """LDAP server connection fixture"""
+    ldap_conn = ad_inst.bind()
+    ldap_conn.ad_inst = ad_inst
+    request.addfinalizer(ldap_conn.unbind_s)
+    return ldap_conn
+
+
+def format_basic_conf(ldap_conn):
+    """Format a basic SSSD configuration"""
+    return unindent("""\
+        [sssd]
+        domains = FakeAD
+        services = pam, nss
+
+        [nss]
+
+        [pam]
+        debug_level = 10
+
+        [domain/FakeAD]
+        debug_level = 10
+        ldap_search_base = {ldap_conn.ad_inst.base_dn}
+        ldap_referrals = false
+
+        id_provider = ldap
+        auth_provider = ldap
+        chpass_provider = ldap
+        access_provider = ldap
+
+        ldap_uri = {ldap_conn.ad_inst.ldap_url}
+        ldap_default_bind_dn = {ldap_conn.ad_inst.admin_dn}
+        ldap_default_authtok_type = password
+        ldap_default_authtok = {ldap_conn.ad_inst.admin_pw}
+
+        ldap_schema = ad
+        ldap_id_mapping = true
+        ldap_idmap_default_domain_sid = S-1-5-21-1305200397-2901131868-73388776
+        case_sensitive = False
+
+        [prompting/password]
+        password_prompt = My global prompt
+
+        [prompting/password/pam_sss_alt_service]
+        password_prompt = My alt service prompt
+    """).format(**locals())
+
+
 USER1 = dict(name='user1', passwd='x', uid=10001, gid=20001,
              gecos='User for tests',
              dir='/home/user1',
@@ -220,6 +290,66 @@ def test_preauth_indicator(simple_pam_cert_auth):
     assert stat.S_ISREG(statinfo.st_mode)
 
 
+@pytest.fixture
+def pam_prompting_config(request, ldap_conn):
+    """Setup SSSD with PAM prompting config"""
+    conf = format_basic_conf(ldap_conn)
+    create_conf_fixture(request, conf)
+    create_sssd_fixture(request)
+    return None
+
+
+def test_password_prompting_config_global(ldap_conn, pam_prompting_config,
+                                          env_for_sssctl):
+    """Check global change of the password prompt"""
+
+    sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661",
+                               "--action=auth", "--service=pam_sss_service"],
+                              universal_newlines=True,
+                              env=env_for_sssctl, stdin=subprocess.PIPE,
+                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+    try:
+        out, err = sssctl.communicate(input="111")
+    except:
+        sssctl.kill()
+        out, err = sssctl.communicate()
+
+    sssctl.stdin.close()
+    sssctl.stdout.close()
+
+    if sssctl.wait() != 0:
+        raise Exception("sssctl failed")
+
+    assert err.find("My global prompt") != -1
+
+
+def test_password_prompting_config_srv(ldap_conn, pam_prompting_config,
+                                       env_for_sssctl):
+    """Check change of the password prompt for dedicated service"""
+
+    sssctl = subprocess.Popen(["sssctl", "user-checks", "user1_dom1-19661",
+                               "--action=auth",
+                               "--service=pam_sss_alt_service"],
+                              universal_newlines=True,
+                              env=env_for_sssctl, stdin=subprocess.PIPE,
+                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+    try:
+        out, err = sssctl.communicate(input="111")
+    except:
+        sssctl.kill()
+        out, err = sssctl.communicate()
+
+    sssctl.stdin.close()
+    sssctl.stdout.close()
+
+    if sssctl.wait() != 0:
+        raise Exception("sssctl failed")
+
+    assert err.find("My alt service prompt") != -1
+
+
 @pytest.fixture
 def env_for_sssctl(request):
     pwrap_runtimedir = os.getenv("PAM_WRAPPER_SERVICE_DIR")
_______________________________________________
sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org
To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: https://getfedora.org/code-of-conduct.html
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org

Reply via email to