This patch addresses: https://fedorahosted.org/sssd/ticket/60

This adds a new option to the [PAM] section of the sssd.conf.

It can be specified by seconds, minutes, hours or days and defines
how long a user can perform offline authentications. If the user
does not perform an online authentication within the timeout, they
will be denied auth once the timeout passes.

-- 
Stephen Gallagher
RHCE 804006346421761

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/
From 784d44eed55896716a4ed46f0630a09df4b6e800 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgall...@redhat.com>
Date: Mon, 19 Oct 2009 15:39:58 -0400
Subject: [PATCH] Add support for offline auth cache timeout

This adds a new option to the [PAM] section of the sssd.conf.

It can be specified by seconds, minutes, hours or days and defines
how long a user can perform offline authentications. If the user
does not perform an online authentication within the timeout, they
will be denied auth once the timeout passes.
---
 server/confdb/confdb.c              |   76 ++++++++++++++++
 server/confdb/confdb.h              |   11 +++
 server/config/etc/sssd.api.conf     |    1 +
 server/db/sysdb.h                   |    1 +
 server/man/sssd.conf.5.xml          |   21 +++++
 server/providers/data_provider.h    |    1 +
 server/responder/pam/pamsrv.c       |   29 +++++-
 server/responder/pam/pamsrv.h       |    6 ++
 server/responder/pam/pamsrv_cache.c |   20 ++++-
 server/responder/pam/pamsrv_cmd.c   |  169 ++++++++++++++++++++++++++++++++++-
 10 files changed, 327 insertions(+), 8 deletions(-)

diff --git a/server/confdb/confdb.c b/server/confdb/confdb.c
index 07d776c..5dd8669 100644
--- a/server/confdb/confdb.c
+++ b/server/confdb/confdb.c
@@ -619,6 +619,82 @@ done:
     return ret;
 }
 
+/* Parse timeout entries in the following formats
+ * 30s: 30 seconds (s is optional)
+ * 45m: 45 minutes
+ * 12h: 12 hours
+ * 3d : 3 days
+ */
+int confdb_get_timeout(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+                            const char *section, const char *attribute,
+                            uint64_t defval, uint64_t *result)
+{
+    char **values = NULL;
+    char *endptr = NULL;
+    long long val;
+    uint64_t retval;
+    int ret;
+
+    ret = confdb_get_param(cdb, ctx, section, attribute, &values);
+    if (ret != EOK) {
+        goto failed;
+    }
+
+    if (values[0]) {
+        if (values[1] != NULL) {
+            /* too many values */
+            ret = EINVAL;
+            goto failed;
+        }
+
+        errno = 0;
+        val = strtoll(values[0], &endptr, 0);
+        if (errno) {
+            ret = errno;
+            goto failed;
+        }
+
+        /* Future-proofing platforms with 128-bit long long */
+        if (val < 0 || val > UINT64_MAX) {
+            ret = ERANGE;
+            goto failed;
+        }
+
+        switch(endptr[0]) {
+        case 's':
+        case '\0':
+            retval = (uint64_t)val;
+            break;
+        case 'm':
+            retval = (uint64_t)val * 60;
+            break;
+        case 'h':
+            retval = (uint64_t)val * 60 * 60;
+            break;
+        case 'd':
+            retval = (uint64_t)val * 60 * 60 * 24;
+            break;
+        default:
+            ret = EINVAL;
+            goto failed;
+        }
+
+    } else {
+        retval = defval;
+    }
+
+    talloc_free(values);
+
+    *result = retval;
+    return EOK;
+
+failed:
+    talloc_free(values);
+    DEBUG(1, ("Failed to read [%s] from [%s], error [%d] (%s)",
+              attribute, section, ret, strerror(ret)));
+    return ret;
+}
+
 int confdb_init(TALLOC_CTX *mem_ctx,
                 struct confdb_ctx **cdb_ctx,
                 char *confdb_location)
diff --git a/server/confdb/confdb.h b/server/confdb/confdb.h
index 0894327..fe685fe 100644
--- a/server/confdb/confdb.h
+++ b/server/confdb/confdb.h
@@ -64,6 +64,7 @@
 
 /* PAM */
 #define CONFDB_PAM_CONF_ENTRY "config/pam"
