From ec5e8045d590eb4d5941f085d76833413d8c7aa6 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Mon, 25 Jan 2010 13:45:16 +0100 Subject: [PATCH] Add offline failed login counter --- server/confdb/confdb.h | 1 + server/config/SSSDConfig.py | 1 + server/config/etc/sssd.api.conf | 1 + server/db/sysdb.h | 2 + server/db/sysdb_ops.c | 206 +++++++++++++++++++++++++++++-- server/man/sssd.conf.5.xml | 16 +++- server/responder/pam/pam_LOCAL_domain.c | 13 +- 7 files changed, 224 insertions(+), 16 deletions(-) diff --git a/server/confdb/confdb.h b/server/confdb/confdb.h index 7f6c63b..5fad2df 100644 --- a/server/confdb/confdb.h +++ b/server/confdb/confdb.h @@ -65,6 +65,7 @@ /* PAM */ #define CONFDB_PAM_CONF_ENTRY "config/pam" #define CONFDB_PAM_CRED_TIMEOUT "offline_credentials_expiration" +#define CONFDB_PAM_FAILED_LOGIN_ATTEMPTS "offline_failed_login_attempts" /* Data Provider */ #define CONFDB_DP_CONF_ENTRY "config/dp" diff --git a/server/config/SSSDConfig.py b/server/config/SSSDConfig.py index d31fbe2..922a37e 100644 --- a/server/config/SSSDConfig.py +++ b/server/config/SSSDConfig.py @@ -61,6 +61,7 @@ option_strings = { # [pam] 'offline_credentials_expiration' : _('How long to allow cached logins between online logins (days)'), + 'offline_failed_login_attempts' : _('How many failed logins attempts are allowed when offline'), # [provider] 'id_provider' : _('Identity provider'), diff --git a/server/config/etc/sssd.api.conf b/server/config/etc/sssd.api.conf index bdb6aab..de7f5d9 100644 --- a/server/config/etc/sssd.api.conf +++ b/server/config/etc/sssd.api.conf @@ -31,6 +31,7 @@ pwfield = str, None, * [pam] # Authentication service offline_credentials_expiration = int, None +offline_failed_login_attempts = int, None [provider] #Available provider types diff --git a/server/db/sysdb.h b/server/db/sysdb.h index 4c25549..48ea93d 100644 --- a/server/db/sysdb.h +++ b/server/db/sysdb.h @@ -66,6 +66,8 @@ #define SYSDB_LAST_LOGIN "lastLogin" #define SYSDB_LAST_ONLINE_AUTH "lastOnlineAuth" #define SYSDB_USERPIC "userPicture" +#define SYSDB_LAST_FAILED_LOGIN "lastFailedLogin" +#define SYSDB_FAILED_LOGIN_ATTEMPTS "failedLoginAttempts" #define SYSDB_LAST_UPDATE "lastUpdate" #define SYSDB_CACHE_EXPIRE "dataExpireTimestamp" diff --git a/server/db/sysdb_ops.c b/server/db/sysdb_ops.c index 469ed8d..f175b81 100644 --- a/server/db/sysdb_ops.c +++ b/server/db/sysdb_ops.c @@ -3168,6 +3168,9 @@ struct tevent_req *sysdb_cache_password_send(TALLOC_CTX *mem_ctx, (long)time(NULL)); if (ret) goto fail; + ret = sysdb_attrs_add_uint32(state->attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0U); + if (ret) goto fail; + state->handle = NULL; if (handle) { @@ -4642,9 +4645,15 @@ struct sysdb_cache_auth_state { struct sss_domain_info *domain; struct sysdb_ctx *sysdb; struct confdb_ctx *cdb; + struct sysdb_attrs *update_attrs; + bool authentication_successful; + struct sysdb_handle *handle; }; static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq); +static void sysdb_cache_auth_transaction_start_done(struct tevent_req *subreq); +static void sysdb_cache_auth_attr_update_done(struct tevent_req *subreq); +static void sysdb_cache_auth_done(struct tevent_req *subreq); struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, @@ -4686,8 +4695,8 @@ struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx, SYSDB_LAST_ONLINE_AUTH, "lastCachedPasswordChange", "accountExpires", - "failedLoginAttempts", - "lastFailedLogin", + SYSDB_FAILED_LOGIN_ATTEMPTS, + SYSDB_LAST_FAILED_LOGIN, NULL}; req = tevent_req_create(mem_ctx, &state, struct sysdb_cache_auth_state); @@ -4703,6 +4712,9 @@ struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx, state->domain = domain; state->sysdb = sysdb; state->cdb = cdb; + state->update_attrs = NULL; + state->authentication_successful = false; + state->handle = NULL; subreq = sysdb_search_user_by_name_send(state, ev, sysdb, NULL, domain, name, attrs); @@ -4725,6 +4737,8 @@ static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq) int i; int ret; uint64_t lastLogin = 0; + uint32_t failed_login_attempts = 0; + int allowed_failed_login_attempts = 0; int cred_expiration; struct tevent_req *req = tevent_req_callback_data(subreq, @@ -4758,12 +4772,36 @@ static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq) cred_expiration)); if (cred_expiration && lastLogin + (cred_expiration * 86400) < time(NULL)) { - DEBUG(4, ("Cached user entry is too old.")); + DEBUG(4, ("Cached user entry is too old.\n")); ret = EACCES; goto done; } - /* TODO: verify user account (failed logins, disabled, expired ...) */ + /* check failed login attempts */ + failed_login_attempts = ldb_msg_find_attr_as_uint(ldb_msg, + SYSDB_FAILED_LOGIN_ATTEMPTS, + 0); + ret = confdb_get_int(state->cdb, state, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_FAILED_LOGIN_ATTEMPTS, 0, + &allowed_failed_login_attempts); + if (ret != EOK) { + DEBUG(1, ("Failed to read the number of allowed failed login " + "attempts.\n")); + ret = EACCES; + goto done; + } + DEBUG(9, ("Failed login attempts [%d], " + "allowed failed login attempts [%d].\n", failed_login_attempts, + allowed_failed_login_attempts)); + + if (allowed_failed_login_attempts && + failed_login_attempts >= allowed_failed_login_attempts) { + DEBUG(4, ("Too many failed logins.\n")); + ret = EACCES; + goto done; + } + + /* TODO: verify user account (disabled, expired ...) */ password = talloc_strndup(state, (const char *) state->authtok, state->authtok_size); @@ -4787,15 +4825,66 @@ static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq) goto done; } + state->update_attrs = sysdb_new_attrs(state); + if (state->update_attrs == NULL) { + DEBUG(1, ("sysdb_new_attrs failed.\n")); + goto done; + } + if (strcmp(userhash, comphash) == 0) { /* TODO: probable good point for audit logging */ DEBUG(4, ("Hashes do match!\n")); - ret = EOK; - goto done; + state->authentication_successful = true; + + ret = sysdb_attrs_add_time_t(state->update_attrs, SYSDB_LAST_LOGIN, + time(NULL)); + if (ret != EOK) { + DEBUG(3, ("sysdb_attrs_add_long failed, " + "but authentication is successful.\n")); + ret = EOK; + goto done; + } + + ret = sysdb_attrs_add_uint32(state->update_attrs, + SYSDB_FAILED_LOGIN_ATTEMPTS, 0U); + if (ret != EOK) { + DEBUG(3, ("sysdb_attrs_add_long failed, " + "but authentication is successful.\n")); + ret = EOK; + goto done; + } + + + } else { + DEBUG(4, ("Authentication failed.\n")); + state->authentication_successful = false; + + ret = sysdb_attrs_add_time_t(state->update_attrs, SYSDB_LAST_FAILED_LOGIN, + time(NULL)); + if (ret != EOK) { + DEBUG(3, ("sysdb_attrs_add_long failed\n.")); + ret = EINVAL; + goto done; + } + + ret = sysdb_attrs_add_uint32(state->update_attrs, + SYSDB_FAILED_LOGIN_ATTEMPTS, + ++failed_login_attempts); + if (ret != EOK) { + DEBUG(3, ("sysdb_attrs_add_long failed.\n")); + ret = EINVAL; + goto done; + } } - DEBUG(4, ("Authentication failed.\n")); - ret = EINVAL; + subreq = sysdb_transaction_send(state, state->ev, state->sysdb); + if (subreq == NULL) { + DEBUG(1, ("sysdb_transaction_send failed.\n")); + goto done; + } + tevent_req_set_callback(subreq, sysdb_cache_auth_transaction_start_done, + req); + return; done: if (password) for (i = 0; password[i]; i++) password[i] = 0; @@ -4807,7 +4896,106 @@ done: return; } +static void sysdb_cache_auth_transaction_start_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + struct sysdb_cache_auth_state *state = tevent_req_data(req, + struct sysdb_cache_auth_state); + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(1, ("sysdb_transaction_send failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + + subreq = sysdb_set_user_attr_send(state, state->ev, state->handle, + state->domain, state->name, + state->update_attrs, LDB_FLAG_MOD_REPLACE); + if (subreq == NULL) { + DEBUG(1, ("sysdb_set_user_attr_send failed.\n")); + goto done; + } + tevent_req_set_callback(subreq, sysdb_cache_auth_attr_update_done, + req); + return; + +done: + if (state->authentication_successful) { + tevent_req_done(req); + } else { + tevent_req_error(req, EINVAL); + } + return; +} + +static void sysdb_cache_auth_attr_update_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + struct sysdb_cache_auth_state *state = tevent_req_data(req, + struct sysdb_cache_auth_state); + + ret = sysdb_set_user_attr_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(1, ("sysdb_set_user_attr_send failed [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + subreq = sysdb_transaction_commit_send(state, state->ev, state->handle); + if (subreq == NULL) { + DEBUG(1, ("sysdb_transaction_commit_send failed.\n")); + goto done; + } + tevent_req_set_callback(subreq, sysdb_cache_auth_done, req); + return; + +done: + if (state->authentication_successful) { + tevent_req_done(req); + } else { + tevent_req_error(req, EINVAL); + } + return; +} + +static void sysdb_cache_auth_done(struct tevent_req *subreq) +{ + int ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + + struct sysdb_cache_auth_state *state = tevent_req_data(req, + struct sysdb_cache_auth_state); + + ret = sysdb_transaction_commit_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(1, ("sysdb_transaction_commit_send failed [%d][%s].\n", + ret, strerror(ret))); + } + + if (state->authentication_successful) { + tevent_req_done(req); + } else { + tevent_req_error(req, EINVAL); + } + return; +} + int sysdb_cache_auth_recv(struct tevent_req *req) { + struct sysdb_cache_auth_state *state = tevent_req_data(req, + struct sysdb_cache_auth_state); TEVENT_REQ_RETURN_ON_ERROR(req); - return EOK; + + return (state->authentication_successful ? EOK : EINVAL); } diff --git a/server/man/sssd.conf.5.xml b/server/man/sssd.conf.5.xml index c9c5568..341114c 100644 --- a/server/man/sssd.conf.5.xml +++ b/server/man/sssd.conf.5.xml @@ -338,7 +338,21 @@ If the authentication provider is offline, how - long should we allow cached logins (in days). + long should we allow cached logins (in days since + the last successful online login). + + + Default: 0 (No limit) + + + + + + offline_failed_login_attempts (integer) + + + If the authentication provider is offline, how + many failed login attempts are allowed. Default: 0 (No limit) diff --git a/server/responder/pam/pam_LOCAL_domain.c b/server/responder/pam/pam_LOCAL_domain.c index 9d3738c..1a5d76b 100644 --- a/server/responder/pam/pam_LOCAL_domain.c +++ b/server/responder/pam/pam_LOCAL_domain.c @@ -164,7 +164,7 @@ static void do_successful_login(struct LOCAL_request *lreq) NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), lreq->error, ret, done); - ret = sysdb_attrs_add_long(lreq->mod_attrs, "failedLoginAttempts", 0L); + ret = sysdb_attrs_add_long(lreq->mod_attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0L); NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), lreq->error, ret, done); @@ -199,16 +199,17 @@ static void do_failed_login(struct LOCAL_request *lreq) lreq->error, ENOMEM, done); ret = sysdb_attrs_add_long(lreq->mod_attrs, - "lastFailedLogin", (long)time(NULL)); + SYSDB_LAST_FAILED_LOGIN, (long)time(NULL)); NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), lreq->error, ret, done); failedLoginAttempts = ldb_msg_find_attr_as_int(lreq->res->msgs[0], - "failedLoginAttempts", 0); + SYSDB_FAILED_LOGIN_ATTEMPTS, + 0); failedLoginAttempts++; ret = sysdb_attrs_add_long(lreq->mod_attrs, - "failedLoginAttempts", + SYSDB_FAILED_LOGIN_ATTEMPTS, (long)failedLoginAttempts); NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), lreq->error, ret, done); @@ -436,10 +437,10 @@ int LOCAL_pam_handler(struct pam_auth_req *preq) SYSDB_LAST_LOGIN, "lastPasswordChange", "accountExpires", - "failedLoginAttempts", + SYSDB_FAILED_LOGIN_ATTEMPTS, "passwordHint", "passwordHistory", - "lastFailedLogin", + SYSDB_LAST_FAILED_LOGIN, NULL}; DEBUG(4, ("LOCAL pam handler.\n")); -- 1.6.6