>From 94942b5cd586595abd48afa7813e3096bcdd4fbb Mon Sep 17 00:00:00 2001
From: Pavel Reichl <preichl@redhat.com>
Date: Mon, 18 May 2015 09:59:38 -0400
Subject: [PATCH 1/2] sysdb: new attribute lastOnlineAuthWithCurrentToken

Introduce new user attribute lastOnlineAuthWithCurrentToken. This attribute
behaves similarly to lastOnlineAuth but is set to NULL after password is
changed.

This attribute is needed for use-case when cached authentication is used, to
request online authentication after password is locally changed.
---
 src/db/sysdb.h                 | 14 +++++++
 src/db/sysdb_ops.c             | 95 ++++++++++++++++++++++++++++++++++++++++++
 src/providers/ldap/ldap_auth.c | 38 +++++++++++------
 src/responder/pam/pamsrv_cmd.c |  7 ++++
 src/tests/sysdb-tests.c        | 76 +++++++++++++++++++++++++++++++++
 5 files changed, 217 insertions(+), 13 deletions(-)

diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index c3d2c1406321e01b325e04e0e3f263893eb91b2e..3934e6e11afb63b1f51ed81f18452d1bd79190ed 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -87,6 +87,7 @@
 #define SYSDB_LAST_ONLINE_AUTH "lastOnlineAuth"
 #define SYSDB_LAST_FAILED_LOGIN "lastFailedLogin"
 #define SYSDB_FAILED_LOGIN_ATTEMPTS "failedLoginAttempts"
+#define SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN "lastOnlineAuthWithCurrentToken"
 
 #define SYSDB_LAST_UPDATE "lastUpdate"
 #define SYSDB_CACHE_EXPIRE "dataExpireTimestamp"
@@ -897,6 +898,19 @@ int sysdb_cache_password_ex(struct sss_domain_info *domain,
                             enum sss_authtok_type authtok_type,
                             size_t second_factor_size);
 
+errno_t
+sysdb_set_last_online_auth_with_curr_token(struct sss_domain_info *domain,
+                                           const char *username,
+                                           uint64_t value);
+errno_t
+sysdb_null_last_online_auth_with_curr_token(struct sss_domain_info *domain,
+                                            const char *username);
+
+errno_t
+sysdb_get_last_online_auth_with_curr_token(struct sss_domain_info *domain,
+                                           const char *name,
+                                           uint64_t *_value);
+
 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 9afb0d7d74cdbc91570f4fe6485790d2a4eeb4be..d9394ccca48e83fed58d2fc0b7e8022d4da23b18 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -2301,6 +2301,101 @@ int sysdb_cache_password(struct sss_domain_info *domain,
                                    SSS_AUTHTOK_TYPE_PASSWORD, 0);
 }
 
+errno_t
+sysdb_set_last_online_auth_with_curr_token(struct sss_domain_info *domain,
+                                           const char *username,
+                                           uint64_t value)
+{
+    TALLOC_CTX *tmp_ctx;
+    struct sysdb_attrs *attrs;
+    int ret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        ERROR_OUT(ret, ENOMEM, done);
+    }
+
+    attrs = sysdb_new_attrs(tmp_ctx);
+    if (attrs == NULL) {
+        ERROR_OUT(ret, ENOMEM, done);
+    }
+
+    ret = sysdb_attrs_add_time_t(attrs,
+                                 SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN,
+                                 value);
+    if (ret != EOK) { goto done; }
+
+    ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP);
+    if (ret != EOK) { goto done; }
+
+done:
+    if (ret != EOK) {
+        DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret));
+    }
+
+    talloc_zfree(tmp_ctx);
+    return ret;
+}
+
+errno_t
+sysdb_null_last_online_auth_with_curr_token(struct sss_domain_info *domain,
+                                            const char *username)
+{
+    return sysdb_set_last_online_auth_with_curr_token(domain, username, 0);
+}
+
+errno_t
+sysdb_get_last_online_auth_with_curr_token(struct sss_domain_info *domain,
+                                           const char *name,
+                                           uint64_t *_value)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL };
+    struct ldb_message *ldb_msg;
+    uint64_t value;
+    errno_t ret;
+
+    if (name == NULL || *name == '\0') {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (domain->sysdb == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n");
+        ret = EINVAL;
+        goto done;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "sysdb_search_user_by_name failed [%d][%s].\n",
+              ret, strerror(ret));
+        goto done;
+    }
+
+    /* Check offline_auth_cache_timeout */
+    value = ldb_msg_find_attr_as_uint64(ldb_msg,
+                                        SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN,
+                                        0);
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        *_value = value;
+    }
+
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
 /* =Custom Search================== */
 
 int sysdb_search_custom(TALLOC_CTX *mem_ctx,
diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c
index 81717942613b4a91ebab668ba2ecfe13caab38be..0521b46fef9167d22aff6808b8cb803bd6ac5752 100644
--- a/src/providers/ldap/ldap_auth.c
+++ b/src/providers/ldap/ldap_auth.c
@@ -1147,21 +1147,33 @@ static void sdap_pam_chpass_done(struct tevent_req *req)
         }
     }
 
