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

Sumit Bose sbose at redhat.com
Tue May 5 19:16:02 UTC 2015


On Wed, Apr 29, 2015 at 05:40:37PM +0200, Lukas Slebodnik wrote:
> On (22/04/15 10:24), Sumit Bose wrote:
> >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
> 
> There are some coding style issues and clang warning
> It shoudl be fixed with attached patches.
> I wanted to simplyfy your job :-)

Thank you very much for the review and the fixes, I included most of
your comments. For the few I skipped please find an explanation below.

> >+
> >+    fd = open(PAM_PREAUTH_INDICATOR, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW,
> >+              0644);
>       Can we use syscall "creat" or do we need special flags?

creat() uses O_CREAT|O_WRONLY|O_TRUNC, I found O_EXCL and O_NOFOLLOW
important hence I used open()

> 
> >+    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 = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_TYPE, authtok_type);
> >+    if (ret) goto fail;
> "enum sss_authtok_type" is stored as long but read as int
> 
> sh$ $git grep SYSDB_CACHEDPWD_TYPE
> src/db/sysdb.h-#define SYSDB_CACHEDPWD "cachedPassword"
> src/db/sysdb.h:#define SYSDB_CACHEDPWD_TYPE "cachedPasswordType"
> --
> src/db/sysdb_ops.c-
> src/db/sysdb_ops.c:    ret = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_TYPE, authtok_type);
> --
> src/db/sysdb_ops.c-    cached_authtok_type = ldb_msg_find_attr_as_uint(ldb_msg,
> src/db/sysdb_ops.c:                                                    SYSDB_CACHEDPWD_TYPE,
> --
> src/db/sysdb_ops.c-                            "accountExpires", SYSDB_FAILED_LOGIN_ATTEMPTS,
> src/db/sysdb_ops.c:                            SYSDB_LAST_FAILED_LOGIN, SYSDB_CACHEDPWD_TYPE,
> --
> src/tests/sysdb-tests.c-    struct ldb_result *res;
> src/tests/sysdb-tests.c:    const char *attrs[] = { SYSDB_CACHEDPWD_TYPE, SYSDB_CACHEDPWD_FA2_LEN,
> --
> src/tests/sysdb-tests.c-
> src/tests/sysdb-tests.c:    val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_TYPE, 0);
> --
> src/tests/sysdb-tests.c-
> src/tests/sysdb-tests.c:    val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_TYPE, 0);
> 
> C standard does not say exactly the size of enum, but int should be enough
> and type should be the same for read and store

unfortunately the overlap between the ldb attr and sysdb attr calls are
quite minimal. ldb has int, unsigned int, int64_t and uint64_t while in
sysdb we have long and uint32_t. So we have to add a new sysdb call, I'm
just wondering what would be the most suitable one for enums. It looks
like the expression that defines the value must be int, so maybe int
might be the most useful choice. Do you have any suggestions here?

New version attached.

bye,
Sumit

-------------- next part --------------
From 5432f3cb852122640363b325faa3b96a4377569d 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 0aad222c1276b76607ec2d0e5c6b411d3b17b0a7..0b30d3c97f038dd8ed26ac638da2af33e1db73c8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1859,11 +1859,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 e83c650e0d0a05b231c6f37ce386708323b64c76 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 | 189 +++++++++++++++++++++++++++++++++++++++-
 src/util/authtok-utils.c        |  74 ++++++++++++++++
 src/util/authtok-utils.h        |  70 +++++++++++++++
 src/util/authtok.c              | 181 ++++++++++++++++++++++++++++++++++++++
 src/util/authtok.h              |  44 ++++++++++
 8 files changed, 564 insertions(+), 3 deletions(-)
 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 0b30d3c97f038dd8ed26ac638da2af33e1db73c8..e0fccbf19c751ba61589019e061a39f8e4a844db 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 \
@@ -1855,6 +1857,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) \
@@ -2781,6 +2784,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 \
@@ -2812,6 +2816,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..5aa47c7b6b8c955666a9c73d5f9627d6378d13e0 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -57,7 +57,7 @@ static int teardown(void **state)
 
     assert_non_null(ts);
 
