[SSSD] [PATCHES] Add special PAM prompting for 2FA authentication

Sumit Bose sbose at redhat.com
Mon Apr 6 18:58:16 UTC 2015


Hi,

this series of patches tries to add special prompting in the PAM dialog
for 2-Factor-Authentication (2FA) and use it where suitable. As
discussed on
https://fedorahosted.org/sssd/wiki/DesignDocs/PAMConversationForOTP if
long-term password and one-time component are entered in a single prompt
SSSD is in general not able to split them and use the long-term password
of offline authentication or some user keyrings. With this patch-set an
IPA user with configured OTP token will be prompted to enter the two
factors separately (if the login application supports it) and the first
factor (long-term password) will be save into the cache for offline
authentication if it is long enough.

Two new internals are needed for this. A special authentication token
type for 2FA and a PAM pre-auth request to check if the given user user
a standard password or 2FA/OTP.

The first two patches enhance the authtok module to handle 2FA.
Patches 4-6 add the pre-auth request to PAM client, responder and IPA
backend. Patches 7-12 add the prompting, the minimal length check and
the caching of the first factor. Finally tests for the PAM responder are
added which should test existing and new behaviour.

bye,
Sumit
-------------- next part --------------
From d95e8e99ba33505ae973106d49cc92463b5d9b4f Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 12 Feb 2015 21:53:15 +0100
Subject: [PATCH 01/14] Add leak check and command line option to test_authtok

---
 Makefile.am                     |  3 ++
 src/tests/cmocka/test_authtok.c | 67 +++++++++++++++++++++++++++++++++++------
 2 files changed, 60 insertions(+), 10 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 38a2ee3e45b5c37e4367b82f321327062f6e2bb4..5f1fb6ce7ab52480a3038148a71483b8e92518cc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1853,11 +1853,14 @@ test_authtok_SOURCES = \
 test_authtok_CFLAGS = \
     $(AM_CFLAGS) \
     $(TALLOC_CFLAGS) \
+    $(POPT_CFLAGS) \
     $(DHASH_CFLAGS)
 test_authtok_LDADD = \
     $(TALLOC_LIBS) \
     $(CMOCKA_LIBS) \
     $(DHASH_LIBS) \
+    $(POPT_LIBS) \
+    libsss_test_common.la \
     libsss_debug.la
 
 sss_nss_idmap_tests_SOURCES = \
diff --git a/src/tests/cmocka/test_authtok.c b/src/tests/cmocka/test_authtok.c
index e37e92f68373d564f53b1267f078ea89c31ae051..0c7b7197fb2c03d69dc4df2310229ea100ad28d4 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -22,11 +22,10 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include <stdarg.h>
-#include <stddef.h>
-#include <setjmp.h>
-#include <cmocka.h>
 #include <string.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
 
 #include "util/authtok.h"
 
@@ -39,12 +38,15 @@ static int setup(void **state)
 {
     struct test_state *ts = NULL;
 
-    ts = talloc(NULL, struct test_state);
+    assert_true(leak_check_setup());
+
+    ts = talloc(global_talloc_context, struct test_state);
     assert_non_null(ts);
 
     ts->authtoken = sss_authtok_new(ts);
     assert_non_null(ts->authtoken);
 
+    check_leaks_push(ts);
     *state = (void *)ts;
     return 0;
 }
@@ -52,7 +54,12 @@ static int setup(void **state)
 static int teardown(void **state)
 {
     struct test_state *ts = talloc_get_type_abort(*state, struct test_state);
+
+    assert_non_null(ts);
+
+    assert_true(check_leaks_pop(ts) == true);
     talloc_free(ts);
+    assert_true(leak_check_teardown());
     return 0;
 }
 
@@ -85,7 +92,7 @@ static void test_sss_authtok_password(void **state)
 {
     size_t len;
     errno_t ret;
-    const char *data;
+    char *data;
     size_t ret_len;
     const char *pwd;
     struct test_state *ts;
@@ -117,6 +124,9 @@ static void test_sss_authtok_password(void **state)
     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);
 }
 
 /* Test when type has value SSS_AUTHTOK_TYPE_CCFILE */
@@ -124,7 +134,7 @@ static void test_sss_authtok_ccfile(void **state)
 {
     size_t len;
     errno_t ret;
-    const char *data;
+    char *data;
     size_t ret_len;
     const char *pwd;
     struct test_state *ts;
@@ -172,6 +182,9 @@ static void test_sss_authtok_ccfile(void **state)
     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);
 }
 
 /* Test when type has value SSS_AUTHTOK_TYPE_EMPTY */
@@ -226,7 +239,7 @@ static void test_sss_authtok_wipe_password(void **state)
 {
     size_t len;
     errno_t ret;
-    const char *data;
+    char *data;
     size_t ret_len;
     const char *pwd;
     struct test_state *ts;
@@ -249,13 +262,16 @@ static void test_sss_authtok_wipe_password(void **state)
     assert_int_equal(ret, EOK);
     assert_string_equal(pwd, "");
     assert_int_equal(len - 1, ret_len);
+
+    sss_authtok_set_empty(ts->authtoken);
+    talloc_free(data);
 }
 
 static void test_sss_authtok_copy(void **state)
 {
     size_t len;
     errno_t ret;
-    const char *data;
+    char *data;
     struct test_state *ts;
     enum sss_authtok_type type;
     struct sss_auth_token *dest_authtoken;
@@ -276,6 +292,7 @@ static void test_sss_authtok_copy(void **state)
     assert_int_equal(EOK, sss_authtok_copy(ts->authtoken, dest_authtoken));
     assert_int_equal(type, sss_authtok_get_type(dest_authtoken));
 
+    sss_authtok_set_empty(dest_authtoken);
     type = SSS_AUTHTOK_TYPE_PASSWORD;
     ret = sss_authtok_set(ts->authtoken, type, (const uint8_t *)data, len);
 
@@ -287,10 +304,23 @@ static void test_sss_authtok_copy(void **state)
     assert_int_equal(type, sss_authtok_get_type(dest_authtoken));
     assert_string_equal(data, sss_authtok_get_data(dest_authtoken));
     assert_int_equal(len, sss_authtok_get_size(dest_authtoken));
+
+    sss_authtok_set_empty(dest_authtoken);
+    talloc_free(dest_authtoken);
+    sss_authtok_set_empty(ts->authtoken);
+    talloc_free(data);
 }
 
-int main(void)
+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_setup_teardown(test_sss_authtok_new,
                                         setup, teardown),
@@ -306,5 +336,22 @@ int main(void)
                                         setup, teardown)
     };
 
+    /* Set debug level to invalid value so we can deside 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);
 }
-- 
2.1.0

-------------- next part --------------
From 32819b035a8d60ee81c91d8afc696494e54230c8 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Wed, 7 Jan 2015 18:11:16 +0100
Subject: [PATCH 02/14] utils: add sss_authtok_[gs]et_2fa

---
 Makefile.am                     |   5 ++
 src/sss_client/pam_sss.c        |   1 +
 src/sss_client/sss_cli.h        |   3 +
 src/tests/cmocka/test_authtok.c | 155 +++++++++++++++++++++++++++++++++-
 src/util/authtok-utils.c        |  74 ++++++++++++++++
 src/util/authtok-utils.h        |  48 +++++++++++
 src/util/authtok.c              | 182 ++++++++++++++++++++++++++++++++++++++++
 src/util/authtok.h              |  44 ++++++++++
 8 files changed, 511 insertions(+), 1 deletion(-)
 create mode 100644 src/util/authtok-utils.c
 create mode 100644 src/util/authtok-utils.h

diff --git a/Makefile.am b/Makefile.am
index 5f1fb6ce7ab52480a3038148a71483b8e92518cc..9c90e05619b59326a0079889de1d54ee23f66dee 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -519,6 +519,7 @@ dist_noinst_HEADERS = \
     src/util/atomic_io.h \
     src/util/auth_utils.h \
     src/util/authtok.h \
+    src/util/authtok-utils.h \
     src/util/util_safealign.h \
     src/util/util_sss_idmap.h \
     src/monitor/monitor.h \
@@ -754,6 +755,7 @@ libsss_util_la_SOURCES = \
     src/util/murmurhash3.c \
     src/util/atomic_io.c \
     src/util/authtok.c \
+    src/util/authtok-utils.c \
     src/util/sss_selinux.c \
     src/util/domain_info_utils.c \
     src/util/util_lock.c \
@@ -1849,6 +1851,7 @@ test_negcache_LDADD = \
 test_authtok_SOURCES = \
     src/tests/cmocka/test_authtok.c \
     src/util/authtok.c \
+    src/util/authtok-utils.c \
     src/util/util.c
 test_authtok_CFLAGS = \
     $(AM_CFLAGS) \
@@ -2773,6 +2776,7 @@ krb5_child_SOURCES = \
     src/util/find_uid.c \
     src/util/atomic_io.c \
     src/util/authtok.c \
+    src/util/authtok-utils.c \
     src/util/util.c \
     src/util/signal.c \
     src/util/strtonum.c \
@@ -2804,6 +2808,7 @@ ldap_child_SOURCES = \
     src/util/sss_krb5.c \
     src/util/atomic_io.c \
     src/util/authtok.c \
+    src/util/authtok-utils.c \
     src/util/util.c \
     src/util/signal.c \
     src/util/become_user.c \
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 6916575bb6d8288508ce1894d272356ffcc23f04..83769a15337f7ba027f66a8f7e530daa4a287383 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -41,6 +41,7 @@
 
 #include "sss_cli.h"
 #include "util/atomic_io.h"
+#include "util/authtok-utils.h"
 
 #include <libintl.h>
 #define _(STRING) dgettext (PACKAGE, STRING)
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index d508a0671cd1b3ee087e0967f7015628ceabe20f..9a19d7d47d0a9d7dabeac36dc2c866c3420ef501 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -301,6 +301,9 @@ enum sss_authtok_type {
                                           * a Kerberos credential cache file,
                                           * it may or may no contain
                                           * a trailing \\0 */
+    SSS_AUTHTOK_TYPE_2FA =       0x0003, /**< Authentication token has two
+                                          * factors, they 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 0c7b7197fb2c03d69dc4df2310229ea100ad28d4..871dfef9552ea34388bb650660090e5aca86460a 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -311,6 +311,153 @@ static void test_sss_authtok_copy(void **state)
     talloc_free(data);
 }
 
+void test_sss_authtok_2fa(void **state)
+{
+    int ret;
+    const char *fa1;
+    uint32_t fa1_size;
+    const char *fa2;
+    uint32_t fa2_size;
+    struct sss_auth_token *tok;
+    struct test_state *ts;
+
+    ts = talloc_get_type_abort(*state, struct test_state);
+
+    ret = sss_authtok_set_2fa(NULL, "a", 0, "b", 0);
+    assert_int_equal(ret, EINVAL);
+
+    /* Test missing first factor */
+    ret = sss_authtok_set_2fa(ts->authtoken, NULL, 1, "b", 1);
+    assert_int_equal(ret, EINVAL);
+    /* Test missing second factor */
+    ret = sss_authtok_set_2fa(ts->authtoken, "a", 1, NULL, 1);
+    assert_int_equal(ret, EINVAL);
+    /* Test wrong first factor length */
+    ret = sss_authtok_set_2fa(ts->authtoken, "ab", 1, "b", 1);
+    assert_int_equal(ret, EINVAL);
+    /* Test wrong second factor length */
+    ret = sss_authtok_set_2fa(ts->authtoken, "a", 1, "bc", 1);
+    assert_int_equal(ret, EINVAL);
+
+    ret = sss_authtok_set_2fa(ts->authtoken, "a", 1, "bc", 2);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(sss_authtok_get_size(ts->authtoken),
+                     2 * sizeof(uint32_t) + 5);
+    assert_int_equal(sss_authtok_get_type(ts->authtoken), SSS_AUTHTOK_TYPE_2FA);
+    assert_memory_equal(sss_authtok_get_data(ts->authtoken),
+                        "\2\0\0\0\3\0\0\0a\0bc\0",
+                        2 * sizeof(uint32_t) + 5);
+
+    ret = sss_authtok_get_2fa(ts->authtoken, &fa1, &fa1_size, &fa2, &fa2_size);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(fa1_size, 1);
+    assert_string_equal(fa1, "a");
+    assert_int_equal(fa2_size, 2);
+    assert_string_equal(fa2, "bc");
+
+    sss_authtok_set_empty(ts->authtoken);
+
+    /* check return code of empty token */
+    ret = sss_authtok_get_2fa(ts->authtoken, &fa1, &fa1_size, &fa2, &fa2_size);
+    assert_int_equal(ret, ENOENT);
+
+    /* check return code for other token type */
+    ret = sss_authtok_set_password(ts->authtoken, "abc", 0);
+    ret = sss_authtok_get_2fa(ts->authtoken, &fa1, &fa1_size, &fa2, &fa2_size);
+    assert_int_equal(ret, EACCES);
+
+    sss_authtok_set_empty(ts->authtoken);
+
+    /* check return code for garbage */
+    ret = sss_authtok_set(ts->authtoken, SSS_AUTHTOK_TYPE_2FA,
+                          "1111222233334444", 16);
+    assert_int_equal(ret, EINVAL);
+
+    sss_authtok_set_empty(ts->authtoken);
+}
+
+void test_sss_authtok_2fa_blobs(void **state)
+{
+    int ret;
+    struct test_state *ts;
+    size_t needed_size;
+    uint8_t *buf;
+    char *fa1;
+    size_t fa1_len;
+    char *fa2;
+    size_t fa2_len;
+
+    ts = talloc_get_type_abort(*state, struct test_state);
+
+    ret = sss_auth_pack_2fa_blob("abc", 0, "defg", 0, NULL, 0, &needed_size);
+    assert_int_equal(ret, EAGAIN);
+
+    buf = talloc_size(ts, needed_size);
+    assert_non_null(buf);
+
+    ret = sss_auth_pack_2fa_blob("abc", 0, "defg", 0, buf, needed_size,
+                                 &needed_size);
+    assert_int_equal(ret, EOK);
+
+    assert_memory_equal(buf, "\4\0\0\0\5\0\0\0abc\0defg\0", needed_size);
+
+    ret = sss_auth_unpack_2fa_blob(ts, buf, needed_size, &fa1, &fa1_len, &fa2,
+                                   &fa2_len);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(fa1_len, 3);
+    assert_string_equal(fa1, "abc");
+    assert_int_equal(fa2_len, 4);
+    assert_string_equal(fa2, "defg");
+
+    talloc_free(buf);
+    talloc_free(fa1);
+    talloc_free(fa2);
+}
+
+#define MISSING_NULL_CHECK do { \
+    assert_int_equal(ret, EOK); \
+    assert_int_equal(fa1_len, 3); \
+    assert_string_equal(fa1, "abc"); \
+    assert_int_equal(fa2_len, 4); \
+    assert_string_equal(fa2, "defg"); \
+ \
+    talloc_free(fa1); \
+    talloc_free(fa2); \
+} while (0)
+
+void test_sss_authtok_2fa_blobs_missing_null(void **state)
+{
+    int ret;
+    struct test_state *ts;
+    char *fa1;
+    size_t fa1_len;
+    char *fa2;
+    size_t fa2_len;
+    uint8_t b0[] = {0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 'a', 'b', 'c', 0x00, 'd', 'e', 'f', 'g', 0x00};
+    uint8_t b1[] = {0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 0x00};
+    uint8_t b2[] = {0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'b', 'c', 0x00, 'd', 'e', 'f', 'g'};
+    uint8_t b3[] = {0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'b', 'c', 'd', 'e', 'f', 'g'};
+
+
+    ts = talloc_get_type_abort(*state, struct test_state);
+
+    ret = sss_auth_unpack_2fa_blob(ts, b0, sizeof(b0), &fa1, &fa1_len, &fa2,
+                                   &fa2_len);
+    MISSING_NULL_CHECK;
+
+    ret = sss_auth_unpack_2fa_blob(ts, b1, sizeof(b1), &fa1, &fa1_len, &fa2,
+                                   &fa2_len);
+    MISSING_NULL_CHECK;
+
+    ret = sss_auth_unpack_2fa_blob(ts, b2, sizeof(b2), &fa1, &fa1_len, &fa2,
+                                   &fa2_len);
+    MISSING_NULL_CHECK;
+
+    ret = sss_auth_unpack_2fa_blob(ts, b3, sizeof(b3), &fa1, &fa1_len, &fa2,
+                                   &fa2_len);
+    MISSING_NULL_CHECK;
+}
+
 int main(int argc, const char *argv[])
 {
     poptContext pc;
@@ -333,7 +480,13 @@ int main(int argc, const char *argv[])
         cmocka_unit_test_setup_teardown(test_sss_authtok_wipe_password,
                                         setup, teardown),
         cmocka_unit_test_setup_teardown(test_sss_authtok_copy,
-                                        setup, teardown)
+                                        setup, teardown),
+        cmocka_unit_test_setup_teardown(test_sss_authtok_2fa,
+                                        setup, teardown),
+        cmocka_unit_test_setup_teardown(test_sss_authtok_2fa_blobs,
+                                        setup, teardown),
+        cmocka_unit_test_setup_teardown(test_sss_authtok_2fa_blobs_missing_null,
+                                        setup, teardown),
     };
 
     /* Set debug level to invalid value so we can deside if -d 0 was used. */