-    if (state->pd->pam_status == PAM_SUCCESS &&
-        dp_opt_get_bool(state->ctx->opts->basic,
-                        SDAP_CHPASS_UPDATE_LAST_CHANGE)) {
-        lastchanged_name = state->ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name;
-
-        subreq = sdap_modify_shadow_lastchange_send(state, be_ctx->ev,
-                                                    state->sh, state->dn,
-                                                    lastchanged_name);
-        if (subreq == NULL) {
-            state->pd->pam_status = PAM_SYSTEM_ERR;
-            goto done;
+    if (state->pd->pam_status == PAM_SUCCESS) {
+
+        if (state->pd->cmd == SSS_PAM_CHAUTHTOK) {
+            ret = sysdb_null_last_online_auth_with_curr_token(be_ctx->domain,
+                                                              state->pd->user);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "sysdb_null_last_online_auth_with_curr_token failed: "
+                      "%s [%d].\n", sss_strerror(ret), ret);
+            }
         }
 
-        tevent_req_set_callback(subreq, sdap_lastchange_done, state);
-        return;
+        if (dp_opt_get_bool(state->ctx->opts->basic,
+                            SDAP_CHPASS_UPDATE_LAST_CHANGE)) {
+            lastchanged_name = state->ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name;
+
+            subreq = sdap_modify_shadow_lastchange_send(state, be_ctx->ev,
+                                                        state->sh, state->dn,
+                                                        lastchanged_name);
+            if (subreq == NULL) {
+                state->pd->pam_status = PAM_SYSTEM_ERR;
+                goto done;
+            }
+
+            tevent_req_set_callback(subreq, sdap_lastchange_done, state);
+            return;
+        }
     }
 
 done:
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 1ca87a6513b83f601e16300c01041076a7a48cb5..e1921043199a347e6f8e499dbb1dd2ae579be713 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -426,6 +426,13 @@ static errno_t set_last_login(struct pam_auth_req *preq)
         goto fail;
     }
 
+    ret = sysdb_attrs_add_time_t(attrs,
+                                 SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN,
+                                 time(NULL));
+    if (ret != EOK) {
+        goto fail;
+    }
+
     ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL));
     if (ret != EOK) {
         goto fail;
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index 82bb9715189f6ab8b8fdd8bae8e01590503d88bf..0ee11ab578fe1b8a80b5e07233e6625879f72a9d 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -2032,6 +2032,80 @@ START_TEST (test_sysdb_cached_authentication)
 }
 END_TEST
 
+START_TEST (test_sysdb_online_auth_with_curr_token)
+{
+    struct sysdb_test_ctx *test_ctx;
+    errno_t ret;
+    const char *username;
+    const char *non_exist_username;
+    uint64_t value;
+
+    /* Setup */
+    ret = setup_sysdb_tests(&test_ctx);
+    if (ret != EOK) {
+        fail("Could not set up the test");
+        return;
+    }
+
+    username = "testuser_newid";
+    non_exist_username = "non_existing_user";
+
+    ret = sysdb_add_user(test_ctx->domain, username,
+                         0, 0, username, "/", "/bin/bash",
+                         NULL, NULL,/* attrs */ 0, 0);
+    fail_if(ret != EOK, "Could not store user %s", username);
+
+    /* null & get */
+    ret = sysdb_null_last_online_auth_with_curr_token(test_ctx->domain,
+                                                      username);
+    fail_if(ret != EOK,
+            "sysdb_set_last_online_auth_with_curr_token failed for "
+            "%s - %s:[%d]", username, sss_strerror(ret), ret);
+
+    ret = sysdb_get_last_online_auth_with_curr_token(test_ctx->domain,
+                                                     username, &value);
+    fail_if(ret != EOK,
+            "sysdb_get_last_online_auth_with_curr_token failed for "
+            "%s - %s:[%d]", username, sss_strerror(ret), ret);
+    ck_assert_int_eq(value, 0);
+
+
+    /* set & get */
+    ret = sysdb_set_last_online_auth_with_curr_token(test_ctx->domain,
+                                                     username, 12345);
+    fail_if(ret != EOK,
+            "sysdb_set_last_online_auth_with_curr_token failed for "
+            "%s - %s:[%d]", username, sss_strerror(ret), ret);
+
+    ret = sysdb_get_last_online_auth_with_curr_token(test_ctx->domain,
+                                                     username, &value);
+    fail_if(ret != EOK,
+            "sysdb_get_last_online_auth_with_curr_token failed for "
+            "%s - %s:[%d]", username, sss_strerror(ret), ret);
+    ck_assert_int_eq(value, 12345);
+
+    /* set & get non_existing_user */
+    ret = sysdb_set_last_online_auth_with_curr_token(test_ctx->domain,
+                                                     non_exist_username, 12345);
+    fail_if(ret != ENOENT,
+            "sysdb_set_last_online_auth_with_curr_token failed for "
+            "%s - %s:[%d]", non_exist_username, sss_strerror(ret), ret);
+
+    ret = sysdb_get_last_online_auth_with_curr_token(test_ctx->domain,
+                                                     non_exist_username,
+                                                     &value);
+    fail_if(ret != ENOENT,
+            "sysdb_get_last_online_auth_with_curr_token failed for "
+            "%s - %s:[%d]", non_exist_username, sss_strerror(ret), ret);
+
+    ret = sysdb_delete_user(test_ctx->domain, username, 0);
+    fail_unless(ret == EOK, "sysdb_delete_user error [%d][%s]",
+                            ret, strerror(ret));
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
 START_TEST (test_sysdb_prepare_asq_test_user)
 {
     struct sysdb_test_ctx *test_ctx;
@@ -6313,6 +6387,8 @@ Suite *create_sysdb_suite(void)
 
     tcase_add_loop_test(tc_sysdb, test_sysdb_cache_password_ex, 27010, 27011);
 
+    tcase_add_test(tc_sysdb, test_sysdb_online_auth_with_curr_token);
+
     /* 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