-    assert_true(check_leaks_pop(ts) == true);
+    assert_true(check_leaks_pop(ts));
     talloc_free(ts);
     assert_true(leak_check_teardown());
     return 0;
@@ -118,8 +118,8 @@ static void test_sss_authtok_password(void **state)
     assert_int_equal(len - 1, ret_len);
 
     ret = sss_authtok_set_password(ts->authtoken, data, len);
-
     assert_int_equal(ret, EOK);
+
     ret = sss_authtok_get_password(ts->authtoken, &pwd, &ret_len);
     assert_int_equal(ret, EOK);
     assert_string_equal(data, pwd);
@@ -311,6 +311,183 @@ 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);
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    assert_memory_equal(sss_authtok_get_data(ts->authtoken),
+                        "\2\0\0\0\3\0\0\0a\0bc\0",
+                        2 * sizeof(uint32_t) + 5);
+#else
+    assert_memory_equal(sss_authtok_get_data(ts->authtoken),
+                        "\0\0\0\2\0\0\0\3a\0bc\0",
+                        2 * sizeof(uint32_t) + 5);
+#endif
+
+    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);
+    assert_int_equal(ret, EOK);
+
+    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(NULL, 0, "defg", 0, NULL, 0, &needed_size);
+    assert_int_equal(ret, EINVAL);
+
+    ret = sss_auth_pack_2fa_blob("abc", 0, NULL, 0, NULL, 0, &needed_size);
+    assert_int_equal(ret, EINVAL);
+
+    ret = sss_auth_pack_2fa_blob("", 0, "defg", 0, NULL, 0, &needed_size);
+    assert_int_equal(ret, EINVAL);
+
+    ret = sss_auth_pack_2fa_blob("abc", 0, "", 0, NULL, 0, &needed_size);
+    assert_int_equal(ret, EINVAL);
+
+    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);
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    assert_memory_equal(buf, "\4\0\0\0\5\0\0\0abc\0defg\0", needed_size);
+#else
+    assert_memory_equal(buf, "\0\0\0\4\0\0\0\5abc\0defg\0", needed_size);
+#endif
+
+    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;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+    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'};
+#else
+    uint8_t b0[] = {0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 'a', 'b', 'c', 0x00, 'd', 'e', 'f', 'g', 0x00};
+    uint8_t b1[] = {0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 0x00};
+    uint8_t b2[] = {0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 'a', 'b', 'c', 0x00, 'd', 'e', 'f', 'g'};
+    uint8_t b3[] = {0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 'a', 'b', 'c', 'd', 'e', 'f', 'g'};
+#endif
+
+
+    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 +510,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..b3be854acfe8fb1bb5b4be10f76474f6fb472001
--- /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 == '\0' || fa1_len > UINT32_MAX
+            || fa2 == NULL || *fa2 == '\0' || 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..07aef3c18395d6e967289f6e345f27e9ee868da2
--- /dev/null
+++ b/src/util/authtok-utils.h
@@ -0,0 +1,70 @@
+/*
+   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);
+
+/**
+ * @brief Extract 2FA data from memory buffer
+ *
+ * @param[in]  mem_ctx   Talloc memory context to allocate the 2FA data on
+ * @param[in]  blob      Memory buffer containing the 2FA data
+ * @param[in]  blob_len  Size of the memory buffer
+ * @param[out] _fa1      First authentication factor, null terminated
+ * @param[out] _fa1_len  Length of the first authentication factor
+ * @param[out] _fa2      Second authentication factor, null terminated
+ * @param[out] _fa2_len  Length of the second authentication factor
+ *
+ * @return     EOK       on success
+ *             EINVAL    if input data is not consistent
+ *             EINVAL    if no memory can be allocated
+ */
+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..45761df80175fded8a6c6e5dac8a90180b11d225 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,175 @@ 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..cb366270832852281a222018f8e27feb1500ff01 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, may not be modified or freed
+ * @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, may not be modified or freed
+ * @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 7172f8bb5d001727f6654dc37488f7f91ff1c6a9 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 c94f2f0ec87161ed5b4713a4410cc688233291b5 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..eeaa42ce7b5b5105c4ee72e7fb71ec41834ee174 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 d8e3f8f55583934330ddfd676dd406ffa480fb0e 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         | 265 +++++++++++++++++++++++++++++---
 src/providers/krb5/krb5_child_handler.c |   4 +
 src/sss_client/sss_cli.h                |   6 +
 4 files changed, 257 insertions(+), 21 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..2c5e446a0e0c3f55261a39d8d3f3bc09aded3cb9 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,87 @@ 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 +447,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 +508,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[0]->vendor);