+#define CONFDB_PAM_OFFLINE_TIMEOUT "offline_auth_cache_timeout"
 
 /* Data Provider */
 #define CONFDB_DP_CONF_ENTRY "config/dp"
@@ -149,6 +150,16 @@ int confdb_get_string_as_list(struct confdb_ctx *cdb, 
TALLOC_CTX *ctx,
                               const char *section, const char *attribute,
                               char ***result);
 
+/* Parse timeout entries in the following formats
+ * 30s: 30 seconds (s is optional)
+ * 45m: 45 minutes
+ * 12h: 12 hours
+ * 3d : 3 days
+ */
+int confdb_get_timeout(struct confdb_ctx *cdb, TALLOC_CTX *ctx,
+                            const char *section, const char *attribute,
+                            uint64_t defval, uint64_t *result);
+
 int confdb_init(TALLOC_CTX *mem_ctx,
                 struct confdb_ctx **cdb_ctx,
                 char *confdb_location);
diff --git a/server/config/etc/sssd.api.conf b/server/config/etc/sssd.api.conf
index 04634ca..7e54b9a 100644
--- a/server/config/etc/sssd.api.conf
+++ b/server/config/etc/sssd.api.conf
@@ -28,6 +28,7 @@ nss_filter_users_in_groups = bool, None, true
 
 [pam]
 # Authentication service
+pam_offline_auth_cache_timeout = int, None
 
 [provider]
 #Available provider types
diff --git a/server/db/sysdb.h b/server/db/sysdb.h
index 9afb957..b390e14 100644
--- a/server/db/sysdb.h
+++ b/server/db/sysdb.h
@@ -60,6 +60,7 @@
 #define SYSDB_KEYBOARD "keyboard"
 #define SYSDB_SESSION "session"
 #define SYSDB_LAST_LOGIN "lastLogin"
+#define SYSDB_LAST_ONLINE_AUTH "lastOnlineAuth"
 #define SYSDB_USERPIC "userPicture"
 
 #define SYSDB_LAST_UPDATE "lastUpdate"
diff --git a/server/man/sssd.conf.5.xml b/server/man/sssd.conf.5.xml
index 4b8a92f..564c53a 100644
--- a/server/man/sssd.conf.5.xml
+++ b/server/man/sssd.conf.5.xml
@@ -327,6 +327,27 @@
                 </varlistentry>
             </variablelist>
         </refsect2>
+        <refsect2 id='PAM'>
+            <title>PAM configuration options</title>
+            <para>
+                These options can be used to configure the
+                Pluggable Authentication Module (PAM) service.
+            </para>
+            <variablelist>
+                <varlistentry>
+                    <term>offline_auth_cache_timeout (integer)</term>
+                    <listitem>
+                        <para>
+                            If the authentication provider is offline, how
+                            long should we allow cached logins.
+                        </para>
+                        <para>
+                            Default: 0 (No limit)
+                        </para>
+                    </listitem>
+                </varlistentry>
+            </variablelist>
+        </refsect2>
     </refsect1>
 
     <refsect1 id='domain-sections'>