diff --git a/src/util/authtok-utils.c b/src/util/authtok-utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..3955e6667f0957fa456d8a33d0325299b56f8246
--- /dev/null
+++ b/src/util/authtok-utils.c
@@ -0,0 +1,74 @@
+/*
+   SSSD - auth utils helpers
+
+   Copyright (C) Sumit Bose <sbose at redhat.com> 2015
+
+   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/>.
+*/
+
+/* This file is use by SSSD clients and the main daemons. Please do not add
+ * code which is specific to only one of them. */
+
+#include <errno.h>
+
+#include "sss_client/sss_cli.h"
+
+errno_t sss_auth_pack_2fa_blob(const char *fa1, size_t fa1_len,
+                               const char *fa2, size_t fa2_len,
+                               uint8_t *buf, size_t buf_len,
+                               size_t *_2fa_blob_len)
+{
+    size_t c;
+    uint32_t tmp_uint32_t;
+
+    if (fa1 == NULL || fa1_len > UINT32_MAX || fa2 == NULL
+            || fa2_len > UINT32_MAX || (buf == NULL && buf_len != 0)) {
+        return EINVAL;
+    }
+
+    if (fa1_len == 0) {
+        fa1_len = strlen(fa1);
+    } else {
+        if (fa1[fa1_len] != '\0') {
+            return EINVAL;
+        }
+    }
+
+    if (fa2_len == 0) {
+        fa2_len = strlen(fa2);
+    } else {
+        if (fa2[fa2_len] != '\0') {
+            return EINVAL;
+        }
+    }
+
+    *_2fa_blob_len = fa1_len + fa2_len + 2 + 2 * sizeof(uint32_t);
+    if (buf_len < *_2fa_blob_len) {
+        return EAGAIN;
+    }
+
+    c = 0;
+    tmp_uint32_t = (uint32_t) fa1_len + 1;
+    SAFEALIGN_COPY_UINT32(buf, &tmp_uint32_t, &c);
+    tmp_uint32_t = (uint32_t) fa2_len + 1;
+    SAFEALIGN_COPY_UINT32(buf + c, &tmp_uint32_t, &c);
+
+    memcpy(buf + c, fa1, fa1_len + 1);
+    c += fa1_len + 1;
+
+    memcpy(buf + c, fa2, fa2_len + 1);
+
+    return 0;
+}
+
diff --git a/src/util/authtok-utils.h b/src/util/authtok-utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ef6c20a85dda90e627e5d4d7107aaa8a9da253c
--- /dev/null
+++ b/src/util/authtok-utils.h
@@ -0,0 +1,48 @@
+/*
+   SSSD - auth utils helpers
+
+   Copyright (C) Sumit Bose <simo at redhat.com> 2015
+
+   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/>.
+*/
+
+#ifndef __AUTHTOK_UTILS_H__
+#define __AUTHTOK_UTILS_H__
+
+#include "sss_client/sss_cli.h"
+
+/**
+ * @brief Fill memory buffer with 2FA blob
+ *
+ * @param[in]  fa1       First authentication factor, null terminated
+ * @param[in]  fa1_len   Length of the first authentication factor, if 0
+ *                       strlen() will be called internally
+ * @param[in]  fa2       Second authentication factor, null terminated
+ * @param[in]  fa2_len   Length of the second authentication factor, if 0
+ *                       strlen() will be called internally
+ * @param[in]  buf       memory buffer of size buf_len
+ * @param[in]  buf_len   size of memory buffer buf
+ *
+ * @param[out] _2fa_blob_len size of the 2FA blob
+ *
+ * @return     EOK       on success
+ *             EINVAL    if input data is not consistent
+ *             EAGAIN    if provided buffer is too small, _2fa_blob_len
+ *                       contains the size needed to store the 2FA blob
+ */
+errno_t sss_auth_pack_2fa_blob(const char *fa1, size_t fa1_len,
+                               const char *fa2, size_t fa2_len,
+                               uint8_t *buf, size_t buf_len,
+                               size_t *_2fa_blob_len);
+#endif /*  __AUTHTOK_UTILS_H__ */
diff --git a/src/util/authtok.c b/src/util/authtok.c
index b7bc17ed0cdc5cfee7f455b0d7047803e628274a..0276ecc57e90acd09e9dbee080ac12a40ce6ea6a 100644
--- a/src/util/authtok.c
+++ b/src/util/authtok.c
@@ -38,6 +38,7 @@ size_t sss_authtok_get_size(struct sss_auth_token *tok)
     switch (tok->type) {
     case SSS_AUTHTOK_TYPE_PASSWORD:
     case SSS_AUTHTOK_TYPE_CCFILE:
+    case SSS_AUTHTOK_TYPE_2FA:
         return tok->length;
     case SSS_AUTHTOK_TYPE_EMPTY:
         return 0;
@@ -70,6 +71,7 @@ errno_t sss_authtok_get_password(struct sss_auth_token *tok,
         }
         return EOK;
     case SSS_AUTHTOK_TYPE_CCFILE:
+    case SSS_AUTHTOK_TYPE_2FA:
         return EACCES;
     }
 
@@ -92,6 +94,7 @@ errno_t sss_authtok_get_ccfile(struct sss_auth_token *tok,
         }
         return EOK;
     case SSS_AUTHTOK_TYPE_PASSWORD:
+    case SSS_AUTHTOK_TYPE_2FA:
         return EACCES;
     }
 
@@ -140,6 +143,7 @@ void sss_authtok_set_empty(struct sss_auth_token *tok)
     case SSS_AUTHTOK_TYPE_EMPTY:
         return;
     case SSS_AUTHTOK_TYPE_PASSWORD:
+    case SSS_AUTHTOK_TYPE_2FA:
         safezero(tok->data, tok->length);
         break;
     case SSS_AUTHTOK_TYPE_CCFILE:
@@ -169,6 +173,9 @@ errno_t sss_authtok_set_ccfile(struct sss_auth_token *tok,
                                   "ccfile", ccfile, len);
 }
 
+static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok,
+                                             const uint8_t *data, size_t len);
+
 errno_t sss_authtok_set(struct sss_auth_token *tok,
                         enum sss_authtok_type type,
                         const uint8_t *data, size_t len)
@@ -178,6 +185,8 @@ errno_t sss_authtok_set(struct sss_auth_token *tok,
         return sss_authtok_set_password(tok, (const char *)data, len);
     case SSS_AUTHTOK_TYPE_CCFILE:
         return sss_authtok_set_ccfile(tok, (const char *)data, len);
+    case SSS_AUTHTOK_TYPE_2FA:
+        return sss_authtok_set_2fa_from_blob(tok, data, len);
     case SSS_AUTHTOK_TYPE_EMPTY:
         sss_authtok_set_empty(tok);
         return EOK;
@@ -230,3 +239,176 @@ void sss_authtok_wipe_password(struct sss_auth_token *tok)
     safezero(tok->data, tok->length);
 }
 
+errno_t sss_auth_unpack_2fa_blob(TALLOC_CTX *mem_ctx,
+                                 const uint8_t *blob, size_t blob_len,
+                                 char **fa1, size_t *_fa1_len,
+                                 char **fa2, size_t *_fa2_len)
+{
+    size_t c;
+    uint32_t fa1_len;
+    uint32_t fa2_len;
+
+    if (blob_len < 2 * sizeof(uint32_t)) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n");
+        return EINVAL;
+    }
+
+    c = 0;
+    SAFEALIGN_COPY_UINT32(&fa1_len, blob, &c);
+    SAFEALIGN_COPY_UINT32(&fa2_len, blob + c, &c);
+
+    if (blob_len != 2 * sizeof(uint32_t) + fa1_len + fa2_len) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n");
+        return EINVAL;
+    }
+
+    if (fa1_len != 0) {
+        *fa1 = talloc_strndup(mem_ctx, (const char *) blob + c, fa1_len);
+        if (*fa1 == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+            return ENOMEM;
+        }
+    } else {
+        *fa1 = NULL;
+    }
+
+    if (fa2_len != 0) {
+        *fa2 = talloc_strndup(mem_ctx, (const char *) blob + c + fa1_len,
+                              fa2_len);
+        if (*fa2 == NULL) {
+            DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+            talloc_free(*fa1);
+            return ENOMEM;
+        }
+    } else {
+        *fa2 = NULL;
+    }
+
+    /* Re-calculate length for the case where \0 was missing in the blob */
+    *_fa1_len = (*fa1 == NULL) ? 0 : strlen(*fa1);
+    *_fa2_len = (*fa2 == NULL) ? 0 : strlen(*fa2);
+
+    return EOK;
+}
+
+static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok,
+                                             const uint8_t *data, size_t len)
+{
+    TALLOC_CTX *tmp_ctx;
+    int ret;
+    char *fa1;
+    size_t fa1_len;
+    char *fa2;
+    size_t fa2_len;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+        ret =  ENOMEM;
+        goto done;
+    }
+
+    ret = sss_auth_unpack_2fa_blob(tmp_ctx, data, len, &fa1, &fa1_len,
+                                   &fa2, &fa2_len);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_auth_unpack_2fa_blob failed.\n");
+        goto done;
+    }
+
+    ret = sss_authtok_set_2fa(tok, fa1, fa1_len, fa2, fa2_len);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_set_2fa failed.\n");
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    talloc_free(tmp_ctx);
+
+    if (ret != EOK) {
+        sss_authtok_set_empty(tok);
+    }
+
+    return ret;
+}
+
+
+errno_t sss_authtok_get_2fa(struct sss_auth_token *tok,
+                            const char **fa1, size_t *fa1_len,
+                            const char **fa2, size_t *fa2_len)
+{
+    size_t c;
+    uint32_t tmp_uint32_t;
+
+    if (tok->type != SSS_AUTHTOK_TYPE_2FA) {
+        return (tok->type == SSS_AUTHTOK_TYPE_EMPTY) ? ENOENT : EACCES;
+    }
+
+    if (tok->length < 2 * sizeof(uint32_t)) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n");
+        return EINVAL;
+    }
+
+    c = 0;
+    SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data, &c);
+    *fa1_len = tmp_uint32_t - 1;
+    SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data + c, &c);
+    *fa2_len = tmp_uint32_t - 1;
+
+    if (*fa1_len == 0 || fa2_len == 0
+            || tok->length != 2 * sizeof(uint32_t) + *fa1_len + *fa2_len + 2) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n");
+        return EINVAL;
+    }
+
+    if (tok->data[c + *fa1_len] != '\0'
+            || tok->data[c + *fa1_len + 1 + *fa2_len] != '\0') {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing terminating null character.\n");
+        return EINVAL;
+    }
+
+    *fa1 = (const char *) tok->data + c;
+    *fa2 = (const char *) tok->data + c + *fa1_len + 1;
+
+    return EOK;
+}
+
+errno_t sss_authtok_set_2fa(struct sss_auth_token *tok,
+                            const char *fa1, size_t fa1_len,
+                            const char *fa2, size_t fa2_len)
+{
+    int ret;
+    size_t needed_size;
+
+    if (tok == NULL) {
+        return EINVAL;
+    }
+
+    sss_authtok_set_empty(tok);
+
+    ret = sss_auth_pack_2fa_blob(fa1, fa1_len, fa2, fa2_len, NULL, 0,
+                                 &needed_size);
+    if (ret != EAGAIN) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "sss_auth_pack_2fa_blob unexpectedly returned [%d].\n", ret);
+        return EINVAL;
+    }
+
+    tok->data = talloc_size(tok, needed_size);
+    if (tok->data == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+        return ENOMEM;
+    }
+
+    ret = sss_auth_pack_2fa_blob(fa1, fa1_len, fa2, fa2_len, tok->data,
+                                 needed_size, &needed_size);
+    if (ret != EOK) {
+        talloc_free(tok->data);
+        DEBUG(SSSDBG_OP_FAILURE, "sss_auth_pack_2fa_blob failed.\n");
+        return ret;
+    }
+    tok->length = needed_size;
+    tok->type = SSS_AUTHTOK_TYPE_2FA;
+
+    return EOK;
+}
diff --git a/src/util/authtok.h b/src/util/authtok.h
index 1f6def4c3b6a1cbf6c4f34bb76c496ddae6f9d5f..e06ec2c07b68dfcc5dc6582bec6a5644ab3d2648 100644
--- a/src/util/authtok.h
+++ b/src/util/authtok.h
@@ -21,6 +21,7 @@
 #define __AUTHTOK_H__
 
 #include "util/util.h"
+#include "util/authtok-utils.h"
 #include "sss_client/sss_cli.h"
 
 /* Use sss_authtok_* accesor functions instead of struct sss_auth_token
@@ -179,4 +180,47 @@ void sss_authtok_wipe_password(struct sss_auth_token *tok);
  */
 struct sss_auth_token *sss_authtok_new(TALLOC_CTX *mem_ctx);
 