+        }
+        if (chl->tokeninfo[0]->token_id != NULL) {
+            kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[0]->token_id);
+        }
+        if (chl->tokeninfo[0]->challenge != NULL) {
+            kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[0]->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 +858,58 @@ 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;
+    size_t idx = 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);
+    }
+    idx += vendor_len +1;
+
+    if (kr->otp_token_id != NULL) {
+        memcpy(msg + idx, kr->otp_token_id, token_id_len);
+    }
+    idx += token_id_len +1;
+
+    if (kr->otp_challenge != NULL) {
+        memcpy(msg + idx, 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;
@@ -1027,9 +1222,18 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
                                         discard_const(password),
                                         sss_krb5_prompter, kr, 0,
                                         NULL, kr->options);
-    if (kerr != 0) {
-        KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
-        return kerr;
+    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,8 +1534,11 @@ 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) {
+    /* 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;
 
@@ -1344,13 +1551,21 @@ static errno_t tgt_req_child(struct krb5_req *kr)
             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 +1766,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 +2533,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 +2714,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..4e453b02d5f4e57b8f5a8f48d7be2434654c0f7e 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 0be7a0478af94784e9a98c44dc6898bbe58974ba 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..15ec2339d95754db2e54f383bf8e423e780e9838 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 4ef5727da8ee7f10d1d19ec88b99e4c181dfc76d 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 | 235 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 234 insertions(+), 2 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index e0fccbf19c751ba61589019e061a39f8e4a844db..e95dba2f6cfd6801439634f09cf5e3ce81444482 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2431,6 +2431,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..0e20d960029001156a57521f23d08542febf1ec7 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,45 @@ 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"));
+                    free(pi->otp_vendor);
+                    pi->otp_vendor = NULL;
+                    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"));
+                    free(pi->otp_token_id);
+                    pi->otp_token_id = NULL;
+                    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 +1155,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 +1210,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 +1345,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 +1393,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;
+    const 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 +1603,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 +1628,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 +1785,27 @@ 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",
@@ -1588,6 +1818,7 @@ static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh,
                 if (ret != PAM_SUCCESS) {
                     D(("failed to get tokens for password change: %s",
                        pam_strerror(pamh, ret)));
+                    overwrite_and_free_pam_items(&pi);
                     return ret;
                 }
                 if (pam_flags & PAM_PRELIM_CHECK) {
-- 
2.1.0

-------------- next part --------------
From c4bfe8c90ad8004819f9b3c08c022ce19abf8d73 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..87960c4db6e698ee5c3b2a24ca18f8d53458c7ca 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 450b8ec5cc0d1812b3338e1855e517aecabea159..dc306848c3b3ba87a5ada1867878e13826da00a8 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 ce1f0965f77199e1103980a8832dc1c86726d30a 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 e5f0a421a218834dfed702824dc6254350fd1ccc..cd9db6a6511052570932978111f27b8ba5111eed 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 1623ae9f3b58b12858ed04b0bdc7b8b18822dc87..82bb9715189f6ab8b8fdd8bae8e01590503d88bf 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)
@@ -6260,6 +6311,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 f75e72d160de558da50a9e2e47fb855d57a34102 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..b003a8a000117722078d299127cf60337a016ca5 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 fa10c4a554661ad71eaa0a088676cb1beb35cc35 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 b003a8a000117722078d299127cf60337a016ca5..91989df428b2a574a0e45ec01569cf94f7841725 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 812a01d152dba5c5a4c7bf60fa2b5b69d4706eb7 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 cd9db6a6511052570932978111f27b8ba5111eed..d132af3813351aff69b3afac41a0735e30531ba2 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 eeaa42ce7b5b5105c4ee72e7fb71ec41834ee174..1ca87a6513b83f601e16300c01041076a7a48cb5 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 1ca1b31938b9b0200c30da650ab4434895d848d4 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 e95dba2f6cfd6801439634f09cf5e3ce81444482..bf56230a1ddecf3f4c382dd1cb27d20f1f01e917 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 \
@@ -2428,6 +2429,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..b8104c680d0e733b713c665e6206dc4b0d379237
--- /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..8ade6d871b840d4d0153bbf56e0d458861ab3816
--- /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 0e20d960029001156a57521f23d08542febf1ec7..e4fa83e12c71bb05dd329686cf2d2df6323ff3bd 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 c6f369952a0ea79fc99393fd7e68337eb994517c 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/sss_client/sss_cli.h        |   4 +-
 src/tests/cmocka/test_pam_srv.c | 965 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 999 insertions(+), 2 deletions(-)
 create mode 100644 src/tests/cmocka/test_pam_srv.c

diff --git a/Makefile.am b/Makefile.am
index bf56230a1ddecf3f4c382dd1cb27d20f1f01e917..8a318c2ffcaabc7c21117cef20b9621d75516382 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
@@ -1776,6 +1777,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/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 317700ef8cfcbb1b58e2a7d1ffcc7f00658fe815..6902d711e30ef327f1c7bcf8348ff419d3f63092 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -430,12 +430,12 @@ enum response_type {
  *  - #SSS_PAM_USER_INFO_OFFLINE_CHPASS
  *     uint32_t | uint32_t | uint32_t
  *    ----------|----------|----------
- *     0x06     | 0x01     | 0x03
+ *     0x06     | 0x04     | 0x03
  *
  *  - #SSS_PAM_USER_INFO_CHPASS_ERROR
  *     uint32_t | uint32_t | uint32_t | uint32_t | uint8_t[3]
  *    ----------|----------|----------|----------|------------
- *     0x06     | 0x05     | 0x04     | 0x03     | abc
+ *     0x06     | 0x0B     | 0x04     | 0x03     | abc
  * @{
  */
 
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
new file mode 100644
index 0000000000000000000000000000000000000000..03faea249a31359d721e82cfd5bc2f9f375d0592
--- /dev/null
+++ b/src/tests/cmocka/test_pam_srv.c
@@ -0,0 +1,965 @@
+/*
+    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);
+    assert_non_null(pctx);
+
+    ret = sss_ncache_init(pctx, &pctx->ncache);
+    assert_int_equal(ret, EOK);
+
+    pctx->neg_timeout = 10;
+
+    ret = sss_hash_create(pctx, 10, &pctx->id_table);
+    assert_int_equal(ret, EOK);
+
+    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_offline_chauthtok_check(uint32_t status,
+                                            uint8_t *body, size_t blen)
+{
+    size_t rp = 0;
+    uint32_t val;
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHTOK_ERR;
+
+    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, 2);
+
+    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);
+    rp += val;
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, SSS_PAM_USER_INFO);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, 4);
+
+    SAFEALIGN_COPY_UINT32(&val, body + rp, &rp);
+    assert_int_equal(val, SSS_PAM_USER_INFO_OFFLINE_CHPASS);
+
+    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);
+}
+
+void test_pam_offline_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);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_offline_chauthtok_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_offline_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);
+
+    pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+    set_cmd_cb(test_pam_offline_chauthtok_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);
+}
+
+
+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),
+        cmocka_unit_test_setup_teardown(test_pam_offline_chauthtok_prelim,
+                                        pam_test_setup, pam_test_teardown),
+        cmocka_unit_test_setup_teardown(test_pam_offline_chauthtok,
+                                        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