From e6d216f88f56549abd504db366ae6f4a3d935cee 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/Makefile.am | 13 +- server/confdb/confdb.h | 4 + server/config/SSSDConfig.py | 2 + server/config/etc/sssd.api.conf | 2 + server/db/sysdb.h | 6 + server/db/sysdb_ops.c | 240 +++++++++++++++++++++- server/man/sssd.conf.5.xml | 36 ++++- server/responder/pam/pam_LOCAL_domain.c | 13 +- server/tests/auth-tests.c | 332 +++++++++++++++++++++++++++++++ 9 files changed, 631 insertions(+), 17 deletions(-) create mode 100644 server/tests/auth-tests.c diff --git a/server/Makefile.am b/server/Makefile.am index 7ba7ffa..3c02f65 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -75,7 +75,8 @@ if HAVE_CHECK files-tests \ refcount-tests \ fail_over-tests \ - find_uid-tests + find_uid-tests \ + auth-tests endif check_PROGRAMS = \ @@ -576,6 +577,16 @@ find_uid_tests_LDADD = \ $(DHASH_LIBS) \ $(CHECK_LIBS) +auth_tests_SOURCES = \ + tests/auth-tests.c \ + $(SSSD_UTIL_OBJ) +auth_tests_CFLAG = \ + $(AM_CFLAGS) \ + $(CHECK_CFLAGS) +auth_tests_LDADD = \ + $(SSSD_LIBS) \ + $(CHECK_LIBS) + endif stress_tests_SOURCES = \ diff --git a/server/confdb/confdb.h b/server/confdb/confdb.h index 7f6c63b..874f608 100644 --- a/server/confdb/confdb.h +++ b/server/confdb/confdb.h @@ -65,6 +65,10 @@ /* 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" +#define CONFDB_DEFAULT_PAM_FAILED_LOGIN_ATTEMPTS 0 +#define CONFDB_PAM_FAILED_LOGIN_DELAY "offline_failed_login_delay" +#define CONFDB_DEFAULT_PAM_FAILED_LOGIN_DELAY 5 /* Data Provider */ #define CONFDB_DP_CONF_ENTRY "config/dp" diff --git a/server/config/SSSDConfig.py b/server/config/SSSDConfig.py index b08e9f4..b751e4d 100644 --- a/server/config/SSSDConfig.py +++ b/server/config/SSSDConfig.py @@ -61,6 +61,8 @@ 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'), + 'offline_failed_login_delay' : _('How long (minutes) to deny login after offline_failed_login_attempts has been reached'), # [provider] 'id_provider' : _('Identity provider'), diff --git a/server/config/etc/sssd.api.conf b/server/config/etc/sssd.api.conf index bdb6aab..5fc8e78 100644 --- a/server/config/etc/sssd.api.conf +++ b/server/config/etc/sssd.api.conf @@ -31,6 +31,8 @@ pwfield = str, None, * [pam] # Authentication service offline_credentials_expiration = int, None +offline_failed_login_attempts = int, None +offline_failed_login_delay = int, None [provider] #Available provider types diff --git a/server/db/sysdb.h b/server/db/sysdb.h index 4c25549..9b77edf 100644 --- a/server/db/sysdb.h +++ b/server/db/sysdb.h @@ -22,6 +22,7 @@ #ifndef __SYS_DB_H__ #define __SYS_DB_H__ +#include "util/util.h" #include "confdb/confdb.h" #include @@ -66,6 +67,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" @@ -543,6 +546,9 @@ struct tevent_req *sysdb_cache_password_send(TALLOC_CTX *mem_ctx, int sysdb_cache_password_recv(struct tevent_req *req); +errno_t check_failed_login_attempts(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, + struct ldb_message *ldb_msg, + uint32_t *failed_login_attempts); struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct sysdb_ctx *sysdb, diff --git a/server/db/sysdb_ops.c b/server/db/sysdb_ops.c index 469ed8d..becd712 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,66 @@ 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; }; +errno_t check_failed_login_attempts(TALLOC_CTX *mem_ctx, struct confdb_ctx *cdb, + struct ldb_message *ldb_msg, + uint32_t *failed_login_attempts) +{ + int ret; + int allowed_failed_login_attempts; + int failed_login_delay; + time_t last_failed_login; + + *failed_login_attempts = ldb_msg_find_attr_as_uint(ldb_msg, + SYSDB_FAILED_LOGIN_ATTEMPTS, 0); + last_failed_login = (time_t) ldb_msg_find_attr_as_int64(ldb_msg, + SYSDB_LAST_FAILED_LOGIN, 0); + ret = confdb_get_int(cdb, mem_ctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_FAILED_LOGIN_ATTEMPTS, + CONFDB_DEFAULT_PAM_FAILED_LOGIN_ATTEMPTS, + &allowed_failed_login_attempts); + if (ret != EOK) { + DEBUG(1, ("Failed to read the number of allowed failed login " + "attempts.\n")); + return EIO; + } + ret = confdb_get_int(cdb, mem_ctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_FAILED_LOGIN_DELAY, + CONFDB_DEFAULT_PAM_FAILED_LOGIN_DELAY, + &failed_login_delay); + if (ret != EOK) { + DEBUG(1, ("Failed to read the failed login delay.\n")); + return EIO; + } + DEBUG(9, ("Failed login attempts [%d], allowed failed login attempts [%d], " + "failed login delay [%d].\n", *failed_login_attempts, + allowed_failed_login_attempts, failed_login_delay)); + + if (allowed_failed_login_attempts) { + if (*failed_login_attempts >= allowed_failed_login_attempts) { + if (failed_login_delay && + last_failed_login + (failed_login_delay * 60) < time(NULL)) { + DEBUG(7, ("failed_login_delay has passed, " + "resetting failed_login_attempts.\n")); + *failed_login_attempts = 0; + } else { + DEBUG(4, ("Too many failed logins.\n")); + return EACCES; + } + } + } + + return EOK; +} + 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 +4746,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 +4763,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); @@ -4726,6 +4789,7 @@ static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq) int ret; uint64_t lastLogin = 0; int cred_expiration; + uint32_t failed_login_attempts = 0; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); @@ -4758,12 +4822,18 @@ 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 ...) */ + ret = check_failed_login_attempts(state, state->cdb, ldb_msg, + &failed_login_attempts); + if (ret != EOK) { + goto done; + } + + /* TODO: verify user account (disabled, expired ...) */ password = talloc_strndup(state, (const char *) state->authtok, state->authtok_size); @@ -4787,15 +4857,67 @@ 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 +4929,107 @@ 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 request 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..531d0f5 100644 --- a/server/man/sssd.conf.5.xml +++ b/server/man/sssd.conf.5.xml @@ -338,13 +338,47 @@ 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) + + + + + + offline_failed_login_delay (integer) + + + The time in minutes which has to pass after + offline_failed_login_attempts has been reached + before a new login attempt is possible. + + + If set to 0 the user cannot authenticate offline if + offline_failed_login_attempts has been reached. Only + a successful online authentication can enable + enable offline authentication again. + + + Default: 5 + + + 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")); diff --git a/server/tests/auth-tests.c b/server/tests/auth-tests.c new file mode 100644 index 0000000..0cb6453 --- /dev/null +++ b/server/tests/auth-tests.c @@ -0,0 +1,332 @@ +/* + SSSD + + Test for local authentication utilities + + Authors: + Sumit Bose + + Copyright (C) 2010 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "util/util.h" +#include "confdb/confdb.h" +#include "db/sysdb.h" + +#define TESTS_PATH "tests_auth" +#define TEST_CONF_FILE "tests_conf.ldb" + +struct sysdb_test_ctx { + struct sysdb_ctx *sysdb; + struct confdb_ctx *confdb; + struct tevent_context *ev; + struct sss_domain_info *domain; +}; + +static int setup_sysdb_tests(struct sysdb_test_ctx **ctx) +{ + struct sysdb_test_ctx *test_ctx; + char *conf_db; + int ret; + + const char *val[2]; + val[1] = NULL; + + /* Create tests directory if it doesn't exist */ + /* (relative to current dir) */ + ret = mkdir(TESTS_PATH, 0775); + if (ret == -1 && errno != EEXIST) { + fail("Could not create %s directory", TESTS_PATH); + return EFAULT; + } + + test_ctx = talloc_zero(NULL, struct sysdb_test_ctx); + if (test_ctx == NULL) { + fail("Could not allocate memory for test context"); + return ENOMEM; + } + + /* Create an event context + * It will not be used except in confdb_init and sysdb_init + */ + test_ctx->ev = tevent_context_init(test_ctx); + if (test_ctx->ev == NULL) { + fail("Could not create event context"); + talloc_free(test_ctx); + return EIO; + } + + conf_db = talloc_asprintf(test_ctx, "%s/%s", TESTS_PATH, TEST_CONF_FILE); + if (conf_db == NULL) { + fail("Out of memory, aborting!"); + talloc_free(test_ctx); + return ENOMEM; + } + DEBUG(3, ("CONFDB: %s\n", conf_db)); + + /* Connect to the conf db */ + ret = confdb_init(test_ctx, &test_ctx->confdb, conf_db); + if (ret != EOK) { + fail("Could not initialize connection to the confdb"); + talloc_free(test_ctx); + return ret; + } + + val[0] = "LOCAL"; + ret = confdb_add_param(test_ctx->confdb, true, + "config/sssd", "domains", val); + if (ret != EOK) { + fail("Could not initialize domains placeholder"); + talloc_free(test_ctx); + return ret; + } + + val[0] = "local"; + ret = confdb_add_param(test_ctx->confdb, true, + "config/domain/LOCAL", "id_provider", val); + if (ret != EOK) { + fail("Could not initialize provider"); + talloc_free(test_ctx); + return ret; + } + + val[0] = "TRUE"; + ret = confdb_add_param(test_ctx->confdb, true, + "config/domain/LOCAL", "enumerate", val); + if (ret != EOK) { + fail("Could not initialize LOCAL domain"); + talloc_free(test_ctx); + return ret; + } + + val[0] = "TRUE"; + ret = confdb_add_param(test_ctx->confdb, true, + "config/domain/LOCAL", "cache_credentials", val); + if (ret != EOK) { + fail("Could not initialize LOCAL domain"); + talloc_free(test_ctx); + return ret; + } + + ret = confdb_get_domain(test_ctx->confdb, "local", &test_ctx->domain); + if (ret != EOK) { + fail("Could not retrieve LOCAL domain"); + talloc_free(test_ctx); + return ret; + } + + ret = sysdb_domain_init(test_ctx, test_ctx->ev, + test_ctx->domain, TESTS_PATH, &test_ctx->sysdb); + if (ret != EOK) { + fail("Could not initialize connection to the sysdb (%d)", ret); + talloc_free(test_ctx); + return ret; + } + + *ctx = test_ctx; + return EOK; +} + +static void do_failed_login_test(uint32_t failed_login_attempts, + time_t last_failed_login, + int offline_failed_login_attempts, + int offline_failed_login_delay, + int expected_result, + int expected_counter) +{ + struct sysdb_test_ctx *test_ctx; + int ret; + const char *val[2]; + val[1] = NULL; + struct ldb_message *ldb_msg; + uint32_t returned_failed_login_attempts; + + /* Setup */ + ret = setup_sysdb_tests(&test_ctx); + fail_unless(ret == EOK, "Could not set up the test"); + + val[0] = talloc_asprintf(test_ctx, "%u", offline_failed_login_attempts); + fail_unless(val[0] != NULL, "talloc_sprintf failed"); + ret = confdb_add_param(test_ctx->confdb, true, + "config/pam", CONFDB_PAM_FAILED_LOGIN_ATTEMPTS, val); + fail_unless(ret == EOK, "Could not set offline_failed_login_attempts"); + + val[0] = talloc_asprintf(test_ctx, "%u", offline_failed_login_delay); + ret = confdb_add_param(test_ctx->confdb, true, + "config/pam", CONFDB_PAM_FAILED_LOGIN_DELAY, val); + fail_unless(ret == EOK, "Could not set offline_failed_login_delay"); + + ldb_msg = ldb_msg_new(test_ctx); + fail_unless(ldb_msg != NULL, "ldb_msg_new failed"); + + ret = ldb_msg_add_fmt(ldb_msg, SYSDB_FAILED_LOGIN_ATTEMPTS, "%u", + failed_login_attempts); + fail_unless(ret == EOK, "ldb_msg_add_string failed"); + + ret = ldb_msg_add_fmt(ldb_msg, SYSDB_LAST_FAILED_LOGIN, "%lld", + (long long) last_failed_login); + fail_unless(ret == EOK, "ldb_msg_add_string failed"); + + ret = check_failed_login_attempts(test_ctx, test_ctx->confdb, ldb_msg, + &returned_failed_login_attempts); + fail_unless(ret == expected_result, + "check_failed_login_attempts returned wrong error code, " + "excected [%d], got [%d]", expected_result, ret); + fail_unless(returned_failed_login_attempts == expected_counter, + "check_failed_login_attempts returned wrong number of failed " + "login attempts, excected [%d], got [%d]", + expected_counter, failed_login_attempts); + + talloc_free(test_ctx); +} + +START_TEST(test_failed_login_attempts) +{ + + /* if offline_failed_login_attempts == 0 a login is never denied */ + do_failed_login_test(0, 0, 0, 5, EOK, 0); + do_failed_login_test(0, time(NULL), 0, 5, EOK, 0); + do_failed_login_test(2, 0, 0, 5, EOK, 2); + do_failed_login_test(2, time(NULL), 0, 5, EOK, 2); + + do_failed_login_test(0, 0, 0, 0, EOK, 0); + do_failed_login_test(0, time(NULL), 0, 0, EOK, 0); + do_failed_login_test(2, 0, 0, 0, EOK, 2); + do_failed_login_test(2, time(NULL), 0, 0, EOK, 2); + + /* if offline_failed_login_attempts != 0 and + * offline_failed_login_delay == 0 a login is denied if the number of + * failed attempts >= offline_failed_login_attempts */ + do_failed_login_test(0, 0, 2, 0, EOK, 0); + do_failed_login_test(0, time(NULL), 2, 0, EOK, 0); + do_failed_login_test(2, 0, 2, 0, EACCES, 2); + do_failed_login_test(2, time(NULL), 2, 0, EACCES, 2); + + /* if offline_failed_login_attempts != 0 and + * offline_failed_login_delay != 0 a login is denied only if the number of + * failed attempts >= offline_failed_login_attempts AND the last failed + * login attempt is not longer than offline_failed_login_delay ago */ + do_failed_login_test(0, 0, 2, 5, EOK, 0); + do_failed_login_test(0, time(NULL), 2, 5, EOK, 0); + do_failed_login_test(2, 0, 2, 5, EOK, 0); + do_failed_login_test(2, time(NULL), 2, 5, EACCES, 2); + +} +END_TEST + +Suite *auth_suite (void) +{ + Suite *s = suite_create ("auth"); + + TCase *tc_auth = tcase_create ("auth"); + + tcase_add_test (tc_auth, test_failed_login_attempts); + tcase_set_timeout(tc_auth, 60); + + suite_add_tcase (s, tc_auth); + + return s; +} + +static int clean_db_dir(void) +{ + int ret; + + ret = unlink(TESTS_PATH"/"TEST_CONF_FILE); + if (ret != EOK && errno != ENOENT) { + fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n", + errno, strerror(errno)); + return ret; + } + + ret = unlink(TESTS_PATH"/"LOCAL_SYSDB_FILE); + if (ret != EOK && errno != ENOENT) { + fprintf(stderr, "Could not delete the test config ldb file (%d) (%s)\n", + errno, strerror(errno)); + return ret; + } + + ret = rmdir(TESTS_PATH); + if (ret != EOK && errno != ENOENT) { + fprintf(stderr, "Could not delete the test directory (%d) (%s)\n", + errno, strerror(errno)); + return ret; + } + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int ret; + int opt; + int failure_count; + poptContext pc; + Suite *s = auth_suite (); + SRunner *sr = srunner_create (s); + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + { NULL } + }; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + ret = clean_db_dir(); + if (ret != EOK) { + fprintf(stderr, "Could not delete the db directory (%d) (%s)\n", + errno, strerror(errno)); + return EXIT_FAILURE; + } + + srunner_run_all(sr, CK_ENV); + failure_count = srunner_ntests_failed (sr); + srunner_free (sr); + if (failure_count == 0) { + ret = clean_db_dir(); + if (ret != EOK) { + fprintf(stderr, "Could not delete the db directory (%d) (%s)\n", + errno, strerror(errno)); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} -- 1.6.6