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

Sumit Bose sbose at redhat.com
Wed Apr 22 08:24:25 UTC 2015


On Mon, Apr 06, 2015 at 08:58:16PM +0200, Sumit Bose wrote:
> 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.
> 

Please find attached a new version of the patch set which contains a few
fixes which should silence a couple of compiler warnings.

bye,
Sumit
-------------- next part --------------
From df63d2b3012242a847254081a232c61dbd7e2b8f 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 52fbd510d67489dfd65b003c99673dc0869cfdc0..233d171b89ee2003881c58fb74fad808702746ae 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1858,11 +1858,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 3fe2e9fe20e41e4c8ed31ab94655d68debeb3268 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 | 154 +++++++++++++++++++++++++++++++++-
 src/util/authtok-utils.c        |  74 ++++++++++++++++
 src/util/authtok-utils.h        |  55 ++++++++++++
 src/util/authtok.c              | 182 ++++++++++++++++++++++++++++++++++++++++
 src/util/authtok.h              |  44 ++++++++++
 8 files changed, 517 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 233d171b89ee2003881c58fb74fad808702746ae..5f400585141f8ebf11b51171ffc0f60070261b42 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 \
@@ -1854,6 +1856,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) \
@@ -2780,6 +2783,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 \
@@ -2811,6 +2815,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..65828b82d5c8aa389df7db80df3deefe79f14a8b 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -311,6 +311,152 @@ static void test_sss_authtok_copy(void **state)
     talloc_free(data);
 }
 
+void test_sss_authtok_2fa(void **state)
+{
+    int ret;
+    const char *fa1;
+    size_t fa1_size;
+    const char *fa2;
+    size_t fa2_size;
+    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,
+                          (const uint8_t *) "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 +479,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..6ce294891a74fd66940e6f4eee5056beba697ee6
--- /dev/null
+++ b/src/util/authtok-utils.h
@@ -0,0 +1,55 @@
+/*
+   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 <talloc.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);
+
+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);
+#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 84f05d84ef5bd88a6f1e5c81bc7bd7f2108bacd1 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 c4d23d29adf9e7c554746be4b9faf5718861dc82 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 6fbeeba4eb1543620a8b3dc3bee7894253ccb924 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..a3977b399d3a36c79a42993ae060f1f51ff30dd4 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, "[%zu] Vendor [%s].\n",
+                                    i, chl->tokeninfo[i]->vendor);
+            DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n",
+                                    i, chl->tokeninfo[i]->token_id);
+            DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n",
+                                    i, chl->tokeninfo[i]->challenge);
+            DEBUG(SSSDBG_TRACE_ALL, "[%zu] 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 c410b69381540c3db3c2f5f0396b6b8162bca764 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 c8e85195377cb4878a1c879d28ec2b217cb01a38 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 | 226 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 225 insertions(+), 2 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 5f400585141f8ebf11b51171ffc0f60070261b42..be49a5c6dac8a138aca1b18ea4b16294ac97a003 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2430,6 +2430,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..e6cf35a90f958e2eceeb737a35e4ebfae99f73fc 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,133 @@ 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;
+    const struct pam_message *mesg[2] = {NULL, NULL};
+    struct pam_message *m1;
+    struct pam_message *m2;
+    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;
+    }
+
+    m1 = malloc(sizeof(struct pam_message));
+    if (m1 == NULL) {
+        D(("Malloc failed."));
+        return PAM_SYSTEM_ERR;
+    }
+
+    m2 = malloc(sizeof(struct pam_message));
+    if (m2 == NULL) {
+        D(("Malloc failed."));
+        free(m1);
+        return PAM_SYSTEM_ERR;
+    }
+    m1->msg_style = PAM_PROMPT_ECHO_OFF;
+    m1->msg = prompt_fa1;
+    m2->msg_style = PAM_PROMPT_ECHO_OFF;
+    m2->msg = prompt_fa2;
+
+    mesg[0] = (const struct pam_message *) m1;
+    mesg[1] = (const struct pam_message *) m2;
+
+    ret=conv->conv(2, mesg, &resp, conv->appdata_ptr);
+    free(m1);
+    free(m2);
+    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,
+                                     (uint8_t* ) 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 +1599,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 +1624,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 +1781,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 6282f77f1d00a46bee3cfd1890403f5469ed814f 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 5ef4f44bd2feb221dbe70c3748fa09e522ac98a0 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 a6c6b33de7be7665e5284524c840de7157ed1891..22b1de428f9918ff82fe64b0ca3941056eac21d0 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -1809,6 +1809,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)
@@ -6241,6 +6292,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 7384c66931f8ed4cbf7a0a632558198538741553 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 5d220353e80c4dcecce175c2182d09b74e52134b 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 02bbf42ba4926efc39d878d4f75b7435d8f10920 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 e6798acb2d6aad59b2ef6ace0a8162b6e297716a 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                  |   2 +
 src/sss_client/pam_message.c | 178 +++++++++++++++++++++++++++++++++++++++++++
 src/sss_client/pam_message.h |  61 +++++++++++++++
 src/sss_client/pam_sss.c     | 177 +-----------------------------------------
 4 files changed, 242 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 be49a5c6dac8a138aca1b18ea4b16294ac97a003..412b4426af8d07f7f4eb4d6dcaa71ad99e9cee5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -614,6 +614,7 @@ dist_noinst_HEADERS = \
     src/tests/cmocka/common_mock_sdap.h \
     src/tests/cmocka/common_mock_sysdb_objects.h \
     src/tests/cmocka/test_expire_common.h \
+    src/sss_client/pam_message.h \
     src/sss_client/ssh/sss_ssh_client.h \
     src/sss_client/sudo/sss_sudo.h \
     src/sss_client/libwbclient/libwbclient.h \
@@ -2427,6 +2428,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 e6cf35a90f958e2eceeb737a35e4ebfae99f73fc..fa3d198369e06d85a5528a3c4670c3fad479870d 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 dc213d592ccb2e622127cafd9fd32eff1201de83 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 412b4426af8d07f7f4eb4d6dcaa71ad99e9cee5f..b970bbc30e4e7414deb5a0be86ef5867e20192e4 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
@@ -1775,6 +1776,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