summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSumit Bose <sbose@redhat.com>2010-01-25 13:45:16 +0100
committerStephen Gallagher <sgallagh@redhat.com>2010-02-02 14:41:41 -0500
commit7ea48433b3bab77813b11c60e1ea82cb9793dc33 (patch)
treef7012746fd8f727cd80bd2361cb239893004f2b5 /server
parent245818557767e511d46b566bd1352f4d8fcb3426 (diff)
downloadsssd-7ea48433b3bab77813b11c60e1ea82cb9793dc33.tar.gz
sssd-7ea48433b3bab77813b11c60e1ea82cb9793dc33.tar.xz
sssd-7ea48433b3bab77813b11c60e1ea82cb9793dc33.zip
Add offline failed login counter
Diffstat (limited to 'server')
-rw-r--r--server/Makefile.am13
-rw-r--r--server/confdb/confdb.h4
-rw-r--r--server/config/SSSDConfig.py2
-rw-r--r--server/config/etc/sssd.api.conf2
-rw-r--r--server/db/sysdb.h6
-rw-r--r--server/db/sysdb_ops.c240
-rw-r--r--server/man/sssd.conf.5.xml36
-rw-r--r--server/responder/pam/pam_LOCAL_domain.c13
-rw-r--r--server/tests/auth-tests.c332
9 files changed, 631 insertions, 17 deletions
diff --git a/server/Makefile.am b/server/Makefile.am
index 7ba7ffa92..3c02f65a8 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 7f6c63b04..874f60890 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 b08e9f4f2..b751e4db8 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 bdb6aab24..5fc8e781f 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 4c2554927..9b77edfa3 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 <tevent.h>
@@ -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 469ed8d4d..c1d996d52 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_time_t 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_uint32 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_time_t 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_uint32 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 c9c556849..531d0f5d5 100644
--- a/server/man/sssd.conf.5.xml
+++ b/server/man/sssd.conf.5.xml
@@ -338,13 +338,47 @@
<listitem>
<para>
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).
</para>
<para>
Default: 0 (No limit)
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>offline_failed_login_attempts (integer)</term>
+ <listitem>
+ <para>
+ If the authentication provider is offline, how
+ many failed login attempts are allowed.
+ </para>
+ <para>
+ Default: 0 (No limit)
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>offline_failed_login_delay (integer)</term>
+ <listitem>
+ <para>
+ The time in minutes which has to pass after
+ offline_failed_login_attempts has been reached
+ before a new login attempt is possible.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>
+ Default: 5
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect2>
</refsect1>
diff --git a/server/responder/pam/pam_LOCAL_domain.c b/server/responder/pam/pam_LOCAL_domain.c
index 9d3738c53..1a5d76b67 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 000000000..0cb64533c
--- /dev/null
+++ b/server/tests/auth-tests.c
@@ -0,0 +1,332 @@
+/*
+ SSSD
+
+ Test for local authentication utilities
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <string.h>
+#include <popt.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include <check.h>
+
+#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;
+}