+/**
+ * @brief Set authtoken with 2FA data
+ *
+ * @param tok            A pointer to a sss_auth_token structure to change, also
+ *                       used as a memory context to allocate the internal data.
+ * @param[in]  fa1       First authentication factor, null terminated
+ * @param[in]  fa1_len   Length of the first authentication factor, if 0
+ *                       strlen() will be called internally
+ * @param[in]  fa2       Second authentication factor, null terminated
+ * @param[in]  fa2_len   Length of the second authentication factor, if 0
+ *                       strlen() will be called internally
+ *
+ * @return     EOK    on success
+ *             ENOMEM if memory allocation failed
+ *             EINVAL if input data is not consistent
+ */ 
+errno_t sss_authtok_set_2fa(struct sss_auth_token *tok,
+                            const char *fa1, size_t fa1_len,
+                            const char *fa2, size_t fa2_len);
+
+/**
+ * @brief Get 2FA factors from authtoken
+ *
+ * @param tok            A pointer to a sss_auth_token structure to change, also
+ *                       used as a memory context to allocate the internal data.
+ * @param[out] fa1       A pointer to a const char *, that will point to a
+ *                       null terminated string holding the first
+ *                       authentication factor
+ * @param[out] fa1_len   Length of the first authentication factor
+ * @param[out] fa2       A pointer to a const char *, that will point to a
+ *                       null terminated string holding the second
+ *                       authentication factor
+ * @param[out] fa2_len   Length of the second authentication factor
+ *
+ * @return     EOK     on success
+ *             ENOMEM  if memory allocation failed
+ *             EINVAL  if input data is not consistent
+ *             ENOENT  if the token is empty
+ *             EACCESS if the token is not a 2FA token
+ */ 
+errno_t sss_authtok_get_2fa(struct sss_auth_token *tok,
+                            const char **fa1, size_t *fa1_len,
+                            const char **fa2, size_t *fa2_len);
 #endif /*  __AUTHTOK_H__ */
-- 
2.1.0

-------------- next part --------------
From b802874fb8d36a876743694a430beb9da9b0b3ff Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 8 Jan 2015 17:10:42 +0100
Subject: [PATCH 03/14] pam: handle 2FA authentication token in the responder

---
 src/responder/pam/pamsrv_cmd.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 0eb78c723c0c066a9a2795f4ae8de85f18fcb2da..dd6574db7e0339aad16ba5806327e117e1735054 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -143,6 +143,10 @@ static int extract_authtok_v2(struct sss_auth_token *tok,
                                            auth_token_length);
         }
         break;
+    case SSS_AUTHTOK_TYPE_2FA:
+        ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA,
+                              auth_token_data, auth_token_length);
+        break;
     default:
         return EINVAL;
     }
-- 
2.1.0

-------------- next part --------------
From 8a71bce2a95d8c8da26013c6405cd72bbadc352a Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 17:24:50 +0100
Subject: [PATCH 04/14] Add pre-auth request

---
 src/providers/data_provider_be.c | 1 +
 src/providers/dp_pam_data_util.c | 2 ++
 src/providers/ipa/ipa_auth.c     | 1 +
 src/providers/krb5/krb5_auth.c   | 2 ++
 src/responder/pam/pamsrv_cmd.c   | 7 +++++++
 src/sss_client/sss_cli.h         | 4 ++++
 6 files changed, 17 insertions(+)

diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
index a48a42878d207785b664c06d3a6c1557dcab69b3..a37fbbc8d72d8bc0bf31b9b05a4aaf24c2f4c9e6 100644
--- a/src/providers/data_provider_be.c
+++ b/src/providers/data_provider_be.c
@@ -1374,6 +1374,7 @@ static int be_pam_handler(struct sbus_request *dbus_req, void *user_data)
 
     switch (pd->cmd) {
         case SSS_PAM_AUTHENTICATE:
+        case SSS_PAM_PREAUTH:
             target = BET_AUTH;
             break;
         case SSS_PAM_ACCT_MGMT:
diff --git a/src/providers/dp_pam_data_util.c b/src/providers/dp_pam_data_util.c
index 313948b369cf605c91eb608b9a394d32a1e128d1..8724bf936f3f46fb8393c8a3da57215a73b4191a 100644
--- a/src/providers/dp_pam_data_util.c
+++ b/src/providers/dp_pam_data_util.c
@@ -43,6 +43,8 @@ static const char *pamcmd2str(int cmd) {
         return "PAM_CHAUTHTOK";
     case SSS_PAM_CHAUTHTOK_PRELIM:
         return "PAM_CHAUTHTOK_PRELIM";
+    case SSS_PAM_PREAUTH:
+        return "SSS_PAM_PREAUTH";
     default:
         return "UNKNOWN";
     }
diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c
index 223448338ef7ad86259657b5bea2e47911b824cb..79e891b77205f75671c146af0192b764f12e13b0 100644
--- a/src/providers/ipa/ipa_auth.c
+++ b/src/providers/ipa/ipa_auth.c
@@ -208,6 +208,7 @@ void ipa_auth(struct be_req *be_req)
 
     switch (state->pd->cmd) {
         case SSS_PAM_AUTHENTICATE:
+        case SSS_PAM_PREAUTH:
             state->ipa_auth_ctx = talloc_get_type(
                                     be_ctx->bet_info[BET_AUTH].pvt_bet_data,
                                     struct ipa_auth_ctx);
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 25caf7b788a3f373f47e9d8aad38a2ea6fc12621..5ce45b1579f93d618da455b7ab2687c078332067 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -441,6 +441,8 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
                 goto done;
             }
             break;
+        case SSS_PAM_PREAUTH:
+            break;
         default:
             DEBUG(SSSDBG_CONF_SETTINGS, "Unexpected pam task %d.\n", pd->cmd);
             state->pam_status = PAM_SYSTEM_ERR;
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index dd6574db7e0339aad16ba5806327e117e1735054..f84349d1f60e4e7c858b9bc6303ade3a962172d1 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1455,6 +1455,12 @@ static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) {
     return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM);
 }
 
+static int pam_cmd_preauth(struct cli_ctx *cctx) {
+    DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n");
+    return pam_forwarder(cctx, SSS_PAM_PREAUTH);
+}
+
+
 struct cli_protocol_version *register_cli_protocol_version(void)
 {
     static struct cli_protocol_version pam_cli_protocol_version[] = {
@@ -1478,6 +1484,7 @@ struct sss_cmd_table *get_pam_cmds(void)
         {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session},
         {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok},
         {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim},
+        {SSS_PAM_PREAUTH, pam_cmd_preauth},
         {SSS_CLI_NULL, NULL}
     };
 
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 9a19d7d47d0a9d7dabeac36dc2c866c3420ef501..2895659b9c3ed4ab520ca90846379c22fd9567f7 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -220,6 +220,10 @@ enum sss_cli_command {
     SSS_CMD_RENEW            = 0x00F8, /**< Renew a credential with a limited
                                         * lifetime, e.g. a Kerberos Ticket
                                         * Granting Ticket (TGT) */
+    SSS_PAM_PREAUTH          = 0x00F9, /**< Request which can be run before
+                                        * an authentication request to find
+                                        * out which authentication methods
+                                        * are available for the given user. */
 
 /* PAC responder calls */
     SSS_PAC_ADD_PAC_USER     = 0x0101,
-- 
2.1.0

-------------- next part --------------
From 5d066679dbca2e6b8aa11b591b04678788f36529 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 17:26:53 +0100
Subject: [PATCH 05/14] krb5-child: add preauth and split 2fa token support

---
 src/providers/krb5/krb5_auth.c          |   3 +-
 src/providers/krb5/krb5_child.c         | 289 ++++++++++++++++++++++++++++----
 src/providers/krb5/krb5_child_handler.c |   4 +
 src/sss_client/sss_cli.h                |   6 +
 4 files changed, 268 insertions(+), 34 deletions(-)

diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 5ce45b1579f93d618da455b7ab2687c078332067..651a9201756be8bc80199636b494b2773cff9c3e 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -394,7 +394,8 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
     switch (pd->cmd) {
         case SSS_PAM_AUTHENTICATE:
         case SSS_PAM_CHAUTHTOK:
-            if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD) {
+            if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD
+                    && authtok_type != SSS_AUTHTOK_TYPE_2FA) {
                 /* handle empty password gracefully */
                 if (authtok_type == SSS_AUTHTOK_TYPE_EMPTY) {
                     DEBUG(SSSDBG_CRIT_FAILURE,
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 4164e75c9918392d0f37516853896436bcda2459..f22726332b248a21536ccfd80cc45afdf0d43513 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -54,6 +54,9 @@ struct krb5_req {
     char* name;
     krb5_creds *creds;
     bool otp;
+    char *otp_vendor;
+    char *otp_token_id;
+    char *otp_challenge;
     krb5_get_init_creds_opt *options;
 
     struct pam_data *pd;
@@ -268,7 +271,88 @@ static int token_pin_destructor(char *mem)
     return 0;
 }
 
-static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
+static krb5_error_code tokeninfo_matches_2fa(TALLOC_CTX *mem_ctx,
+                                         const krb5_responder_otp_tokeninfo *ti,
+                                         const char *fa1, size_t fa1_len,
+                                         const char *fa2, size_t fa2_len,
+                                         char **out_token, char **out_pin)
+{
+    char *token = NULL, *pin = NULL;
+    checker check = NULL;
+    int i;
+
+
+    if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_NEXTOTP) {
+        return ENOTSUP;
+    }
+
+    if (ti->challenge != NULL) {
+        return ENOTSUP;
+    }
+
+    /* This is a non-sensical value. */
+    if (ti->length == 0) {
+        return EPROTO;
+    }
+
+    if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) {
+        if (ti->length > 0 && ti->length != fa2_len) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Expected [%d] and  given [%zu] token size " \
+                  "do not match.\n", ti->length, fa2_len);
+            return EMSGSIZE;
+        }
+
+        if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) {
+            if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) {
+
+                pin = talloc_strndup(mem_ctx, fa1, fa1_len);
+                if (pin == NULL) {
+                    talloc_free(token);
+                    return ENOMEM;
+                }
+                talloc_set_destructor(pin, token_pin_destructor);
+
+                token = talloc_strndup(mem_ctx, fa2, fa2_len);
+                if (token == NULL) {
+                    return ENOMEM;
+                }
+                talloc_set_destructor(token, token_pin_destructor);
+
+                check = pick_checker(ti->format);
+            }
+        } else {
+            token = talloc_asprintf(mem_ctx, "%s%s", fa1, fa2);
+            if (token == NULL) {
+                return ENOMEM;
+            }
+            talloc_set_destructor(token, token_pin_destructor);
+
+            check = pick_checker(ti->format);
+        }
+    } else {
+        /* Assuming PIN only required */
+        pin = talloc_strndup(mem_ctx, fa1, fa1_len);
+        if (pin == NULL) {
+            return ENOMEM;
+        }
+        talloc_set_destructor(pin, token_pin_destructor);
+    }
+
+    /* If check is set, we need to verify the contents of the token. */
+    for (i = 0; check != NULL && token[i] != '\0'; i++) {
+        if (!check(token[i])) {
+            talloc_free(token);
+            talloc_free(pin);
+            return EBADMSG;
+        }
+    }
+
+    *out_token = token;
+    *out_pin = pin;
+    return 0;
+}
+static krb5_error_code tokeninfo_matches_pwd(TALLOC_CTX *mem_ctx,
                                          const krb5_responder_otp_tokeninfo *ti,
                                          const char *pwd, size_t len,
                                          char **out_token, char **out_pin)
@@ -364,15 +448,52 @@ static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
     return 0;
 }
 