diff --git a/server/providers/data_provider.h b/server/providers/data_provider.h
index 7653f07..c1cc722 100644
--- a/server/providers/data_provider.h
+++ b/server/providers/data_provider.h
@@ -109,6 +109,7 @@ struct pam_data {
     struct response_data *resp_list;
 
     bool offline_auth;
+    bool last_auth_saved;
     int priv;
     uid_t pw_uid;
     gid_t gr_gid;
diff --git a/server/responder/pam/pamsrv.c b/server/responder/pam/pamsrv.c
index 4e74063..740f2b0 100644
--- a/server/responder/pam/pamsrv.c
+++ b/server/responder/pam/pamsrv.c
@@ -111,17 +111,33 @@ static void pam_dp_reconnect_init(struct sbus_connection 
*conn, int status, void
     /* pam_shutdown(rctx); */
 }
 
+static errno_t pam_get_config(struct pam_ctx *pctx,
+                              struct resp_ctx *rctx,
+                              struct confdb_ctx *cdb)
+{
+    int ret;
+    ret = confdb_get_timeout(cdb, pctx, CONFDB_PAM_CONF_ENTRY,
+                             CONFDB_PAM_OFFLINE_TIMEOUT, 0,
+                             &pctx->offline_auth_timeout);
+    return ret;
+}
+
 static int pam_process_init(TALLOC_CTX *mem_ctx,
                             struct tevent_context *ev,
                             struct confdb_ctx *cdb)
 {
     struct sss_cmd_table *pam_cmds;
     struct be_conn *iter;
-    struct resp_ctx *rctx;
+    struct pam_ctx *pctx;
     int ret, max_retries;
 
+    pctx = talloc_zero(mem_ctx, struct pam_ctx);
+    if (!pctx) {
+        return ENOMEM;
+    }
+
     pam_cmds = get_pam_cmds();
-    ret = sss_process_init(mem_ctx, ev, cdb,
+    ret = sss_process_init(pctx, ev, cdb,
                            pam_cmds,
                            SSS_PAM_SOCKET_NAME,
                            SSS_PAM_PRIV_SOCKET_NAME,
@@ -130,23 +146,26 @@ static int pam_process_init(TALLOC_CTX *mem_ctx,
                            PAM_SBUS_SERVICE_VERSION,
                            &monitor_pam_interface,
                            "PAM", &pam_dp_interface,
-                           &rctx);
+                           &pctx->rctx);
     if (ret != EOK) {
         return ret;
     }
 
+    pctx->rctx->pvt_ctx = pctx;
+    ret = pam_get_config(pctx, pctx->rctx, pctx->rctx->cdb);
+
     /* Enable automatic reconnection to the Data Provider */
 
     /* FIXME: "retries" is too generic, either get it from a global config
      * or specify these retries are about the sbus connections to DP */
-    ret = confdb_get_int(rctx->cdb, rctx, CONFDB_PAM_CONF_ENTRY,
+    ret = confdb_get_int(pctx->rctx->cdb, pctx->rctx, CONFDB_PAM_CONF_ENTRY,
                          CONFDB_SERVICE_RECON_RETRIES, 3, &max_retries);
     if (ret != EOK) {
         DEBUG(0, ("Failed to set up automatic reconnection\n"));
         return ret;
     }
 
-    for (iter = rctx->be_conns; iter; iter = iter->next) {
+    for (iter = pctx->rctx->be_conns; iter; iter = iter->next) {
         sbus_reconnect_init(iter->conn, max_retries,
                             pam_dp_reconnect_init, iter);
     }
diff --git a/server/responder/pam/pamsrv.h b/server/responder/pam/pamsrv.h
index 0963a05..3f20f29 100644
--- a/server/responder/pam/pamsrv.h
+++ b/server/responder/pam/pamsrv.h
@@ -31,10 +31,16 @@ struct pam_auth_req;
 
 typedef void (pam_dp_callback_t)(struct pam_auth_req *preq);
 
+struct pam_ctx {
+    uint64_t offline_auth_timeout;
+    struct resp_ctx *rctx;
+};
+
 struct pam_auth_req {
     struct cli_ctx *cctx;
     struct sss_domain_info *domain;
 
+    struct pam_ctx *pctx;
     struct pam_data *pd;
 
     pam_dp_callback_t *callback;
diff --git a/server/responder/pam/pamsrv_cache.c 
b/server/responder/pam/pamsrv_cache.c
index 9c5c209..d6463b1 100644
--- a/server/responder/pam/pamsrv_cache.c
+++ b/server/responder/pam/pamsrv_cache.c
@@ -61,17 +61,21 @@ static void pam_cache_auth_callback(void *pvt, int 
ldb_status,
                                     struct ldb_result *res)
 {
     struct pam_auth_req *preq;
+    struct pam_ctx *pctx;
     struct pam_data *pd;
     const char *userhash;
     char *comphash;
     char *password = NULL;
     int i, ret;
+    uint64_t lastLogin = 0;
 
     preq = talloc_get_type(pvt, struct pam_auth_req);
     pd = preq->pd;
 
+    pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx);
+
     if (ldb_status != LDB_SUCCESS) {
-        DEBUG(4, ("User info retireval failed! (%d [%s])\n",
+        DEBUG(4, ("User info retrieval failed! (%d [%s])\n",
                   ldb_status, sysdb_error_to_errno(ldb_status)));
 
         ret = PAM_SYSTEM_ERR;
@@ -86,12 +90,23 @@ static void pam_cache_auth_callback(void *pvt, int 
ldb_status,
     }
 
     if (res->count != 1) {
-        DEBUG(4, ("Too manyt results for user [...@%s].\n",
+        DEBUG(4, ("Too many results for user [...@%s].\n",
                   pd->user, preq->domain->name));
         ret = PAM_SYSTEM_ERR;
         goto done;
     }
 
+    /* Check offline_auth_cache_timeout */
+    lastLogin = ldb_msg_find_attr_as_uint64(res->msgs[0],
+                                            SYSDB_LAST_ONLINE_AUTH,
+                                            0);
+    if (pctx->offline_auth_timeout &&
+        lastLogin + pctx->offline_auth_timeout < time(NULL)) {
+        DEBUG(4, ("Cached user entry is too old."));
+        ret = PAM_AUTHINFO_UNAVAIL;
+        goto done;
+    }
+
     /* TODO: verify user account (failed logins, disabled, expired ...) */
 
     ret = authtok2str(preq, pd->authtok, pd->authtok_size, &password);
@@ -139,6 +154,7 @@ int pam_cache_auth(struct pam_auth_req *preq)
                                   SYSDB_CACHEDPWD,
                                   SYSDB_DISABLED,
                                   SYSDB_LAST_LOGIN,
+                                  SYSDB_LAST_ONLINE_AUTH,
                                   "lastCachedPasswordChange",
                                   "accountExpires",
                                   "failedLoginAttempts",
diff --git a/server/responder/pam/pamsrv_cmd.c 
b/server/responder/pam/pamsrv_cmd.c
index cfc973d..2f44f52 100644
--- a/server/responder/pam/pamsrv_cmd.c
+++ b/server/responder/pam/pamsrv_cmd.c
@@ -30,6 +30,20 @@
 #include "responder/pam/pamsrv.h"
 #include "db/sysdb.h"
 
+struct last_login_request {
+    struct tevent_context *ev;
+    struct sysdb_ctx *dbctx;
+    struct sysdb_attrs *mod_attrs;
+    struct sysdb_handle *handle;
+
+    struct ldb_result *res;
+    int error;
+
+    struct pam_auth_req *preq;
+};
+
+static void pam_reply(struct pam_auth_req *preq);
+
 static int extract_authtok(uint32_t *type, uint32_t *size, uint8_t **tok, 
uint8_t *body, size_t blen, size_t *c) {
     size_t data_size;
 
@@ -266,7 +280,146 @@ static int pam_parse_in_data(struct sss_names_ctx *snctx,
     return EOK;
 }
 
-static void pam_reply(struct pam_auth_req *preq);
+static void prepare_reply(struct last_login_request *llreq)
+{
+    struct pam_data *pd;
+
+    pd = llreq->preq->pd;
+
+    if (llreq->error != EOK && pd->pam_status == PAM_SUCCESS)
+        pd->pam_status = PAM_SYSTEM_ERR;
+
+    llreq->preq->callback(llreq->preq);
+}
+
+static void set_user_last_login_done(struct tevent_req *req)
+{
+    struct last_login_request *llreq =
+        tevent_req_callback_data(req,
+                                 struct last_login_request);
+    int ret;
+
+    ret = sysdb_transaction_commit_recv(req);
+    if(ret != EOK) {
+        DEBUG(2, ("set_last_login failed.\n"));
+        llreq->error = ret;
+    }
+
+    llreq->preq->pd->last_auth_saved = true;
+
+    prepare_reply(llreq);
+}
+
+static void set_user_last_login_req_done(struct tevent_req *subreq);
+static void set_user_last_login_req(struct tevent_req *req)
+{
+    struct last_login_request *llreq =
+        tevent_req_callback_data(req,
+                                 struct last_login_request);
+    struct tevent_req *subreq;
+    int ret;
+
+    ret = sysdb_transaction_recv(req, llreq, &llreq->handle);
+    if (ret != EOK) {
+        llreq->error = ret;
+        return prepare_reply(llreq);
+    }
+
+    subreq = sysdb_set_user_attr_send(llreq, llreq->ev, llreq->handle,
+                                      llreq->preq->domain,
+                                      llreq->preq->pd->user,
+                                      llreq->mod_attrs, SYSDB_MOD_REP);
+    if (!subreq) {
+        /* Cancel transaction */
+        talloc_zfree(llreq->handle);
+        llreq->error = EIO;
+        return prepare_reply(llreq);
+    }
+    tevent_req_set_callback(subreq, set_user_last_login_req_done, llreq);
+}
+
+static void set_user_last_login_req_done(struct tevent_req *subreq)
+{
+    struct last_login_request *llreq =
+        tevent_req_callback_data(subreq,
+                                 struct last_login_request);
+    struct tevent_req *req;
+    int ret;
+
+    ret = sysdb_set_user_attr_recv(subreq);
+    talloc_zfree(subreq);
+
+    DEBUG(4, ("set_user_attr_callback, status [%d][%s]\n", ret, 
strerror(ret)));
+
+    if (ret) {
+        llreq->error = ret;
+        goto fail;
+    }
+
+    req = sysdb_transaction_commit_send(llreq, llreq->ev, llreq->handle);
+    if (!req) {
+        llreq->error = ENOMEM;
+        goto fail;
+    }
+    tevent_req_set_callback(req, set_user_last_login_done, llreq);
+
+fail:
+    DEBUG(2, ("set_last_login failed.\n"));
+
+    /* cancel transaction */
+    talloc_zfree(llreq->handle);
+
+    prepare_reply(llreq);
+}
+
+static errno_t set_last_login(struct pam_auth_req *preq)
+{
+    struct last_login_request *llreq;
+    struct tevent_req *req;
+    errno_t ret;
+
+    llreq = talloc_zero(preq, struct last_login_request);
+    if (!llreq) {
+        ret = ENOMEM;
+        goto fail;
+    }
+    llreq->ev = preq->cctx->ev;
+    llreq->preq = preq;
+
+    llreq->mod_attrs = sysdb_new_attrs(llreq);
+    if (!llreq->mod_attrs) {
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    ret = sysdb_attrs_add_long(llreq->mod_attrs, SYSDB_LAST_ONLINE_AUTH,
+                               (long)time(NULL));
+    if (ret != EOK) {
+        goto fail;
+    }
+
+    ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list,
+                                  preq->domain, &llreq->dbctx);
+    if (ret != EOK) {
+        DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n"));
+        goto fail;
+    }
+
+    req = sysdb_transaction_send(llreq, llreq->ev, llreq->dbctx);
+    if (!req) {
+        DEBUG(1, ("Unable to acquire sysdb transaction lock\n"));
+        ret = EIO;
+        goto fail;
+    }
+
+    tevent_req_set_callback(req, set_user_last_login_req, llreq);
+    return EOK;
+
+fail:
+    talloc_free(llreq);
+    return ret;
+}
+
 static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te,
                             struct timeval tv, void *pvt)
 {
@@ -352,6 +505,20 @@ static void pam_reply(struct pam_auth_req *preq)
         return;
     }
 
+    /* If this was a successful login, save the lastLogin time */
+    if (pd->cmd == SSS_PAM_AUTHENTICATE &&
+        pd->pam_status == PAM_SUCCESS &&
+        !pd->offline_auth &&
+        !pd->last_auth_saved) {
+        ret = set_last_login(preq);
+        if (ret != EOK) {
+            err = ret;
+            goto done;
+        }
+
+        return;
+    }
+
     ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in),
                          &cctx->creq->out);
     if (ret != EOK) {
-- 
1.6.2.5

Attachment: signature.asc
Description: OpenPGP digital signature

_______________________________________________
sssd-devel mailing list
sssd-devel@lists.fedorahosted.org
https://fedorahosted.org/mailman/listinfo/sssd-devel

Reply via email to