[SSSD] [PATCH] Add offline failed login counter

Sumit Bose sbose at redhat.com
Mon Feb 1 14:02:06 UTC 2010


On Mon, Feb 01, 2010 at 08:29:09AM -0500, Stephen Gallagher wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> Nack
> 
> On 01/29/2010 05:51 AM, Sumit Bose wrote:
> > On Thu, Jan 28, 2010 at 02:52:21PM -0500, Stephen Gallagher wrote:
> > The DEBUG messages after failure to save SYSDB_LAST_LOGIN and
> > SYSDB_FAILED_LOGIN_ATTEMPTS are wrong.
> > 
> >> fixed
> 
> No it's not:
> +        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;
> +        }
> 
> And several other similar ones.
> 

oh, sorry, I thought you've meant a different DEBUG message. I hope I've
found all of them now.

bye,
Sumit
-------------- next part --------------
From 817790bd683451db7f47d0bc9cdfbf329adf015f Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
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 <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 469ed8d..c1d996d 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 c9c5568..531d0f5 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 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 <sbose at 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;
+}
-- 
1.6.6



More information about the sssd-devel mailing list