+static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
+                                         const krb5_responder_otp_tokeninfo *ti,
+                                         struct sss_auth_token *auth_tok,
+                                         char **out_token, char **out_pin)
+{
+    int ret;
+    const char *pwd;
+    size_t len;
+    const char *fa2;
+    size_t fa2_len;
+
+    switch (sss_authtok_get_type(auth_tok)) {
+    case SSS_AUTHTOK_TYPE_PASSWORD:
+        ret = sss_authtok_get_password(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:
+        ret = sss_authtok_get_2fa(auth_tok, &pwd, &len, &fa2, &fa2_len);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_2fa failed.\n");
+            return ret;
+        }
+
+        return tokeninfo_matches_2fa(mem_ctx, ti, pwd, len, fa2, fa2_len,
+                                     out_token, out_pin);
+        break;
+    default:
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type.\n");
+    }
+
+    return EINVAL;
+}
+
 static krb5_error_code answer_otp(krb5_context ctx,
                                   struct krb5_req *kr,
                                   krb5_responder_context rctx)
 {
     krb5_responder_otp_challenge *chl;
     char *token = NULL, *pin = NULL;
-    const char *pwd = NULL;
     krb5_error_code ret;
-    size_t i, len;
+    size_t i;
 
     ret = krb5_responder_otp_get_challenge(ctx, rctx, &chl);
     if (ret != EOK || chl == NULL) {
@@ -388,14 +509,37 @@ static krb5_error_code answer_otp(krb5_context ctx,
 
     kr->otp = true;
 
-    /* Validate our assumptions about the contents of authtok. */
-    ret = sss_authtok_get_password(kr->pd->authtok, &pwd, &len);
-    if (ret != EOK)
-        goto done;
+    if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+        for (i = 0; chl->tokeninfo[i] != NULL; i++) {
+            DEBUG(SSSDBG_TRACE_ALL, "[%d] Vendor [%s].\n",
+                                    i, chl->tokeninfo[i]->vendor);
+            DEBUG(SSSDBG_TRACE_ALL, "[%d] Token-ID [%s].\n",
+                                    i, chl->tokeninfo[i]->token_id);
+            DEBUG(SSSDBG_TRACE_ALL, "[%d] Challenge [%s].\n",
+                                    i, chl->tokeninfo[i]->challenge);
+            DEBUG(SSSDBG_TRACE_ALL, "[%d] Flags [%d].\n",
+                                    i, chl->tokeninfo[i]->flags);
+        }
+
+        if (chl->tokeninfo[0]->vendor != NULL) {
+            kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[i]->vendor);
+        }
+        if (chl->tokeninfo[0]->token_id != NULL) {
+            kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[i]->token_id);
+        }
+        if (chl->tokeninfo[0]->challenge != NULL) {
+            kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[i]->challenge);
+        }
+        /* Allocation errors are ignored on purpose */
+
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Exit answer_otp during pre-auth.\n");
+        return EAGAIN;
+    }
 
     /* Find the first supported tokeninfo which matches our authtoken. */
     for (i = 0; chl->tokeninfo[i] != NULL; i++) {
-        ret = tokeninfo_matches(kr, chl->tokeninfo[i], pwd, len, &token, &pin);
+        ret = tokeninfo_matches(kr, chl->tokeninfo[i], kr->pd->authtok,
+                                &token, &pin);
         if (ret == EOK) {
             break;
         }
@@ -715,6 +859,56 @@ static errno_t pack_response_packet(TALLOC_CTX *mem_ctx, errno_t error,
     return EOK;
 }
 
+static errno_t k5c_attach_otp_info_msg(struct krb5_req *kr)
+{
+    uint8_t *msg = NULL;
+    size_t msg_len;
+    int ret;
+    size_t vendor_len = 0;
+    size_t token_id_len = 0;
+    size_t challenge_len = 0;
+
+    msg_len = 3;
+    if (kr->otp_vendor != NULL) {
+        vendor_len = strlen(kr->otp_vendor);
+        msg_len += vendor_len;
+    }
+
+    if (kr->otp_token_id != NULL) {
+        token_id_len = strlen(kr->otp_token_id);
+        msg_len += token_id_len;
+    }
+
+    if (kr->otp_challenge != NULL) {
+        challenge_len = strlen(kr->otp_challenge);
+        msg_len += challenge_len;
+    }
+
+    msg = talloc_zero_size(kr, msg_len);
+    if (msg == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+        return ENOMEM;
+    }
+
+    if (kr->otp_vendor != NULL) {
+        memcpy(msg, kr->otp_vendor, vendor_len);
+    }
+
+    if (kr->otp_token_id != NULL) {
+        memcpy(msg + vendor_len + 1, kr->otp_token_id, token_id_len);
+    }
+
+    if (kr->otp_challenge != NULL) {
+        memcpy(msg + vendor_len + token_id_len + 2 , kr->otp_challenge,
+               challenge_len);
+    }
+
+    ret = pam_add_response(kr->pd, SSS_PAM_OTP_INFO, msg_len, msg);
+    talloc_zfree(msg);
+
+    return ret;
+}
+
 static errno_t k5c_attach_ccname_msg(struct krb5_req *kr)
 {
     char *msg = NULL;
@@ -1024,12 +1218,20 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
     DEBUG(SSSDBG_TRACE_FUNC,
           "Attempting kinit for realm [%s]\n",realm_name);
     kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
-                                        discard_const(password),
-                                        sss_krb5_prompter, kr, 0,
-                                        NULL, kr->options);
-    if (kerr != 0) {
-        KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
-        return kerr;
+                      discard_const(password), sss_krb5_prompter, kr, 0,
+                      NULL, kr->options);
+    if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+        /* Any errors are ignored during pre-auth, only data is collected to
+         * be send back to the client.*/
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "krb5_get_init_creds_password returned [%d} during pre-auth.\n",
+              kerr);
+        return 0;
+    } else {
+        if (kerr != 0) {
+            KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+            return kerr;
+        }
     }
 
     if (kr->validate) {
@@ -1330,27 +1532,38 @@ static errno_t tgt_req_child(struct krb5_req *kr)
 
     DEBUG(SSSDBG_TRACE_LIBS, "Attempting to get a TGT\n");
 
-    ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
-    switch (ret) {
-        case EOK:
-            break;
+    /* No password is needed for pre-auth, or if we have 2FA */
+    if (kr->pd->cmd != SSS_PAM_PREAUTH
+            && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA) {
+        ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
+        switch (ret) {
+            case EOK:
+                break;
 
-        case EACCES:
-            DEBUG(SSSDBG_OP_FAILURE, "Invalid authtok type\n");
-            return ERR_INVALID_CRED_TYPE;
-            break;
+            case EACCES:
+                DEBUG(SSSDBG_OP_FAILURE, "Invalid authtok type\n");
+                return ERR_INVALID_CRED_TYPE;
+                break;
 
-        default:
-            DEBUG(SSSDBG_OP_FAILURE, "No credentials available\n");
-            return ERR_NO_CREDS;
-            break;
+            default:
+                DEBUG(SSSDBG_OP_FAILURE, "No credentials available\n");
+                return ERR_NO_CREDS;
+                break;
+        }
     }
 
     kerr = get_and_save_tgt(kr, password);
 
     if (kerr != KRB5KDC_ERR_KEY_EXP) {
-        if (kerr == 0) {
-            kerr = k5c_attach_ccname_msg(kr);
+        if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+            /* add OTP tokeninfo messge if available */
+            if (kr->otp) {
+                kerr = k5c_attach_otp_info_msg(kr);
+            }
+        } else {
+            if (kerr == 0) {
+                kerr = k5c_attach_ccname_msg(kr);
+            }
         }
         ret = map_krb5_error(kerr);
         goto done;
@@ -1551,6 +1764,10 @@ 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:
+        ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA, (buf + *p),
+                              auth_token_length);
+        break;
     default:
         return EINVAL;
     }
@@ -2314,11 +2531,13 @@ static krb5_error_code privileged_krb5_setup(struct krb5_req *kr,
     }
 
     /* For ccache types FILE: and DIR: we might need to create some directory
-     * components as root */
-    ret = k5c_ccache_setup(kr, offline);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
-        return ret;
+     * components as root. Cache files are not needed during preauth. */
+    if (kr->pd->cmd != SSS_PAM_PREAUTH) {
+        ret = k5c_ccache_setup(kr, offline);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
+            return ret;
+        }
     }
 
     if (!(offline ||
@@ -2493,6 +2712,10 @@ int main(int argc, const char *argv[])
         DEBUG(SSSDBG_TRACE_FUNC, "Will perform ticket renewal\n");
         ret = renew_tgt_child(kr);
         break;
+    case SSS_PAM_PREAUTH:
+        DEBUG(SSSDBG_TRACE_FUNC, "Will perform pre-auth\n");
+        ret = tgt_req_child(kr);
+        break;
     default:
         DEBUG(SSSDBG_CRIT_FAILURE,
               "PAM command [%d] not supported.\n", kr->pd->cmd);
diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
index 28ed0cb1c3daf54b629d0b88fb35d50f72aa0f39..423084e9b1b8e2102e2cdf95df52cd835f15b5cd 100644
--- a/src/providers/krb5/krb5_child_handler.c
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -77,6 +77,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:
+        data =  (char *) sss_authtok_get_data(tok);
+        auth_token_length = sss_authtok_get_size(tok);
+        break;
     default:
         ret = EINVAL;
     }
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 2895659b9c3ed4ab520ca90846379c22fd9567f7..1d7e8549cd548b00eeedba95080f346439afc3dd 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -402,6 +402,12 @@ enum response_type {
                           * the user.This should only be used in the case where
                           * it is not possile to use SSS_PAM_USER_INFO.
                           * @param A zero terminated string. */
+    SSS_PAM_OTP_INFO,    /**< A message which optionally may contain the name
+                          * of the vendor, the ID of an OTP token and a
+                          * challenge.
+                          * @param Three zero terminated strings, if one of the
+                          * strings is missing the message will contain only
+                          * an empty string (\0) for that component. */
     SSS_OTP,             /**< Indicates that the autotok was a OTP, so don't
                           * cache it. There is no message.
                           * @param None. */
-- 
2.1.0

-------------- next part --------------
From 7373a6d7fa1b07b8eb692422f52d40340136acb4 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 11:19:46 +0100
Subject: [PATCH 06/14] IPA: create preauth indicator file at startup

---
 src/providers/ipa/ipa_init.c | 66 ++++++++++++++++++++++++++++++++++++++++++++
 src/sss_client/sss_cli.h     |  2 ++
 2 files changed, 68 insertions(+)

diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c
index 4b26e8baad4d0592729aec9a0b188ae89973fa98..1560da1e8a89c67453e79243acc378abdd30d565 100644
--- a/src/providers/ipa/ipa_init.c
+++ b/src/providers/ipa/ipa_init.c
@@ -371,6 +371,62 @@ done:
     return ret;
 }
 
+void cleanup_ipa_preauth_indicator(void)
+{
+    int ret;
+
+    ret = unlink(PAM_PREAUTH_INDICATOR);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Failed to remove preauth indicator file [%s].\n",
+              PAM_PREAUTH_INDICATOR);
+    }
+}
+
+static errno_t create_ipa_preauth_indicator(void)
+{
+    int ret;
+    TALLOC_CTX *tmp_ctx = NULL;
+    int fd;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+        return ENOMEM;
+    }
+
+    fd = open(PAM_PREAUTH_INDICATOR, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW,
+              0644);
+    if (fd < 0) {
+        if (errno != EEXIST) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "Failed to create preauth indicator file [%s].\n",
+                  PAM_PREAUTH_INDICATOR);
+            ret = EOK;
+            goto done;
+        }
+
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Preauth indicator file [%s] already exists. " \
+              "Maybe it is left after an unplanned exit. Continuing.\n",
+              PAM_PREAUTH_INDICATOR);
+    } else {
+        close(fd);
+    }
+
+    ret =  atexit(cleanup_ipa_preauth_indicator);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "atexit failed. Continuing.\n");
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
 int sssm_ipa_auth_init(struct be_ctx *bectx,
                        struct bet_ops **ops,
                        void **pvt_data)
@@ -469,6 +525,16 @@ int sssm_ipa_auth_init(struct be_ctx *bectx,
         goto done;
     }
 
+    ret = create_ipa_preauth_indicator();
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Failed to create preauth indicator file, special password " \
+              "prompting might not be available.\n");
+        sss_log(SSSDBG_CRIT_FAILURE,
+                "Failed to create preauth indicator file, special password " \
+                "prompting might not be available.\n");
+    }
+
     *ops = &ipa_auth_ops;
     *pvt_data = ipa_auth_ctx;
     ret = EOK;
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 1d7e8549cd548b00eeedba95080f346439afc3dd..317700ef8cfcbb1b58e2a7d1ffcc7f00658fe815 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -317,6 +317,8 @@ enum sss_authtok_type {
 #define SSS_START_OF_PAM_REQUEST 0x4d415049
 #define SSS_END_OF_PAM_REQUEST 0x4950414d
 
+#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available"
+
 enum pam_item_type {
     SSS_PAM_ITEM_EMPTY = 0x0000,
     SSS_PAM_ITEM_USER,
-- 
2.1.0

-------------- next part --------------
From ba4db837842cebc07aaef08f05fd8722c0974d30 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 12 Feb 2015 23:08:12 +0100
Subject: [PATCH 07/14] pam_sss: add pre-auth and 2fa support

---
 Makefile.am              |   1 +
 src/sss_client/pam_sss.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 220 insertions(+), 2 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 9c90e05619b59326a0079889de1d54ee23f66dee..e7fddeee619639c70887e15349e4b03e2b5b551c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2424,6 +2424,7 @@ pam_sss_la_SOURCES = \
     src/sss_client/common.c \
     src/sss_client/sss_cli.h \
     src/util/atomic_io.c \
+    src/util/authtok-utils.c \
     src/sss_client/sss_pam_macros.h \
     src/sss_client/sss_pam_compat.h
 
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 83769a15337f7ba027f66a8f7e530daa4a287383..98afaa779c95396295715413507bc7a9e40ee699 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -51,6 +51,7 @@
 #define FLAGS_USE_AUTHTOK    (1 << 2)
 #define FLAGS_IGNORE_UNKNOWN_USER (1 << 3)
 #define FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4)
+#define FLAGS_USE_2FA (1 << 5)
 
 #define PWEXP_FLAG "pam_sss:password_expired_flag"
 #define FD_DESTRUCTOR "pam_sss:fd_destructor"
@@ -88,6 +89,10 @@ struct pam_items {
     char *domain_name;
     const char *requested_domains;
     size_t requested_domains_size;
+    char *otp_vendor;
+    char *otp_token_id;
+    char *otp_challenge;
+    char *first_factor;
 };
 
 #define DEBUG_MGS_LEN 1024
@@ -223,6 +228,12 @@ static void overwrite_and_free_authtoks(struct pam_items *pi)
         pi->pam_newauthtok = NULL;
     }
 
+    if (pi->first_factor != NULL) {
+        _pam_overwrite_n((void *)pi->first_factor,  strlen(pi->first_factor));
+        free((void *)pi->first_factor);
+        pi->first_factor = NULL;
+    }
+
     pi->pamstack_authtok = NULL;
     pi->pamstack_oldauthtok = NULL;
 }
@@ -233,6 +244,15 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
 
     free(pi->domain_name);
     pi->domain_name = NULL;
+
+    free(pi->otp_vendor);
+    pi->otp_vendor = NULL;
+
+    free(pi->otp_token_id);
+    pi->otp_token_id = NULL;
+
+    free(pi->otp_challenge);
+    pi->otp_challenge = NULL;
 }
 
 static int pack_message_v3(struct pam_items *pi, size_t *size,
@@ -968,6 +988,7 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
     int32_t type;
     int32_t len;
     int32_t pam_status;
+    size_t offset;
 
     if (buflen < (2*sizeof(int32_t))) {
         D(("response buffer is too small"));
@@ -1074,6 +1095,41 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
                        pam_strerror(pamh,ret)));
                 }
                 break;
+            case SSS_PAM_OTP_INFO:
+                if (buf[p + (len -1)] != '\0') {
+                    D(("system info does not end with \\0."));
+                    break;
+                }
+
+                pi->otp_vendor = strdup((char *) &buf[p]);
+                if (pi->otp_vendor == NULL) {
+                    D(("strdup failed"));
+                    break;
+                }
+
+                offset = strlen(pi->otp_vendor) + 1;
+                if (offset >= len) {
+                    D(("OTP message size mismatch"));
+                    break;
+                }
+                pi->otp_token_id = strdup((char *) &buf[p + offset]);
+                if (pi->otp_token_id == NULL) {
+                    D(("strdup failed"));
+                    break;
+                }
+
+                offset += strlen(pi->otp_token_id) + 1;
+                if (offset >= len) {
+                    D(("OTP message size mismatch"));
+                    break;
+                }
+                pi->otp_challenge = strdup((char *) &buf[p + offset]);
+                if (pi->otp_challenge == NULL) {
+                    D(("strdup failed"));
+                    break;
+                }
+
+                break;
             default:
                 D(("Unknown response type [%d]", type));
         }
@@ -1095,6 +1151,7 @@ static int get_pam_items(pam_handle_t *pamh, struct pam_items *pi)
     pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY;
     pi->pam_newauthtok = NULL;
     pi->pam_newauthtok_size = 0;
+    pi->first_factor = NULL;
 
     ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service));
     if (ret != PAM_SUCCESS) return ret;
@@ -1149,6 +1206,10 @@ static int get_pam_items(pam_handle_t *pamh, struct pam_items *pi)
     if (pi->requested_domains == NULL) pi->requested_domains = "";
     pi->requested_domains_size = strlen(pi->requested_domains) + 1;
 
+    pi->otp_vendor = NULL;
+    pi->otp_token_id = NULL;
+    pi->otp_challenge = NULL;
+
     return PAM_SUCCESS;
 }
 
@@ -1280,6 +1341,7 @@ static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi,
         case SSS_PAM_OPEN_SESSION:
         case SSS_PAM_SETCRED:
         case SSS_PAM_CLOSE_SESSION:
+        case SSS_PAM_PREAUTH:
             break;
         default:
             D(("Illegal task [%d]", task));
@@ -1327,6 +1389,128 @@ static int prompt_password(pam_handle_t *pamh, struct pam_items *pi,
     return PAM_SUCCESS;
 }
 
