From 0aa18cc0bf3447ca734476926724f1632e160807 Mon Sep 17 00:00:00 2001 From: Pavel Reichl Date: Thu, 16 Apr 2015 03:41:58 -0400 Subject: PAM: authenticate agains cache Enable authenticating users from cache even when SSSD is in online mode. Introduce new option `cached_auth_timeout`. Resolves: https://fedorahosted.org/sssd/ticket/1807 Reviewed-by: Jakub Hrozek --- src/confdb/confdb.c | 62 +++++++++++++ src/confdb/confdb.h | 2 + src/config/SSSDConfig/__init__.py.in | 1 + src/config/SSSDConfigTest.py | 6 +- src/config/etc/sssd.api.conf | 1 + src/man/sssd.conf.5.xml | 24 +++++ src/responder/pam/pamsrv.h | 3 + src/responder/pam/pamsrv_cmd.c | 170 +++++++++++++++++++++++++++++++++-- 8 files changed, 261 insertions(+), 8 deletions(-) diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c index 9af754912..3a8a1c01b 100644 --- a/src/confdb/confdb.c +++ b/src/confdb/confdb.c @@ -760,6 +760,59 @@ static uint32_t confdb_get_min_id(struct sss_domain_info *domain) return defval; } +static errno_t init_cached_auth_timeout(struct confdb_ctx *cdb, + struct ldb_message *msg, + uint32_t *_cached_auth_timeout) +{ + int cred_expiration; + int id_timeout; + errno_t ret; + uint32_t cached_auth_timeout; + + ret = get_entry_as_uint32(msg, &cached_auth_timeout, + CONFDB_DOMAIN_CACHED_AUTH_TIMEOUT, 0); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Invalid value for [%s]\n", CONFDB_DOMAIN_CACHED_AUTH_TIMEOUT); + goto done; + } + + ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CRED_TIMEOUT, 0, &cred_expiration); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read expiration time of offline credentials.\n"); + goto done; + } + + /* convert from days to seconds */ + cred_expiration *= 3600 * 24; + if (cred_expiration != 0 && + cred_expiration < cached_auth_timeout) { + cached_auth_timeout = cred_expiration; + } + + /* Set up the PAM identity timeout */ + ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ID_TIMEOUT, 5, + &id_timeout); + if (ret != EOK) goto done; + + if (cached_auth_timeout > id_timeout) { + DEBUG(SSSDBG_MINOR_FAILURE, + "cached_auth_timeout is greater than pam_id_timeout so be aware " + "that back end could be called to handle initgroups.\n"); + } + + ret = EOK; + +done: + if (ret == EOK) { + *_cached_auth_timeout = cached_auth_timeout; + } + return ret; +} + static int confdb_get_domain_internal(struct confdb_ctx *cdb, TALLOC_CTX *mem_ctx, const char *name, @@ -1277,6 +1330,15 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb, goto done; } + ret = init_cached_auth_timeout(cdb, res->msgs[0], + &domain->cached_auth_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "init_cached_auth_timeout failed: %s:[%d].\n", + sss_strerror(ret), ret); + goto done; + } + domain->has_views = false; domain->view_name = NULL; diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 801a13fc2..b2ec2e0b9 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -188,6 +188,7 @@ #define CONFDB_DOMAIN_REFRESH_EXPIRED_INTERVAL "refresh_expired_interval" #define CONFDB_DOMAIN_OFFLINE_TIMEOUT "offline_timeout" #define CONFDB_DOMAIN_SUBDOMAIN_INHERIT "subdomain_inherit" +#define CONFDB_DOMAIN_CACHED_AUTH_TIMEOUT "cached_auth_timeout" /* Local Provider */ #define CONFDB_LOCAL_DEFAULT_SHELL "default_shell" @@ -248,6 +249,7 @@ struct sss_domain_info { uint32_t refresh_expired_interval; uint32_t subdomain_refresh_interval; + uint32_t cached_auth_timeout; int pwd_expiration_warning; diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index f2d9bf019..4d45e42af 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -149,6 +149,7 @@ option_strings = { 'subdomain_enumerate' : _('Control enumeration of trusted domains'), 'subdomain_refresh_interval' : _('How often should subdomains list be refreshed'), 'subdomain_inherit' : _('List of options that should be inherited into a subdomain'), + 'cached_auth_timeout' : _('How long can cached credentials be used for cached authentication'), # [provider/ipa] 'ipa_domain' : _('IPA domain'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index c6ba9f051..1d6107cea 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -547,7 +547,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'subdomains_provider', 'realmd_tags', 'subdomain_refresh_interval', - 'subdomain_inherit'] + 'subdomain_inherit', + 'cached_auth_timeout'] self.assertTrue(type(options) == dict, "Options should be a dictionary") @@ -910,7 +911,8 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'subdomains_provider', 'realmd_tags', 'subdomain_refresh_interval', - 'subdomain_inherit'] + 'subdomain_inherit', + 'cached_auth_timeout'] self.assertTrue(type(options) == dict, "Options should be a dictionary") diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index 7ad84cd82..29fd896cc 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -133,6 +133,7 @@ description = str, None, false realmd_tags = str, None, false subdomain_refresh_interval = int, None, false subdomain_inherit = str, None, false +cached_auth_timeout = int, None, false #Entry cache timeouts entry_cache_user_timeout = int, None, false diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 75d13a631..7d3a57b0e 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -2176,6 +2176,30 @@ pam_account_expired_message = Account expired, please call help desk. + + cached_auth_timeout (int) + + + Specifies time in seconds since last successful + online authentication for which user will be + authenticated using cached credentials while + SSSD is in the online mode. + + + Special value 0 implies that this feature is + disabled. + + + Please note that if cached_auth_timeout + is longer than pam_id_timeout then the + back end could be called to handle + initgroups. + + + Default: 0 + + + diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h index 066f35a42..027800646 100644 --- a/src/responder/pam/pamsrv.h +++ b/src/responder/pam/pamsrv.h @@ -60,6 +60,9 @@ struct pam_auth_req { bool is_uid_trusted; bool check_provider; void *data; + bool use_cached_auth; + /* whether cached authentication was tried and failed */ + bool cached_auth_failed; struct pam_auth_dp_req *dpreq_spy; }; diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 3bd676395..c144406aa 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -45,6 +45,10 @@ enum pam_verbosity { static errno_t pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, const char *username); +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value); static void pam_reply(struct pam_auth_req *preq); static errno_t pack_user_info_account_expired(TALLOC_CTX *mem_ctx, @@ -568,7 +572,7 @@ static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, 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); + time_t expire_date, time_t delayed_until, bool cached_auth); static void pam_reply(struct pam_auth_req *preq) { @@ -606,14 +610,21 @@ static void pam_reply(struct pam_auth_req *preq) DEBUG(SSSDBG_FUNC_DATA, "pam_reply called with result [%d]: %s.\n", pd->pam_status, pam_strerror(NULL, pd->pam_status)); + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { - if (pd->pam_status == PAM_AUTHINFO_UNAVAIL) { switch(pd->cmd) { case SSS_PAM_AUTHENTICATE: if ((preq->domain != NULL) && (preq->domain->cache_credentials == true) && (pd->offline_auth == false)) { const char *password = NULL; + bool use_cached_auth; + + /* backup value of preq->use_cached_auth*/ + use_cached_auth = preq->use_cached_auth; + /* set to false to avoid entering this branch when pam_reply() + * is recursively called from pam_handle_cached_login() */ + preq->use_cached_auth = false; /* do auth with offline credentials */ pd->offline_auth = true; @@ -637,7 +648,8 @@ static void pam_reply(struct pam_auth_req *preq) pctx->rctx->cdb, false, &exp_date, &delay_until); - pam_handle_cached_login(preq, ret, exp_date, delay_until); + pam_handle_cached_login(preq, ret, exp_date, delay_until, + use_cached_auth); return; } break; @@ -805,8 +817,11 @@ done: sss_cmd_done(cctx, preq); } +static void pam_dom_forwarder(struct pam_auth_req *preq); + static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, - time_t expire_date, time_t delayed_until) + time_t expire_date, time_t delayed_until, + bool use_cached_auth) { uint32_t resp_type; size_t resp_len; @@ -855,6 +870,18 @@ static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, } } break; + case PAM_AUTH_ERR: + /* Was this attempt to authenticate from cache? */ + if (use_cached_auth) { + /* Don't try cached authentication again, try online check. */ + DEBUG(SSSDBG_FUNC_DATA, + "Cached authentication failed for: %s\n", + preq->pd->user); + preq->cached_auth_failed = true; + pam_dom_forwarder(preq); + return; + } + break; default: DEBUG(SSSDBG_TRACE_LIBS, "cached login returned: %d\n", preq->pd->pam_status); @@ -869,7 +896,6 @@ static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr); static int pam_check_user_search(struct pam_auth_req *preq); static int pam_check_user_done(struct pam_auth_req *preq, int ret); -static void pam_dom_forwarder(struct pam_auth_req *preq); /* TODO: we should probably return some sort of cookie that is set in the * PAM_ENVIRONMENT, so that we can save performing some calls and cache @@ -1423,6 +1449,76 @@ static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, } } +static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, + const char* user, + struct confdb_ctx *cdb, + int cached_auth_timeout, + bool *_result) +{ + errno_t ret; + bool result; + uint64_t last_login; + + ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + result = time(NULL) < (last_login + cached_auth_timeout); + ret = EOK; + +done: + if (ret == EOK) { + *_result = result; + } + return ret; +} + +static bool pam_is_cmd_cachable(int cmd) +{ + bool is_cachable; + + switch(cmd) { + case SSS_PAM_AUTHENTICATE: + is_cachable = true; + break; + default: + is_cachable = false; + } + + return is_cachable; +} + +static bool pam_can_user_cache_auth(struct confdb_ctx *cdb, + struct sss_domain_info *domain, + int pam_cmd, const char* user, + bool cached_auth_failed) +{ + errno_t ret; + bool result = false; + + if (!cached_auth_failed /* don't try cached auth again */ + && domain->cache_credentials + && domain->cached_auth_timeout > 0 + && pam_is_cmd_cachable(pam_cmd)) { + + ret = pam_is_last_online_login_fresh(domain, user, cdb, + domain->cached_auth_timeout, + &result); + if (ret != EOK) { + /* non-critical, consider fail as 'non-fresh value' */ + DEBUG(SSSDBG_MINOR_FAILURE, + "pam_is_last_online_login_fresh failed: %s:[%d]\n", + sss_strerror(ret), ret); + } + } + + return result; +} + static void pam_dom_forwarder(struct pam_auth_req *preq) { int ret; @@ -1454,7 +1550,17 @@ static void pam_dom_forwarder(struct pam_auth_req *preq) return; } - if (!NEED_CHECK_PROVIDER(preq->domain->provider)) { + if (pam_can_user_cache_auth(pctx->rctx->cdb, + preq->domain, + preq->pd->cmd, + preq->pd->user, + preq->cached_auth_failed)) { + preq->use_cached_auth = true; + pam_reply(preq); + return; + } + + if (!NEED_CHECK_PROVIDER(preq->domain->provider) ) { preq->callback = pam_reply; ret = LOCAL_pam_handler(preq); } @@ -1585,3 +1691,55 @@ pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, { return pam_set_last_online_auth_with_curr_token(domain, username, 0); } + +static errno_t +pam_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; +} -- cgit