From 5f12df678d68f5447daf7de7804a1ab77fb3e6e2 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher 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 | 25 +++++ 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 | 171 ++++++++++++++++++++++++++++++++++- 10 files changed, 333 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..da38506 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 +offline_auth_cache_timeout = str, 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..93ed1f2 100644 --- a/server/man/sssd.conf.5.xml +++ b/server/man/sssd.conf.5.xml @@ -327,6 +327,31 @@ + + PAM configuration options + + These options can be used to configure the + Pluggable Authentication Module (PAM) service. + + + + offline_auth_cache_timeout (string) + + + If the authentication provider is offline, how + long should we allow cached logins. + This value can be specified in seconds (30s), + minutes (45m), hours (12h) or days (14d). + If the qualifier is ommitted, seconds are + assumed. + + + Default: 0 (No limit) + + + + + diff --git a/server/providers/data_provider.h b/server/providers/data_provider.h index 57b318f..1e97d04 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@%s].\n", + DEBUG(4, ("Too many results for user [%s@%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..f05709f 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,22 @@ static void pam_reply(struct pam_auth_req *preq) return; } + /* If this was a successful login, save the lastLogin time */ + if (preq->domain->cache_credentials && + pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + !pd->offline_auth && + !pd->last_auth_saved && + NEED_CHECK_PROVIDER(preq->domain->provider)) { + 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