+static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi,
+                      const char *prompt_fa1, const char *prompt_fa2)
+{
+    int ret;
+    struct pam_conv *conv;
+    struct pam_message *mesg[2] = {NULL, NULL};
+    struct pam_response *resp = NULL;
+    size_t needed_size;
+
+    ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
+    if (ret != PAM_SUCCESS) {
+        return ret;
+    }
+
+    mesg[0] = malloc(sizeof(struct pam_message));
+    if (mesg[0] == NULL) {
+        D(("Malloc failed."));
+        return PAM_SYSTEM_ERR;
+    }
+
+    mesg[1] = malloc(sizeof(struct pam_message));
+    if (mesg[0] == NULL) {
+        D(("Malloc failed."));
+        free(mesg[0]);
+        return PAM_SYSTEM_ERR;
+    }
+    mesg[0]->msg_style = PAM_PROMPT_ECHO_OFF;
+    mesg[0]->msg = prompt_fa1;
+    mesg[1]->msg_style = PAM_PROMPT_ECHO_OFF;
+    mesg[1]->msg = prompt_fa2;
+
+    ret=conv->conv(2, mesg, &resp, conv->appdata_ptr);
+    free(mesg[0]);
+    free(mesg[1]);
+    if (ret != PAM_SUCCESS) {
+        D(("Conversation failure: %s.",  pam_strerror(pamh,ret)));
+        return ret;
+    }
+
+    if (resp == NULL) {
+        D(("response expected, but resp==NULL"));
+        return PAM_SYSTEM_ERR;
+    }
+
+    if (resp[0].resp == NULL || *(resp[0].resp) == '\0') {
+        D(("Missing factor."));
+        ret = PAM_CRED_INSUFFICIENT;
+        goto done;
+    }
+
+    if (resp[1].resp == NULL || *(resp[1].resp) == '\0'
+            || (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0
+                    && strcmp(resp[0].resp, resp[1].resp) == 0)) {
+        /* Missing second factor, assume first factor contains combined 2FA
+         * credentials.
+         * Special handling for SSH with password authentication. Combined
+         * 2FA credentials are used but SSH puts them in both responses. */
+
+        pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE);
+        if (pi->pam_authtok == NULL) {
+            D(("strndup failed."));
+            ret = PAM_BUF_ERR;
+            goto done;
+        }
+        pi->pam_authtok_size = strlen(pi->pam_authtok) + 1;
+        pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+    } else {
+
+        ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0,
+                                     &needed_size);
+        if (ret != EAGAIN) {
+            D(("sss_auth_pack_2fa_blob failed."));
+            ret = PAM_BUF_ERR;
+            goto done;
+        }
+
+        pi->pam_authtok = malloc(needed_size);
+        if (pi->pam_authtok == NULL) {
+            D(("malloc failed."));
+            ret = PAM_BUF_ERR;
+            goto done;
+        }
+
+        ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0,
+                                     pi->pam_authtok, needed_size,
+                                     &needed_size);
+        if (ret != EOK) {
+            D(("sss_auth_pack_2fa_blob failed."));
+            ret = PAM_BUF_ERR;
+            goto done;
+        }
+
+        pi->pam_authtok_size = needed_size;
+        pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA;
+        pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE);
+        if (pi->first_factor == NULL) {
+            D(("strndup failed."));
+            ret = PAM_BUF_ERR;
+            goto done;
+        }
+    }
+
+    ret = PAM_SUCCESS;
+
+done:
+    if (resp != NULL) {
+        if (resp[0].resp != NULL) {
+            _pam_overwrite((void *)resp[0].resp);
+            free(resp[0].resp);
+        }
+        if (resp[1].resp != NULL) {
+            _pam_overwrite((void *)resp[1].resp);
+            free(resp[1].resp);
+        }
+
+        free(resp);
+        resp = NULL;
+    }
+
+    return ret;
+}
+
 static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi)
 {
     int ret;
@@ -1410,6 +1594,8 @@ static void eval_argv(pam_handle_t *pamh, int argc, const char **argv,
             *flags |= FLAGS_IGNORE_UNKNOWN_USER;
         } else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) {
             *flags |= FLAGS_IGNORE_AUTHINFO_UNAVAIL;
+        } else if (strcmp(*argv, "use_2fa") == 0) {
+            *flags |= FLAGS_USE_2FA;
         } else {
             logger(pamh, LOG_WARNING, "unknown option: %s", *argv);
         }
@@ -1433,14 +1619,28 @@ static int get_authtok_for_authentication(pam_handle_t *pamh,
         }
         pi->pam_authtok_size = strlen(pi->pam_authtok);
     } else {
-        ret = prompt_password(pamh, pi, _("Password: "));
+        if ( flags & FLAGS_USE_2FA
+                || (pi->otp_vendor != NULL && pi->otp_token_id != NULL
+                        && pi->otp_challenge != NULL)) {
+            ret = prompt_2fa(pamh, pi, _("First Factor: "),
+                               _("Second Factor: "));
+        } else {
+            ret = prompt_password(pamh, pi, _("Password: "));
+        }
         if (ret != PAM_SUCCESS) {
             D(("failed to get password from user"));
             return ret;
         }
 
         if (flags & FLAGS_FORWARD_PASS) {
-            ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok);
+            if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD) {
+                ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok);
+            } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA
+                            && pi->first_factor != NULL) {
+                ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor);
+            } else {
+                ret = EINVAL;
+            }
             if (ret != PAM_SUCCESS) {
                 D(("Failed to set PAM_AUTHTOK [%s], "
                    "authtok may not be available for other modules",
@@ -1576,6 +1776,23 @@ static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh,
 
         switch(task) {
             case SSS_PAM_AUTHENTICATE:
+                /* Only do preauth if
+                 * - FLAGS_USE_FIRST_PASS is not set
+                 * - no password is on the stack
+                 * - preauth indicator file exists. */
+                if ( !(flags & FLAGS_USE_FIRST_PASS) && pi.pam_authtok == NULL
+                        && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) {
+                    pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH,
+                                                  quiet_mode);
+                    if (pam_status != PAM_SUCCESS) {
+                        D(("send_and_receive returned [%d] during pre-auth",
+                           pam_status));
+                        /* Since we are only interested in the result message and
+                         * will always use password authentication as a fallback,
+                         * errors can be ignored here. */
+                    }
+                }
+
                 ret = get_authtok_for_authentication(pamh, &pi, flags);
                 if (ret != PAM_SUCCESS) {
                     D(("failed to get authentication token: %s",
-- 
2.1.0

-------------- next part --------------
From 13762a19a77b969a0d54400fed3b98eb9d8fc45c Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 13:00:14 +0100
Subject: [PATCH 08/14] Add cache_credentials_minimal_first_factor_length
 config option

---
 src/confdb/confdb.c          | 11 +++++++++++
 src/confdb/confdb.h          |  4 ++++
 src/config/SSSDConfigTest.py |  2 ++
 src/config/etc/sssd.api.conf |  1 +
 src/man/sssd.conf.5.xml      | 22 ++++++++++++++++++++++
 src/util/domain_info_utils.c |  2 ++
 6 files changed, 42 insertions(+)

diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c
index 5aee1ee069bf4ed9ac968588518a3350f573132b..cb458dfc2c8f14bf397ae70e8b829fbbbc2ddff2 100644
--- a/src/confdb/confdb.c
+++ b/src/confdb/confdb.c
@@ -985,6 +985,17 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb,
         goto done;
     }
 
+    ret = get_entry_as_uint32(res->msgs[0],
+                              &domain->cache_credentials_min_ff_length,
+                              CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH,
+                              CONFDB_DEFAULT_CACHE_CREDS_MIN_FF_LENGTH);
+    if(ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Invalid value for %s\n",
+              CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH);
+        goto done;
+    }
+
     ret = get_entry_as_bool(res->msgs[0], &domain->legacy_passwords,
                             CONFDB_DOMAIN_LEGACY_PASS, 0);
     if(ret != EOK) {
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 19c56402069f9a7001188e91f77db8ad8525d690..c8c91288cd63df7629a98802b7b5373df92d6ca4 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -162,6 +162,9 @@
 #define CONFDB_DOMAIN_MINID "min_id"
 #define CONFDB_DOMAIN_MAXID "max_id"
 #define CONFDB_DOMAIN_CACHE_CREDS "cache_credentials"
+#define CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH \
+                                 "cache_credentials_minimal_first_factor_length"
+#define CONFDB_DEFAULT_CACHE_CREDS_MIN_FF_LENGTH 8
 #define CONFDB_DOMAIN_LEGACY_PASS "store_legacy_passwords"
 #define CONFDB_DOMAIN_MPG "magic_private_groups"
 #define CONFDB_DOMAIN_FQ "use_fully_qualified_names"
@@ -221,6 +224,7 @@ struct sss_domain_info {
     uint32_t id_max;
 
     bool cache_credentials;
+    uint32_t cache_credentials_min_ff_length;
     bool legacy_passwords;
     bool case_sensitive;
     bool case_preserve;
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
index 3a5312ea945b5247c69e97b73565b7061e037b69..db16bc433cf4c47c6a15760d85b322a6655aa0c1 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -494,6 +494,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
             'command',
             'enumerate',
             'cache_credentials',
+            'cache_credentials_minimal_first_factor_length',
             'store_legacy_passwords',
             'use_fully_qualified_names',
             'ignore_group_members',
@@ -853,6 +854,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
             'command',
             'enumerate',
             'cache_credentials',
+            'cache_credentials_minimal_first_factor_length',
             'store_legacy_passwords',
             'use_fully_qualified_names',
             'ignore_group_members',
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index 4fa542704fbd3af065843e777b84b6305ec3e78b..f1ac6366c73c03fe5c60c79bfe7b15fde9382215 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -110,6 +110,7 @@ subdomain_enumerate = str, None, false
 force_timeout = int, None, false
 offline_timeout = int, None, false
 cache_credentials = bool, None, false
+cache_credentials_minimal_first_factor_length = int, None, false
 store_legacy_passwords = bool, None, false
 use_fully_qualified_names = bool, None, false
 ignore_group_members = bool, None, false
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 92c64b9fd337377ce301af63bbd146118328cd6d..fc34d8ee246a75099b4dfcfd58ed480a4ef12318 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -1392,6 +1392,28 @@ pam_account_expired_message = Account expired, please call help desk.
                         </para>
                     </listitem>
                 </varlistentry>
+
+                <varlistentry>
+                    <term>cache_credentials_minimal_first_factor_length (int)</term>
+                    <listitem>
+                        <para>
+                            If 2-Factor-Authentication (2FA) is used and
+                            credentials should be saved this value determines
+                            the minimal lenght the first authentication factor
+                            (long term password) must have to be saved as SHA512
+                            hash into the cache.
+                        </para>
+                        <para>
+                            This should avoid that the short PINs of a PIN based
+                            2FA scheme are saved in the cache which would make
+                            them easy targets for brute-force attacks.
+                        </para>
+                        <para>
+                            Default: 8
+                        </para>
+                    </listitem>
+                </varlistentry>
+
                 <varlistentry>
                     <term>account_cache_expiration (integer)</term>
                     <listitem>
diff --git a/src/util/domain_info_utils.c b/src/util/domain_info_utils.c
index a889bd7aad3a8435d1177cb44371df8d527160c0..dfa322f4b42347d6fcd88c724d8ac0f6994c840d 100644
--- a/src/util/domain_info_utils.c
+++ b/src/util/domain_info_utils.c
@@ -288,6 +288,8 @@ struct sss_domain_info *new_subdomain(TALLOC_CTX *mem_ctx,
     dom->id_max = parent->id_max ? parent->id_max : 0xffffffff;
     dom->pwd_expiration_warning = parent->pwd_expiration_warning;
     dom->cache_credentials = parent->cache_credentials;
+    dom->cache_credentials_min_ff_length =
+                                        parent->cache_credentials_min_ff_length;
     dom->case_sensitive = false;
     dom->user_timeout = parent->user_timeout;
     dom->group_timeout = parent->group_timeout;
-- 
2.1.0

-------------- next part --------------
From 3db40e2ae859a5f869eefaa265ad152260c11f1b Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 15:35:01 +0100
Subject: [PATCH 09/14] sysdb: add sysdb_cache_password_ex()

---
 src/db/sysdb.h          |  9 +++++++++
 src/db/sysdb_ops.c      | 25 ++++++++++++++++++++---
 src/tests/sysdb-tests.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 2a3a2df98fa1e51cf024b7bd57347e8b02449eff..c3d2c1406321e01b325e04e0e3f263893eb91b2e 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -24,6 +24,7 @@
 
 #include "util/util.h"
 #include "confdb/confdb.h"
+#include "sss_client/sss_cli.h"
 #include <tevent.h>
 
 #define CACHE_SYSDB_FILE "cache_%s.ldb"
@@ -105,6 +106,8 @@
 #define SYSDB_SERVERHOSTNAME "serverHostname"
 
 #define SYSDB_CACHEDPWD "cachedPassword"
+#define SYSDB_CACHEDPWD_TYPE "cachedPasswordType"
+#define SYSDB_CACHEDPWD_FA2_LEN "cachedPasswordSecondFactorLen"
 
 #define SYSDB_UUID "uniqueID"
 #define SYSDB_SID "objectSID"
@@ -888,6 +891,12 @@ int sysdb_cache_password(struct sss_domain_info *domain,
                          const char *username,
                          const char *password);
 
+int sysdb_cache_password_ex(struct sss_domain_info *domain,
+                            const char *username,
+                            const char *password,
+                            enum sss_authtok_type authtok_type,
+                            size_t second_factor_size);
+
 errno_t check_failed_login_attempts(struct confdb_ctx *cdb,
                                     struct ldb_message *ldb_msg,
                                     uint32_t *failed_login_attempts,
diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
index 1f84a60ba332d70529b2170c04415d7fc0704597..b4b2eb9fc2ba6055fcfc770601c04815464228cd 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -2234,9 +2234,11 @@ int sysdb_remove_group_member(struct sss_domain_info *domain,
 
 /* =Password-Caching====================================================== */
 
-int sysdb_cache_password(struct sss_domain_info *domain,
-                         const char *username,
-                         const char *password)
+int sysdb_cache_password_ex(struct sss_domain_info *domain,
+                            const char *username,
+                            const char *password,
+                            enum sss_authtok_type authtok_type,
+                            size_t second_factor_len)
 {
     TALLOC_CTX *tmp_ctx;
     struct sysdb_attrs *attrs;
@@ -2269,6 +2271,15 @@ int sysdb_cache_password(struct sss_domain_info *domain,
     ret = sysdb_attrs_add_string(attrs, SYSDB_CACHEDPWD, hash);
     if (ret) goto fail;
 
+    ret = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_TYPE, authtok_type);
+    if (ret) goto fail;
+
+    if (authtok_type == SSS_AUTHTOK_TYPE_2FA && second_factor_len > 0) {
+        ret = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_FA2_LEN,
+                                   second_factor_len);
+        if (ret) goto fail;
+    }
+
     /* FIXME: should we use a different attribute for chache passwords ?? */
     ret = sysdb_attrs_add_long(attrs, "lastCachedPasswordChange",
                                (long)time(NULL));
@@ -2293,6 +2304,14 @@ fail:
     return ret;
 }
 
+int sysdb_cache_password(struct sss_domain_info *domain,
+                         const char *username,
+                         const char *password)
+{
+    return sysdb_cache_password_ex(domain, username, password,
+                                   SSS_AUTHTOK_TYPE_PASSWORD, 0);
+}
+
 /* =Custom Search================== */
 
 int sysdb_search_custom(TALLOC_CTX *mem_ctx,
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index 7c2c6d208127b0b6f9025435619b027246c6dd35..9410287477e56905d4af3d2298085c10e039d231 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -1808,6 +1808,57 @@ START_TEST (test_sysdb_cache_password)
 }
 END_TEST
 
+START_TEST (test_sysdb_cache_password_ex)
+{
+    struct sysdb_test_ctx *test_ctx;
+    struct test_data *data;
+    int ret;
+    struct ldb_result *res;
+    const char *attrs[] = { SYSDB_CACHEDPWD_TYPE, SYSDB_CACHEDPWD_FA2_LEN,
+                            NULL };
+    int val;
+
+    /* Setup */
+    ret = setup_sysdb_tests(&test_ctx);
+    fail_unless(ret == EOK, "Could not set up the test");
+
+    data = talloc_zero(test_ctx, struct test_data);
+    data->ctx = test_ctx;
+    data->ev = test_ctx->ev;
+    data->username = talloc_asprintf(data, "testuser%d", _i);
+
+    ret = sysdb_get_user_attr(test_ctx, test_ctx->domain, data->username,
+                              attrs, &res);
+    fail_unless(ret == EOK, "sysdb_get_user_attr request failed [%d].", ret);
+
+    val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_TYPE, 0);
+    fail_unless(val == SSS_AUTHTOK_TYPE_PASSWORD,
+                "Unexptected authtok type, found [%d], expected [%d].",
+                val, SSS_AUTHTOK_TYPE_PASSWORD);
+
+    ret = sysdb_cache_password_ex(test_ctx->domain, data->username,
+                                  data->username, SSS_AUTHTOK_TYPE_2FA, 12);
+
+    fail_unless(ret == EOK, "sysdb_cache_password request failed [%d].", ret);
+
+    ret = sysdb_get_user_attr(test_ctx, test_ctx->domain, data->username,
+                              attrs, &res);
+    fail_unless(ret == EOK, "sysdb_get_user_attr request failed [%d].", ret);
+
+    val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_TYPE, 0);
+    fail_unless(val == SSS_AUTHTOK_TYPE_2FA,
+                "Unexptected authtok type, found [%d], expected [%d].",
+                val, SSS_AUTHTOK_TYPE_2FA);
+
+    val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_FA2_LEN, 0);
+    fail_unless(val == 12,
+                "Unexptected second factor lenght, found [%d], expected [%d].",
+                val, 12);
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
 static void cached_authentication_without_expiration(const char *username,
                                                      const char *password,
                                                      int expected_result)
@@ -6237,6 +6288,8 @@ Suite *create_sysdb_suite(void)
                         27010, 27011);
     tcase_add_loop_test(tc_sysdb, test_sysdb_cached_authentication, 27010, 27011);
 
+    tcase_add_loop_test(tc_sysdb, test_sysdb_cache_password_ex, 27010, 27011);
+
     /* ASQ search test */
     tcase_add_loop_test(tc_sysdb, test_sysdb_prepare_asq_test_user, 28011, 28020);
     tcase_add_test(tc_sysdb, test_sysdb_asq_search);
-- 
2.1.0

-------------- next part --------------
From 9f27405cbe6c947dfbf37583b6e48e9e6acd8c43 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 15:53:17 +0100
Subject: [PATCH 10/14] krb5: save hash of the first authentication factor to
 the cache

---
 src/providers/krb5/krb5_auth.c | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 651a9201756be8bc80199636b494b2773cff9c3e..162f6a1572238591a06f1e314a42810c8df029b1 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -265,6 +265,9 @@ static void krb5_auth_store_creds(struct sss_domain_info *domain,
                                   struct pam_data *pd)
 {
     const char *password = NULL;
+    const char *fa2;
+    size_t password_len;
+    size_t fa2_len = 0;
     int ret = EOK;
 
     switch(pd->cmd) {
@@ -276,7 +279,20 @@ static void krb5_auth_store_creds(struct sss_domain_info *domain,
             break;
         case SSS_PAM_AUTHENTICATE:
         case SSS_PAM_CHAUTHTOK_PRELIM:
-            ret = sss_authtok_get_password(pd->authtok, &password, NULL);
+            if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_2FA) {
+                ret = sss_authtok_get_2fa(pd->authtok, &password, &password_len,
+                                          &fa2, &fa2_len);
+                if (ret == EOK && password_len <
+                                      domain->cache_credentials_min_ff_length) {
+                    DEBUG(SSSDBG_FATAL_FAILURE,
+                          "First factor is too short to be cache, " \
+                          "minimum length is [%u].\n",
+                          domain->cache_credentials_min_ff_length);
+                    ret = EINVAL;
+                }
+            } else {
+                ret = sss_authtok_get_password(pd->authtok, &password, NULL);
+            }
             break;
         case SSS_PAM_CHAUTHTOK:
             ret = sss_authtok_get_password(pd->newauthtok, &password, NULL);
@@ -302,7 +318,8 @@ static void krb5_auth_store_creds(struct sss_domain_info *domain,
         return;
     }
 
-    ret = sysdb_cache_password(domain, pd->user, password);
+    ret = sysdb_cache_password_ex(domain, pd->user, password,
+                                  sss_authtok_get_type(pd->authtok), fa2_len);
     if (ret) {
         DEBUG(SSSDBG_OP_FAILURE,
               "Failed to cache password, offline auth may not work."
@@ -1018,7 +1035,10 @@ static void krb5_auth_done(struct tevent_req *subreq)
         goto done;
     }
 
-    if (state->be_ctx->domain->cache_credentials == TRUE && !res->otp) {
+    if (state->be_ctx->domain->cache_credentials == TRUE
+            && (!res->otp
+                || (res->otp && sss_authtok_get_type(pd->authtok) ==
+                                                       SSS_AUTHTOK_TYPE_2FA))) {
         krb5_auth_store_creds(state->domain, pd);
     }
 
-- 
2.1.0

-------------- next part --------------
From 0d074ffe4e9e49c041c72f66ee0734a608cf6230 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Wed, 25 Mar 2015 12:04:57 +0100
Subject: [PATCH 11/14] krb5: try delayed online authentication only for single
 factor auth

---
 src/providers/krb5/krb5_auth.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 162f6a1572238591a06f1e314a42810c8df029b1..7aa11c46515d7560cf73b4e21ffaa66472e9db60 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -207,6 +207,13 @@ static void krb5_auth_cache_creds(struct krb5_ctx *krb5_ctx,
     const char *password = NULL;
     errno_t ret;
 
+    if (sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_PASSWORD) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Delayed authentication is only available for password " \
+              "authentication (single factor).\n");
+        return;
+    }
+
     ret = sss_authtok_get_password(pd->authtok, &password, NULL);
     if (ret != EOK) {
         DEBUG(SSSDBG_FATAL_FAILURE,
-- 
2.1.0

-------------- next part --------------
From 11f795fcd7ec3ddaeb6e674818d415d14050d600 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Fri, 27 Mar 2015 15:20:13 +0100
Subject: [PATCH 12/14] 2FA offline auth

---
 src/db/sysdb_ops.c             | 77 ++++++++++++++++++++++++++++++++++++++++--
 src/responder/pam/pamsrv_cmd.c | 35 +++++++++++++++++--
 2 files changed, 107 insertions(+), 5 deletions(-)

diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
index b4b2eb9fc2ba6055fcfc770601c04815464228cd..d98ef8ddac4ca6f10efba594254fc6faf437fc8e 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -3161,6 +3161,76 @@ done:
     return ret;
 }
 
+static errno_t check_for_combined_2fa_password(struct sss_domain_info *domain,
+                                               struct ldb_message *ldb_msg,
+                                               const char *password,
+                                               const char *userhash)
+{
+
+    unsigned int cached_authtok_type;
+    unsigned int cached_fa2_len;
+    char *short_pw;
+    char *comphash;
+    size_t pw_len;
+    TALLOC_CTX *tmp_ctx;
+    int ret;
+
+    cached_authtok_type = ldb_msg_find_attr_as_uint(ldb_msg,
+                                                    SYSDB_CACHEDPWD_TYPE,
+                                                    SSS_AUTHTOK_TYPE_EMPTY);
+    if (cached_authtok_type != SSS_AUTHTOK_TYPE_2FA) {
+        DEBUG(SSSDBG_TRACE_LIBS, "Wrong authtok type.\n");
+        return EINVAL;
+    }
+
+    cached_fa2_len = ldb_msg_find_attr_as_uint(ldb_msg, SYSDB_CACHEDPWD_FA2_LEN,
+                                               0);
+    if (cached_fa2_len == 0) {
+        DEBUG(SSSDBG_TRACE_LIBS, "Second factor size not available.\n");
+        return EINVAL;
+    }
+
+    pw_len = strlen(password);
+    if (pw_len < cached_fa2_len + domain->cache_credentials_min_ff_length) {
+        DEBUG(SSSDBG_TRACE_LIBS, "Password too short.\n");
+        return EINVAL;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+        return ENOMEM;
+    }
+
+    short_pw = talloc_strndup(tmp_ctx , password, (pw_len - cached_fa2_len));
+    if (short_pw == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = s3crypt_sha512(tmp_ctx, short_pw, userhash, &comphash);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CONF_SETTINGS, "Failed to create password hash.\n");
+        ret = ERR_INTERNAL;
+        goto done;
+    }
+
+    if (strcmp(userhash, comphash) != 0) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Hash of shorten password does not match.\n");
+        ret = ERR_AUTH_FAILED;
+        goto done;
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
 int sysdb_cache_auth(struct sss_domain_info *domain,
                      const char *name,
                      const char *password,
@@ -3174,7 +3244,8 @@ int sysdb_cache_auth(struct sss_domain_info *domain,
                             SYSDB_LAST_LOGIN, SYSDB_LAST_ONLINE_AUTH,
                             "lastCachedPasswordChange",
                             "accountExpires", SYSDB_FAILED_LOGIN_ATTEMPTS,
-                            SYSDB_LAST_FAILED_LOGIN, NULL };
+                            SYSDB_LAST_FAILED_LOGIN, SYSDB_CACHEDPWD_TYPE,
+                            SYSDB_CACHEDPWD_FA2_LEN, NULL };
     struct ldb_message *ldb_msg;
     const char *userhash;
     char *comphash;
@@ -3285,7 +3356,9 @@ int sysdb_cache_auth(struct sss_domain_info *domain,
         goto done;
     }
 
-    if (strcmp(userhash, comphash) == 0) {
+    if (strcmp(userhash, comphash) == 0
+            || check_for_combined_2fa_password(domain, ldb_msg,
+                                               password, userhash) == EOK ) {
         /* TODO: probable good point for audit logging */
         DEBUG(SSSDBG_CONF_SETTINGS, "Hashes do match!\n");
         authentication_successful = true;
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index f84349d1f60e4e7c858b9bc6303ade3a962172d1..20aeda41e143f10faa9319fea6b2a9ff9041ed55 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -528,6 +528,34 @@ static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te,
     pam_reply(preq);
 }
 
+static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok,
+                                           const char **password)
+{
+    int ret;
+    size_t pw_len;
+    const char *fa2;
+    size_t fa2_len;
+
+    switch (sss_authtok_get_type(authtok)) {
+    case SSS_AUTHTOK_TYPE_PASSWORD:
+        ret = sss_authtok_get_password(authtok, password, NULL);
+        break;
+    case SSS_AUTHTOK_TYPE_2FA:
+        ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len);
+        break;
+    default:
+        DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n",
+              sss_authtok_get_type(authtok));
+        ret = EINVAL;
+    }
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n");
+        return ret;
+    }
+
+    return EOK;
+}
+
 static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd);
 static void pam_handle_cached_login(struct pam_auth_req *preq, int ret,
                                     time_t expire_date, time_t delayed_until);
@@ -587,9 +615,10 @@ static void pam_reply(struct pam_auth_req *preq)
                     goto done;
                 }
 
-                ret = sss_authtok_get_password(pd->authtok, &password, NULL);
-                if (ret) {
-                    DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n");
+                ret = get_password_for_cache_auth(pd->authtok, &password);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_FATAL_FAILURE,
+                          "get_password_and_type_for_cache_auth failed.\n");
                     goto done;
                 }
 
-- 
2.1.0

-------------- next part --------------
From 6033421fe4b6d0de666136bf8d4872b6825b344d Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Wed, 18 Mar 2015 16:02:47 +0100
Subject: [PATCH 13/14] pam_sss: move message encoding into separate file

---
 Makefile.am                  |   1 +
 src/sss_client/pam_message.c | 178 +++++++++++++++++++++++++++++++++++++++++++
 src/sss_client/pam_message.h |  61 +++++++++++++++
 src/sss_client/pam_sss.c     | 177 +-----------------------------------------
 4 files changed, 241 insertions(+), 176 deletions(-)
 create mode 100644 src/sss_client/pam_message.c
 create mode 100644 src/sss_client/pam_message.h

diff --git a/Makefile.am b/Makefile.am
index e7fddeee619639c70887e15349e4b03e2b5b551c..361b0f9350195a339744523f663dbc42f32bf1ac 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2421,6 +2421,7 @@ endif
 pamlib_LTLIBRARIES = pam_sss.la
 pam_sss_la_SOURCES = \
     src/sss_client/pam_sss.c \
+    src/sss_client/pam_message.c \
     src/sss_client/common.c \
     src/sss_client/sss_cli.h \
     src/util/atomic_io.c \
diff --git a/src/sss_client/pam_message.c b/src/sss_client/pam_message.c
new file mode 100644
index 0000000000000000000000000000000000000000..89e18331f53c1fcf39b793a4ef8c6f8e81aa6d91
--- /dev/null
+++ b/src/sss_client/pam_message.c
@@ -0,0 +1,178 @@
+/*
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    PAM client - create message blob
+
+    Copyright (C) 2015 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 <stdlib.h>
+#include <security/pam_modules.h>
+
+#include "sss_pam_compat.h"
+#include "sss_pam_macros.h"
+
+#include "pam_message.h"
+
+#include "sss_cli.h"
+
+
+static size_t add_authtok_item(enum pam_item_type type,
+                               enum sss_authtok_type authtok_type,
+                               const char *tok, const size_t size,
+                               uint8_t *buf) {
+    size_t rp=0;
+    uint32_t c;
+
+    if (tok == NULL) return 0;
+
+    c = type;
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    c = size + sizeof(uint32_t);
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    c = authtok_type;
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    memcpy(&buf[rp], tok, size);
+    rp += size;
+
+    return rp;
+}
+
+
+static size_t add_uint32_t_item(enum pam_item_type type, const uint32_t val,
+                                uint8_t *buf) {
+    size_t rp=0;
+    uint32_t c;
+
+    c = type;
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    c = sizeof(uint32_t);
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    c = val;
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    return rp;
+}
+
+static size_t add_string_item(enum pam_item_type type, const char *str,
+                           const size_t size, uint8_t *buf) {
+    size_t rp=0;
+    uint32_t c;
+
+    if (str == NULL || *str == '\0') return 0;
+
+    c = type;
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    c = size;
+    memcpy(&buf[rp], &c, sizeof(uint32_t));
+    rp += sizeof(uint32_t);
+
+    memcpy(&buf[rp], str, size);
+    rp += size;
+
+    return rp;
+}
+
+int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer)
+{
+    int len;
+    uint8_t *buf;
+    size_t rp;
+
+    len = sizeof(uint32_t) +
+          2*sizeof(uint32_t) + pi->pam_user_size +
+          sizeof(uint32_t);
+    len += *pi->pam_service != '\0' ?
+                2*sizeof(uint32_t) + pi->pam_service_size : 0;
+    len += *pi->pam_tty != '\0' ?
+                2*sizeof(uint32_t) + pi->pam_tty_size : 0;
+    len += *pi->pam_ruser != '\0' ?
+                2*sizeof(uint32_t) + pi->pam_ruser_size : 0;
+    len += *pi->pam_rhost != '\0' ?
+                2*sizeof(uint32_t) + pi->pam_rhost_size : 0;
+    len += pi->pam_authtok != NULL ?
+                3*sizeof(uint32_t) + pi->pam_authtok_size : 0;
+    len += pi->pam_newauthtok != NULL ?
+                3*sizeof(uint32_t) + pi->pam_newauthtok_size : 0;
+    len += 3*sizeof(uint32_t); /* cli_pid */
+    len += *pi->requested_domains != '\0' ?
+                2*sizeof(uint32_t) + pi->requested_domains_size : 0;
+
+
+    buf = malloc(len);
+    if (buf == NULL) {
+        D(("malloc failed."));
+        return PAM_BUF_ERR;
+    }
+
+    rp = 0;
+    SAFEALIGN_SETMEM_UINT32(buf, SSS_START_OF_PAM_REQUEST, &rp);
+
+    rp += add_string_item(SSS_PAM_ITEM_USER, pi->pam_user, pi->pam_user_size,
+                          &buf[rp]);
+
+    rp += add_string_item(SSS_PAM_ITEM_SERVICE, pi->pam_service,
+                          pi->pam_service_size, &buf[rp]);
+
+    rp += add_string_item(SSS_PAM_ITEM_TTY, pi->pam_tty, pi->pam_tty_size,
+                          &buf[rp]);
+
+    rp += add_string_item(SSS_PAM_ITEM_RUSER, pi->pam_ruser, pi->pam_ruser_size,
+                          &buf[rp]);
+
+    rp += add_string_item(SSS_PAM_ITEM_RHOST, pi->pam_rhost, pi->pam_rhost_size,
+                          &buf[rp]);
+
+    rp += add_string_item(SSS_PAM_ITEM_REQUESTED_DOMAINS, pi->requested_domains, pi->requested_domains_size,
+                          &buf[rp]);
+
+    rp += add_uint32_t_item(SSS_PAM_ITEM_CLI_PID, (uint32_t) pi->cli_pid,
+                            &buf[rp]);
+
+    rp += add_authtok_item(SSS_PAM_ITEM_AUTHTOK, pi->pam_authtok_type,
+                           pi->pam_authtok, pi->pam_authtok_size, &buf[rp]);
+
+    rp += add_authtok_item(SSS_PAM_ITEM_NEWAUTHTOK, pi->pam_newauthtok_type,
+                           pi->pam_newauthtok, pi->pam_newauthtok_size,
+                           &buf[rp]);
+
+    SAFEALIGN_SETMEM_UINT32(buf + rp, SSS_END_OF_PAM_REQUEST, &rp);
+
+    if (rp != len) {
+        D(("error during packet creation."));
+        free(buf);
+        return PAM_BUF_ERR;
+    }
+
+    *size = len;
+    *buffer = buf;
+
+    return 0;
+}
diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h
new file mode 100644
index 0000000000000000000000000000000000000000..52f96bc1bd4871ccb09846f37c7d566692aa7112
--- /dev/null
+++ b/src/sss_client/pam_message.h
@@ -0,0 +1,61 @@
+/*
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    PAM client - create message blob
+
+    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/>.
+*/
+
+#ifndef _PAM_MESSAGE_H_
+#define _PAM_MESSAGE_H_
+
+#include <unistd.h>
+#include <stdint.h>
+
+struct pam_items {
+    const char* pam_service;
+    const char* pam_user;
+    const char* pam_tty;
+    const char* pam_ruser;
+    const char* pam_rhost;
+    char* pam_authtok;
+    char* pam_newauthtok;
+    const char* pamstack_authtok;
+    const char* pamstack_oldauthtok;
+    size_t pam_service_size;
+    size_t pam_user_size;
+    size_t pam_tty_size;
+    size_t pam_ruser_size;
+    size_t pam_rhost_size;
+    int pam_authtok_type;
+    size_t pam_authtok_size;
+    int pam_newauthtok_type;
+    size_t pam_newauthtok_size;
+    pid_t cli_pid;
+    const char *login_name;
+    char *domain_name;
+    const char *requested_domains;
+    size_t requested_domains_size;
+    char *otp_vendor;
+    char *otp_token_id;
+    char *otp_challenge;
+    char *first_factor;
+};
+
+int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer);
+
+#endif /* _PAM_MESSAGE_H_ */
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 98afaa779c95396295715413507bc7a9e40ee699..48796eeb0a9d93fb893eb19e7e760e1a91ef4ba9 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -40,6 +40,7 @@
 #include "sss_pam_macros.h"
 
 #include "sss_cli.h"
+#include "pam_message.h"
 #include "util/atomic_io.h"
 #include "util/authtok-utils.h"
 
@@ -65,36 +66,6 @@
 #define EXP_ACC_MSG _("Permission denied. ")
 #define SRV_MSG     _("Server message: ")
 
-struct pam_items {
-    const char* pam_service;
-    const char* pam_user;
-    const char* pam_tty;
-    const char* pam_ruser;
-    const char* pam_rhost;
-    char* pam_authtok;
-    char* pam_newauthtok;
-    const char* pamstack_authtok;
-    const char* pamstack_oldauthtok;
-    size_t pam_service_size;
-    size_t pam_user_size;
-    size_t pam_tty_size;
-    size_t pam_ruser_size;
-    size_t pam_rhost_size;
-    int pam_authtok_type;
-    size_t pam_authtok_size;
-    int pam_newauthtok_type;
-    size_t pam_newauthtok_size;
-    pid_t cli_pid;
-    const char *login_name;
-    char *domain_name;
-    const char *requested_domains;
-    size_t requested_domains_size;
-    char *otp_vendor;
-    char *otp_token_id;
-    char *otp_challenge;
-    char *first_factor;
-};
-
 #define DEBUG_MGS_LEN 1024
 #define MAX_AUTHTOK_SIZE (1024*1024)
 #define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)")
@@ -145,75 +116,6 @@ static void close_fd(pam_handle_t *pamh, void *ptr, int err)
     sss_pam_close_fd();
 }
 
-static size_t add_authtok_item(enum pam_item_type type,
-                               enum sss_authtok_type authtok_type,
-                               const char *tok, const size_t size,
-                               uint8_t *buf) {
-    size_t rp=0;
-    uint32_t c;
-
-    if (tok == NULL) return 0;
-
-    c = type;
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    c = size + sizeof(uint32_t);
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    c = authtok_type;
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    memcpy(&buf[rp], tok, size);
-    rp += size;
-
-    return rp;
-}
-
-
-static size_t add_uint32_t_item(enum pam_item_type type, const uint32_t val,
-                                uint8_t *buf) {
-    size_t rp=0;
-    uint32_t c;
-
-    c = type;
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    c = sizeof(uint32_t);
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    c = val;
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    return rp;
-}
-
-static size_t add_string_item(enum pam_item_type type, const char *str,
-                           const size_t size, uint8_t *buf) {
-    size_t rp=0;
-    uint32_t c;
-
-    if (str == NULL || *str == '\0') return 0;
-
-    c = type;
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    c = size;
-    memcpy(&buf[rp], &c, sizeof(uint32_t));
-    rp += sizeof(uint32_t);
-
-    memcpy(&buf[rp], str, size);
-    rp += size;
-
-    return rp;
-}
-
 static void overwrite_and_free_authtoks(struct pam_items *pi)
 {
     if (pi->pam_authtok != NULL) {
@@ -255,83 +157,6 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
     pi->otp_challenge = NULL;
 }
 
-static int pack_message_v3(struct pam_items *pi, size_t *size,
-                           uint8_t **buffer) {
-    int len;
-    uint8_t *buf;
-    size_t rp;
-
-    len = sizeof(uint32_t) +
-          2*sizeof(uint32_t) + pi->pam_user_size +
-          sizeof(uint32_t);
-    len += *pi->pam_service != '\0' ?
-                2*sizeof(uint32_t) + pi->pam_service_size : 0;
-    len += *pi->pam_tty != '\0' ?
-                2*sizeof(uint32_t) + pi->pam_tty_size : 0;
-    len += *pi->pam_ruser != '\0' ?
-                2*sizeof(uint32_t) + pi->pam_ruser_size : 0;
-    len += *pi->pam_rhost != '\0' ?
-                2*sizeof(uint32_t) + pi->pam_rhost_size : 0;
-    len += pi->pam_authtok != NULL ?
-                3*sizeof(uint32_t) + pi->pam_authtok_size : 0;
-    len += pi->pam_newauthtok != NULL ?
-                3*sizeof(uint32_t) + pi->pam_newauthtok_size : 0;
-    len += 3*sizeof(uint32_t); /* cli_pid */
-    len += *pi->requested_domains != '\0' ?
-                2*sizeof(uint32_t) + pi->requested_domains_size : 0;
-
-
-    buf = malloc(len);
-    if (buf == NULL) {
-        D(("malloc failed."));
-        return PAM_BUF_ERR;
-    }
-
-    rp = 0;
-    SAFEALIGN_SETMEM_UINT32(buf, SSS_START_OF_PAM_REQUEST, &rp);
-
-    rp += add_string_item(SSS_PAM_ITEM_USER, pi->pam_user, pi->pam_user_size,
-                          &buf[rp]);
-
-    rp += add_string_item(SSS_PAM_ITEM_SERVICE, pi->pam_service,
-                          pi->pam_service_size, &buf[rp]);
-
-    rp += add_string_item(SSS_PAM_ITEM_TTY, pi->pam_tty, pi->pam_tty_size,
-                          &buf[rp]);
-
-    rp += add_string_item(SSS_PAM_ITEM_RUSER, pi->pam_ruser, pi->pam_ruser_size,
-                          &buf[rp]);
-
-    rp += add_string_item(SSS_PAM_ITEM_RHOST, pi->pam_rhost, pi->pam_rhost_size,
-                          &buf[rp]);
-
-    rp += add_string_item(SSS_PAM_ITEM_REQUESTED_DOMAINS, pi->requested_domains, pi->requested_domains_size,
-                          &buf[rp]);
-
-    rp += add_uint32_t_item(SSS_PAM_ITEM_CLI_PID, (uint32_t) pi->cli_pid,
-                            &buf[rp]);
-
-    rp += add_authtok_item(SSS_PAM_ITEM_AUTHTOK, pi->pam_authtok_type,
-                           pi->pam_authtok, pi->pam_authtok_size, &buf[rp]);
-
-    rp += add_authtok_item(SSS_PAM_ITEM_NEWAUTHTOK, pi->pam_newauthtok_type,
-                           pi->pam_newauthtok, pi->pam_newauthtok_size,
-                           &buf[rp]);
-
-    SAFEALIGN_SETMEM_UINT32(buf + rp, SSS_END_OF_PAM_REQUEST, &rp);
-
-    if (rp != len) {
-        D(("error during packet creation."));
-        free(buf);
-        return PAM_BUF_ERR;
-    }
-
-    *size = len;
-    *buffer = buf;
-
-    return 0;
-}
-
 static int null_strcmp(const char *s1, const char *s2) {
     if (s1 == NULL && s2 == NULL) return 0;
     if (s1 == NULL && s2 != NULL) return -1;
-- 
2.1.0

-------------- next part --------------
From 881b58b4bab9914b6f786b57ce9ddfa48d72473e Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 19 Mar 2015 13:12:11 +0100
Subject: [PATCH 14/14] PAM: add PAM responder unit test

---
 Makefile.am                     |  32 ++
 src/tests/cmocka/test_pam_srv.c | 907 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 939 insertions(+)
 create mode 100644 src/tests/cmocka/test_pam_srv.c

diff --git a/Makefile.am b/Makefile.am
index 361b0f9350195a339744523f663dbc42f32bf1ac..a62a3523d7b17e8e7e5bcdc5a63b1aee3d9ac05d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -224,6 +224,7 @@ if HAVE_CMOCKA
         responder_cache_req-tests \
         test_sbus_opath \
         test_fo_srv \
+        pam-srv-tests \
         $(NULL)
 
 if HAVE_LIBRESOLV
@@ -1769,6 +1770,37 @@ nss_srv_tests_LDADD = \
     libsss_test_common.la \
     libsss_idmap.la
 
+EXTRA_pam_srv_tests_DEPENDENCIES = \
+    $(ldblib_LTLIBRARIES) \
+    $(NULL)
+pam_srv_tests_SOURCES = \
+    $(TEST_MOCK_RESP_OBJ) \
+    src/tests/cmocka/test_pam_srv.c \
+    src/sss_client/pam_message.c \
+    src/responder/pam/pamsrv_cmd.c \
+    src/responder/pam/pam_helpers.c \
+    src/responder/pam/pamsrv_dp.c \
+    src/responder/pam/pam_LOCAL_domain.c \
+    $(NULL)
+pam_srv_tests_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+pam_srv_tests_LDFLAGS = \
+    -Wl,-wrap,sss_packet_get_body \
+    -Wl,-wrap,sss_packet_get_cmd \
+    -Wl,-wrap,sss_cmd_send_empty \
+    -Wl,-wrap,sss_cmd_done \
+    -Wl,-wrap,pam_dp_send_req \
+    $(NULL)
+pam_srv_tests_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(PAM_LIBS) \
+    $(SSSD_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    libsss_test_common.la \
+    libsss_idmap.la \
+    $(NULL)
+
 EXTRA_responder_get_domains_tests_DEPENDENCIES = \
      $(ldblib_LTLIBRARIES)
 responder_get_domains_tests_SOURCES = \
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
new file mode 100644
index 0000000000000000000000000000000000000000..13e808f3eb5bd0b5410c7d3e5659bc9ac31298bc
--- /dev/null
+++ b/src/tests/cmocka/test_pam_srv.c
@@ -0,0 +1,907 @@
+/*
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2015 Red Hat
+
+    SSSD tests: PAM responder tests
+
+    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 <security/pam_modules.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/negcache.h"
+#include "responder/pam/pamsrv.h"
+#include "responder/pam/pam_helpers.h"
+#include "sss_client/pam_message.h"
+#include "sss_client/sss_cli.h"
+
+#include "util/crypto/nss/nss_util.h"
+
+#define TESTS_PATH "tests_pam"
+#define TEST_CONF_DB "test_pam_conf.ldb"
+#define TEST_DOM_NAME "pam_test"
+#define TEST_SUBDOM_NAME "test.subdomain"
+#define TEST_ID_PROVIDER "ldap"
+
+struct pam_test_ctx {
+    struct sss_test_ctx *tctx;
+    struct sss_domain_info *subdom;
+
+    struct resp_ctx *rctx;
+    struct cli_ctx *cctx;
+    struct sss_cmd_table *pam_cmds;
+    struct pam_ctx *pctx;
+
+    int ncache_hits;
+    int exp_pam_status;
+};
+
+/* Must be global because it is needed in some wrappers */
+struct pam_test_ctx *pam_test_ctx;
+
+struct pam_ctx *mock_pctx(TALLOC_CTX *mem_ctx)
+{
+    struct pam_ctx *pctx;
+    errno_t ret;
+
+    pctx = talloc_zero(mem_ctx, struct pam_ctx);
+    if (!pctx) {
+        return NULL;
+    }
+
+    ret = sss_ncache_init(pctx, &pctx->ncache);
+    if (ret != EOK) {
+        talloc_free(pctx);
+        return NULL;
+    }
+    pctx->neg_timeout = 10;
+
+    ret = sss_hash_create(pctx, 10, &pctx->id_table);
+    if (ret != EOK) {
+        talloc_free(pctx);
+        return NULL;
+    }
+
+    return pctx;
+}
+
+void test_pam_setup(struct sss_test_conf_param params[],
+                    void **state)
+{
+    errno_t ret;
+
+    pam_test_ctx = talloc_zero(NULL, struct pam_test_ctx);
+    assert_non_null(pam_test_ctx);
+
+    pam_test_ctx->tctx = create_dom_test_ctx(pam_test_ctx, TESTS_PATH,
+                                             TEST_CONF_DB, TEST_DOM_NAME,
+                                             TEST_ID_PROVIDER, params);
+    assert_non_null(pam_test_ctx->tctx);
+
+    pam_test_ctx->pam_cmds = get_pam_cmds();
+    assert_non_null(pam_test_ctx->pam_cmds);
+
+    /* FIXME - perhaps this should be folded into sssd_domain_init or stricty
+     * used together
+     */
+    ret = sss_names_init(pam_test_ctx, pam_test_ctx->tctx->confdb,
+                         TEST_DOM_NAME, &pam_test_ctx->tctx->dom->names);
+    assert_int_equal(ret, EOK);
+
+    /* Initialize the PAM responder */
+    pam_test_ctx->pctx = mock_pctx(pam_test_ctx);
+    assert_non_null(pam_test_ctx->pctx);
+
+    pam_test_ctx->rctx = mock_rctx(pam_test_ctx, pam_test_ctx->tctx->ev,
+                                   pam_test_ctx->tctx->dom, pam_test_ctx->pctx);
+    assert_non_null(pam_test_ctx->rctx);
+    pam_test_ctx->rctx->cdb = pam_test_ctx->tctx->confdb;
+    pam_test_ctx->pctx->rctx = pam_test_ctx->rctx;
+
+    /* Create client context */
+    pam_test_ctx->cctx = mock_cctx(pam_test_ctx, pam_test_ctx->rctx);
+    assert_non_null(pam_test_ctx->cctx);
+
+    pam_test_ctx->cctx->cli_protocol_version = register_cli_protocol_version();
+}
+
+static int pam_test_setup(void **state)
+{
+    int ret;
+
+    struct sss_test_conf_param params[] = {
+        { "enumerate", "false" },
+        { "cache_credentials", "true" },
+        { NULL, NULL },             /* Sentinel */
+    };
+
+    test_pam_setup(params, state);
+
+    /* Prime the cache with a valid user */
+    ret = sysdb_add_user(pam_test_ctx->tctx->dom,
+                         "pamuser", 123, 456, "pam user",
+                         "/home/pamuser", "/bin/sh", NULL,
+                         NULL, 300, 0);
+    assert_int_equal(ret, EOK);
+
+    /* Add entry to the initgr cache to make sure no initgr request is sent to
+     * the backend */
+    ret = pam_initgr_cache_set(pam_test_ctx->pctx->rctx->ev,
+                               pam_test_ctx->pctx->id_table,
+                               discard_const("pamuser"),
+                               pam_test_ctx->pctx->id_timeout);
+    assert_int_equal(ret, EOK);
+    return 0;
+}
+
+static int pam_test_teardown(void **state)
+{
+    int ret;
+
+    ret = sysdb_delete_user(pam_test_ctx->tctx->dom, "pamuser", 0);
+    assert_int_equal(ret, EOK);
+
+    talloc_free(pam_test_ctx);
+    return 0;
+}
+
+typedef int (*cmd_cb_fn_t)(uint32_t, uint8_t *, size_t );
+
+void __real_sss_packet_get_body(struct sss_packet *packet,
+                                uint8_t **body, size_t *blen);
+
+void __wrap_sss_packet_get_body(struct sss_packet *packet,
+                                uint8_t **body, size_t *blen)
+{
+    enum sss_test_wrapper_call wtype = sss_mock_type(enum sss_test_wrapper_call);
+    size_t len;
+
+    if (wtype == WRAP_CALL_REAL) {
+        return __real_sss_packet_get_body(packet, body, blen);
+    }
+
+    *body = sss_mock_ptr_type(uint8_t *);
+    len = sss_mock_type(size_t);
+    if (len == 0) {
+        len = strlen((const char *) *body)+1;
+    }
+    *blen = len;
+    return;
+}
+
+void __real_sss_packet_get_body(struct sss_packet *packet,
+                                uint8_t **body, size_t *blen);
+
+void __wrap_sss_cmd_done(struct cli_ctx *cctx, void *freectx)
+{
+    struct sss_packet *packet = cctx->creq->out;
+    uint8_t *body;
+    size_t blen;
+    cmd_cb_fn_t check_cb;
+
+    assert_non_null(packet);
+
+    check_cb = sss_mock_ptr_type(cmd_cb_fn_t);
+
+    __real_sss_packet_get_body(packet, &body, &blen);
+
+    pam_test_ctx->tctx->error = check_cb(sss_packet_get_status(packet),
+                                         body, blen);
+    pam_test_ctx->tctx->done = true;
+}
+
+enum sss_cli_command __wrap_sss_packet_get_cmd(struct sss_packet *packet)
+{
+    return sss_mock_type(enum sss_cli_command);
+}
+
+int __wrap_sss_cmd_send_empty(struct cli_ctx *cctx, TALLOC_CTX *freectx)
+{
+    pam_test_ctx->tctx->done = true;
+    pam_test_ctx->tctx->error = ENOENT;
+    return EOK;
+}
+
+static void set_cmd_cb(cmd_cb_fn_t fn)
+{
+    will_return(__wrap_sss_cmd_done, fn);
+}
+
+int __wrap_pam_dp_send_req(struct pam_auth_req *preq, int timeout)
+{
+
+    /* Set expected status */
+    preq->pd->pam_status = pam_test_ctx->exp_pam_status;
+
+    preq->callback(preq);
+
+    return EOK;
+}
+
+static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name,
+                           const char *pwd, const char *fa2)
+{
+    size_t buf_size;
+    uint8_t *m_buf;
+    uint8_t *buf;
+    struct pam_items pi = { 0 };
+    int ret;
+    size_t needed_size;
+    uint8_t *authtok;
+
+    pi.pam_user = name;
+    pi.pam_user_size = strlen(pi.pam_user) + 1;
+
+    if (pwd != NULL) {
+        if (fa2 != NULL) {
+            ret = sss_auth_pack_2fa_blob(pwd, 0, fa2, 0, NULL, 0, &needed_size);
+            assert_int_equal(ret, EAGAIN);
+
+            authtok = talloc_size(mem_ctx, needed_size);
+            assert_non_null(authtok);
+
+            ret = sss_auth_pack_2fa_blob(pwd, 0, fa2, 0, authtok,
+                                         needed_size, &needed_size);
+            assert_int_equal(ret, EOK);
+
+            pi.pam_authtok = (char *) authtok;
+            pi.pam_authtok_size = needed_size;
+            pi.pam_authtok_type = SSS_AUTHTOK_TYPE_2FA;
+        } else {
+            pi.pam_authtok = discard_const(pwd);
+            pi.pam_authtok_size = strlen(pi.pam_authtok) + 1;
+            pi.pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+        }
+    }
+
+    pi.pam_service = "ssh";
+    pi.pam_service_size=strlen(pi.pam_service)+1;
+    pi.pam_tty = "/dev/tty";
+    pi.pam_tty_size=strlen(pi.pam_tty)+1;
+    pi.pam_ruser = "remuser";
+    pi.pam_ruser_size=strlen(pi.pam_ruser)+1;
+    pi.pam_rhost = "remhost";
+    pi.pam_rhost_size=strlen(pi.pam_rhost)+1;
+    pi.requested_domains = "";
+    pi.cli_pid = 12345;
+
+    ret = pack_message_v3(&pi, &buf_size, &m_buf);
+    assert_int_equal(ret, 0);
+
+    buf = talloc_memdup(mem_ctx, m_buf, buf_size);
+    free(m_buf);
+    assert_non_null(buf);
+
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_WRAPPER);
+    will_return(__wrap_sss_packet_get_body, buf);
+    will_return(__wrap_sss_packet_get_body, buf_size);
+}
+
+static int test_pam_simple_check(uint32_t status, uint8_t *body, size_t blen)
+{
+    size_t rp = 0;
+    uint32_t val;
+
+    assert_int_equal(status, 0);
+
+    SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+    assert_int_equal(val, pam_test_ctx->exp_pam_status);
+
+    SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+    assert_int_equal(val, 1);
+
+    SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+    assert_int_equal(val, SSS_PAM_DOMAIN_NAME);
+
+    SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+    assert_int_equal(val, 9);
+
+    assert_int_equal(*(body + rp + val - 1), 0);
+    assert_string_equal(body+rp, TEST_DOM_NAME);
+
+    return EOK;
+}
+
+static int test_pam_failed_offline_auth_check(uint32_t status, uint8_t *body,
+                                              size_t blen)
+{
+    pam_test_ctx->exp_pam_status = PAM_PERM_DENIED;
+    return test_pam_simple_check(status, body, blen);
+}
+
+static int test_pam_successful_offline_auth_check(uint32_t status,
+                                                  uint8_t *body, size_t blen)
+{
+    pam_test_ctx->exp_pam_status = PAM_SUCCESS;
+    return test_pam_simple_check(status, body, blen);
+}
+
+static int test_pam_wrong_pw_offline_auth_check(uint32_t status,
+                                                uint8_t *body, size_t blen)
+{
+    pam_test_ctx->exp_pam_status = PAM_AUTH_ERR;
+    return test_pam_simple_check(status, body, blen);
+}
+
+void test_pam_authenticate(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_setcreds(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_SETCRED);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_SETCRED,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_acct_mgmt(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_ACCT_MGMT);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_ACCT_MGMT,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+
+void test_pam_open_session(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_OPEN_SESSION);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_NO_MODULE_DATA;
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_OPEN_SESSION,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_close_session(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_CLOSE_SESSION);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_CLOSE_SESSION,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_chauthtok(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_CHAUTHTOK);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_CHAUTHTOK,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_chauthtok_prelim(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_CHAUTHTOK_PRELIM);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_CHAUTHTOK_PRELIM,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_preauth(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    set_cmd_cb(test_pam_simple_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_no_hash(void **state)
+{
+    int ret;
+
+    mock_input_pam(pam_test_ctx, "pamuser", "12345", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_failed_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "12345", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_successful_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_wrong_pw(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "11111", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "12345", "abcde");
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_successful_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "11111", "abcde");
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_2fa_with_cached_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+                                  SSS_AUTHTOK_TYPE_2FA, 5);
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "12345", "abcde");
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_successful_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_2fa_with_cached_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+                                  SSS_AUTHTOK_TYPE_2FA, 5);
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "11111", "abcde");
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_pw_with_cached_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+                                  SSS_AUTHTOK_TYPE_2FA, 5);
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "12345", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_successful_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_pw_with_cached_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+                                  SSS_AUTHTOK_TYPE_2FA, 5);
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "11111", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_combined_pw_with_cached_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser",
+                                  "12345678", SSS_AUTHTOK_TYPE_2FA, 5);
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "12345678abcde", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_successful_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_combined_pw_with_cached_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser",
+                                  "12345678", SSS_AUTHTOK_TYPE_2FA, 5);
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "11111111abcde", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_wrong_2fa_size_with_cached_2fa(void **state)
+{
+    int ret;
+
+    ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser",
+                                  "12345678", SSS_AUTHTOK_TYPE_2FA, 5);
+    assert_int_equal(ret, EOK);
+
+    mock_input_pam(pam_test_ctx, "pamuser", "12345678abcd", NULL);
+
+    will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+    will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+    ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+                          pam_test_ctx->pam_cmds);
+    assert_int_equal(ret, EOK);
+
+    /* Wait until the test finishes with EOK */
+    ret = test_ev_loop(pam_test_ctx->tctx);
+    assert_int_equal(ret, EOK);
+
+}
+
+int main(int argc, const char *argv[])
+{
+    int rv;
+    int no_cleanup = 0;
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0,
+         _("Do not delete the test database after a test run"), NULL },
+        POPT_TABLEEND
+    };
+
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_pam_authenticate,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_setcreds,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_acct_mgmt,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_open_session,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_close_session,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_chauthtok,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_chauthtok_prelim,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_preauth,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_offline_auth_no_hash,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_offline_auth_success,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_offline_auth_wrong_pw,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_offline_auth_success_2fa,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_offline_auth_failed_2fa,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                              test_pam_offline_auth_success_2fa_with_cached_2fa,
+                              pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                               test_pam_offline_auth_failed_2fa_with_cached_2fa,
+                               pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                               test_pam_offline_auth_success_pw_with_cached_2fa,
+                               pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                                test_pam_offline_auth_failed_pw_with_cached_2fa,
+                                pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                      test_pam_offline_auth_success_combined_pw_with_cached_2fa,
+                      pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                       test_pam_offline_auth_failed_combined_pw_with_cached_2fa,
+                       pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(
+                    test_pam_offline_auth_failed_wrong_2fa_size_with_cached_2fa,
+                     pam_test_setup, pam_test_teardown),
+    };
+
+    /* Set debug level to invalid value so we can deside 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);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old db to be sure */
+    tests_set_cwd();
+    test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+    test_dom_suite_setup(TESTS_PATH);
+
+    rv = cmocka_run_group_tests(tests, NULL, NULL);
+    if (rv == 0 && !no_cleanup) {
+        test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+    }
+
+#ifdef HAVE_NSS
+    /* Cleanup NSS and NSPR to make valgrund happy. */
+    nspr_nss_cleanup();
+#endif
+
+    return rv;
+}
-- 
2.1.0



More information about the sssd-devel mailing list