Update kerberos password policy values on LDAP binds. This is so locked-out accounts in kerberos don't try things using LDAP instead.

On a failed bind this will update krbLoginFailedCount and krbLastFailedAuth and will potentially fail the bind altogether.


On a successful bind it will zero krbLoginFailedCount and set
krbLastSuccessfulAuth.

This will also enforce locked-out accounts.

See http://k5wiki.kerberos.org/wiki/Projects/Lockout for details on kerberos lockout.

ticket 343
>From 7c9aabdf43715550fc39da508a2f6f9a327b15a6 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Mon, 17 Jan 2011 10:47:00 -0500
Subject: [PATCH] Update kerberos password policy values on LDAP binds.

On a failed bind this will update krbLoginFailedCount and krbLastFailedAuth
and will potentially fail the bind altogether.

On a successful bind it will zero krbLoginFailedCount and set
krbLastSuccessfulAuth.

This will also enforce locked-out accounts.

See http://k5wiki.kerberos.org/wiki/Projects/Lockout for details on
kerberos lockout.

ticket 343
---
 daemons/configure.ac                               |    1 +
 daemons/ipa-slapi-plugins/Makefile.am              |    1 +
 daemons/ipa-slapi-plugins/ipa-lockout/Makefile.am  |   46 ++
 .../ipa-slapi-plugins/ipa-lockout/ipa_lockout.c    |  618 ++++++++++++++++++++
 .../ipa-lockout/lockout-conf.ldif                  |   15 +
 ipa.spec.in                                        |    2 +
 ipaserver/install/dsinstance.py                    |    4 +
 7 files changed, 687 insertions(+), 0 deletions(-)
 create mode 100644 daemons/ipa-slapi-plugins/ipa-lockout/Makefile.am
 create mode 100644 daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
 create mode 100644 daemons/ipa-slapi-plugins/ipa-lockout/lockout-conf.ldif

diff --git a/daemons/configure.ac b/daemons/configure.ac
index 72ff750..ef6e97d 100644
--- a/daemons/configure.ac
+++ b/daemons/configure.ac
@@ -296,6 +296,7 @@ AC_CONFIG_FILES([
     ipa-kpasswd/Makefile
     ipa-slapi-plugins/Makefile
     ipa-slapi-plugins/ipa-enrollment/Makefile
+    ipa-slapi-plugins/ipa-lockout/Makefile
     ipa-slapi-plugins/ipa-pwd-extop/Makefile
     ipa-slapi-plugins/ipa-winsync/Makefile
     ipa-slapi-plugins/ipa-version/Makefile
diff --git a/daemons/ipa-slapi-plugins/Makefile.am b/daemons/ipa-slapi-plugins/Makefile.am
index 1ae2351..25f50d5 100644
--- a/daemons/ipa-slapi-plugins/Makefile.am
+++ b/daemons/ipa-slapi-plugins/Makefile.am
@@ -2,6 +2,7 @@ NULL =
 
 SUBDIRS =			\
 	ipa-enrollment		\
+	ipa-lockout		\
 	ipa-modrdn		\
 	ipa-pwd-extop		\
 	ipa-uuid		\
diff --git a/daemons/ipa-slapi-plugins/ipa-lockout/Makefile.am b/daemons/ipa-slapi-plugins/ipa-lockout/Makefile.am
new file mode 100644
index 0000000..fea3fe6
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-lockout/Makefile.am
@@ -0,0 +1,46 @@
+NULL =
+
+PLUGIN_COMMON_DIR=../common
+
+INCLUDES =							\
+	-I.							\
+	-I$(srcdir)						\
+	-I$(PLUGIN_COMMON_DIR)					\
+	-I/usr/include/dirsrv					\
+	-DPREFIX=\""$(prefix)"\" 				\
+	-DBINDIR=\""$(bindir)"\"				\
+	-DLIBDIR=\""$(libdir)"\" 				\
+	-DLIBEXECDIR=\""$(libexecdir)"\"			\
+	-DDATADIR=\""$(datadir)"\"				\
+	$(AM_CFLAGS)						\
+	$(LDAP_CFLAGS)					\
+	$(WARN_CFLAGS)						\
+	$(NULL)
+
+plugindir = $(libdir)/dirsrv/plugins
+plugin_LTLIBRARIES = 		\
+	libipa_lockout.la		\
+	$(NULL)
+
+libipa_lockout_la_SOURCES = 	\
+	ipa_lockout.c		\
+	$(NULL)
+
+libipa_lockout_la_LDFLAGS = -avoid-version
+
+libipa_lockout_la_LIBADD = 	\
+	$(LDAP_LIBS)		\
+	$(NULL)
+
+appdir = $(IPA_DATA_DIR)
+app_DATA =			\
+	lockout-conf.ldif		\
+	$(NULL)
+
+EXTRA_DIST =			\
+	$(app_DATA)		\
+	$(NULL)
+
+MAINTAINERCLEANFILES =		\
+	*~			\
+	Makefile.in
diff --git a/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c b/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
new file mode 100644
index 0000000..674099d
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
@@ -0,0 +1,618 @@
+/** BEGIN COPYRIGHT BLOCK
+ * 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/>.
+ *
+ * Additional permission under GPLv3 section 7:
+ *
+ * In the following paragraph, "GPL" means the GNU General Public
+ * License, version 3 or any later version, and "Non-GPL Code" means
+ * code that is governed neither by the the GPL nor a license
+ * compatible with the GPL.
+ *
+ * You may link the code of this Program with Non-GPL Code and convey
+ * linked combinations including the two, provided that such Non-GPL
+ * Code only links to the code of this Program through those well
+ * defined interfaces identified in the file named EXCEPTION found in
+ * the source code files (the "Approved Interfaces"). The files of
+ * Non-GPL Code may instantiate templates or use macros or inline
+ * functions from the Approved Interfaces without causing the resulting
+ * work to be covered by the GPL. Only the copyright holders of this
+ * Program may make changes or additions to the list of Approved
+ * Interfaces.
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+/**
+ * IPA Lockout plug-in
+ *
+ * Update the Kerberos lockout variables on LDAP binds.
+ *
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <time.h>
+#include "slapi-plugin.h"
+#include "nspr.h"
+
+#include "util.h"
+
+#define IPALOCKOUT_PLUGIN_NAME "ipa-lockout-plugin"
+#define IPALOCKOUT_PLUGIN_VERSION 0x00010000
+
+#define IPA_PLUGIN_NAME IPALOCKOUT_PLUGIN_NAME
+
+#define IPALOCKOUT_FEATURE_DESC      "IPA Lockout"
+#define IPALOCKOUT_PLUGIN_DESC       "IPA Lockout plugin"
+#define IPALOCKOUT_POSTOP_DESC       "IPA Lockout postop plugin"
+#define IPALOCKOUT_PREOP_DESC        "IPA Lockout preop plugin"
+
+static Slapi_PluginDesc pdesc = {
+    IPALOCKOUT_FEATURE_DESC,
+    "Red Hat, Inc.",
+    "1.0",
+    IPALOCKOUT_PLUGIN_DESC
+};
+
+static void *_PluginID = NULL;
+static char *_PluginDN = NULL;
+
+static int g_plugin_started = 0;
+
+#define GENERALIZED_TIME_LENGTH 15
+
+/**
+ *
+ * management functions
+ *
+ */
+int ipalockout_init(Slapi_PBlock * pb);
+static int ipalockout_start(Slapi_PBlock * pb);
+static int ipalockout_close(Slapi_PBlock * pb);
+static int ipalockout_postop_init(Slapi_PBlock * pb);
+static int ipalockout_preop_init(Slapi_PBlock * pb);
+
+/**
+ *
+ * the ops (where the real work is done)
+ *
+ */
+static int ipalockout_postop(Slapi_PBlock *pb);
+static int ipalockout_preop(Slapi_PBlock *pb);
+
+/**
+ *
+ * Get the plug-in version
+ *
+ */
+int ipalockout_version(void)
+{
+    return IPALOCKOUT_PLUGIN_VERSION;
+}
+
+/**
+ * Plugin identity mgmt
+ */
+void setPluginID(void *pluginID)
+{
+    _PluginID = pluginID;
+}
+
+void *getPluginID(void)
+{
+    return _PluginID;
+}
+
+void setPluginDN(char *pluginDN)
+{
+    _PluginDN = pluginDN;
+}
+
+char *getPluginDN(void)
+{
+    return _PluginDN;
+}
+
+int
+ipalockout_init(Slapi_PBlock *pb)
+{
+    int status = EOK;
+    char *plugin_identity = NULL;
+
+    LOG_TRACE("--in-->\n");
+
+    slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
+    PR_ASSERT(plugin_identity);
+    setPluginID(plugin_identity);
+
+    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+                         SLAPI_PLUGIN_VERSION_01) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+                         (void *) ipalockout_start) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+                         (void *) ipalockout_close) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+                         (void *) &pdesc) != 0 ||
+        slapi_register_plugin("postoperation",
+                              1,
+                              "ipalockout_init",
+                              ipalockout_postop_init,
+                              IPALOCKOUT_POSTOP_DESC,
+                              NULL,
+                              plugin_identity
+        ) ||
+        slapi_register_plugin("preoperation",
+                              1,
+                              "ipalockout_init",
+                              ipalockout_preop_init,
+                              IPALOCKOUT_PREOP_DESC,
+                              NULL,
+                              plugin_identity
+        )
+        ) {
+        LOG_FATAL("failed to register plugin\n");
+        status = EFAIL;
+    }
+
+    LOG_TRACE("<--out--\n");
+    return status;
+}
+
+static int
+ipalockout_postop_init(Slapi_PBlock *pb)
+{
+    int status = EOK;
+
+    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+                         SLAPI_PLUGIN_VERSION_01) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+                         (void *) &pdesc) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_POST_BIND_FN,
+                         (void *) ipalockout_postop) != 0) {
+        status = EFAIL;
+    }
+ 
+    return status;
+}
+
+static int
+ipalockout_preop_init(Slapi_PBlock *pb)
+{
+    int status = EOK;
+
+    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
+                         SLAPI_PLUGIN_VERSION_01) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
+                         (void *) &pdesc) != 0 ||
+        slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN,
+                         (void *) ipalockout_preop) != 0) {
+        status = EFAIL;
+    }
+ 
+    return status;
+}
+
+static int
+ipalockout_start(Slapi_PBlock * pb)
+{
+    LOG_TRACE("--in-->\n");
+
+    /* Check if we're already started */
+    if (g_plugin_started) {
+        goto done;
+    }
+
+    g_plugin_started = 1;
+    LOG("ready for service\n");
+    LOG_TRACE("<--out--\n");
+
+done:
+    return EOK;
+}
+
+static int
+ipalockout_close(Slapi_PBlock * pb)
+{
+    LOG_TRACE( "--in-->\n");
+
+    LOG_TRACE("<--out--\n");
+
+    return EOK;
+}
+
+/*
+ * In the post-operation we know whether the bind was successful or not
+ * so here we handle updating the Kerberos lockout policy attributes.
+ */
+static int ipalockout_postop(Slapi_PBlock *pb)
+{
+    char *dn = NULL;
+    char *policy_dn = NULL;
+    Slapi_Entry *target_entry = NULL;
+    Slapi_Entry *policy_entry = NULL;
+    Slapi_DN *sdn = NULL;
+    Slapi_DN *pdn = NULL;
+    Slapi_Value *objectclass = NULL;
+    Slapi_PBlock *pbtm = NULL;
+    Slapi_Mods *smods = NULL;
+    char *errstr = NULL;
+    int ldrc, rc = 0;
+    int ret = LDAP_SUCCESS;
+    unsigned long failedcount = 0;
+    char failedcountstr[32];
+    int failed_bind = 0;
+    struct tm utctime;
+    time_t time_now;
+    char timestr[GENERALIZED_TIME_LENGTH+1];
+    unsigned int failcnt_interval = 0;
+    char *lastfail = NULL;
+
+    LOG_TRACE("--in-->\n");
+
+    /* Just bail if we aren't ready to service requests yet. */
+    if (!g_plugin_started) {
+        goto done;
+    }
+
+    slapi_pblock_get(pb, SLAPI_RESULT_CODE, &rc);
+
+    /* free the dn here */
+    if (slapi_pblock_get(pb, SLAPI_CONN_DN, &dn) != 0) {
+        LOG_FATAL("Error retrieving bind DN\n");
+        ret = LDAP_OPERATIONS_ERROR;
+        goto done;
+    }
+
+    /* dn will be NULL on failed auth, get the target instead */
+    /* don't free this dn */
+    if (dn == NULL && rc != LDAP_SUCCESS) {
+        failed_bind = 1;
+        if (slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn) != 0) {
+            LOG_FATAL("Error retrieving target DN\n");
+            ret = LDAP_OPERATIONS_ERROR;
+            goto done;
+        }
+    }
+
+    /* Client is anonymously bound */
+    if (dn == NULL) {
+        LOG_TRACE("anonymous bind\n");
+        goto done;
+    }
+
+    /* Get the entry */
+    sdn = slapi_sdn_new_dn_byref(dn);
+    if (sdn == NULL) {
+        LOG_OOM();
+        errstr = "Out of memory.\n";
+        ret = LDAP_OPERATIONS_ERROR;
+        goto done;
+    }
+
+    ldrc = slapi_search_internal_get_entry(sdn, NULL, &target_entry,
+            getPluginID());
+
+    if (ldrc != LDAP_SUCCESS) {
+            LOG_FATAL("Failed to retrieve entry \"%s\": %d\n", dn, ldrc);
+            errstr = "Failed to retrieve entry.";
+            ret = LDAP_OPERATIONS_ERROR;
+            goto done;
+    } 
+
+    /* Only update kerberos principal entries */
+    objectclass = slapi_value_new_string("krbPrincipalAux");
+    if ((slapi_entry_attr_has_syntax_value(target_entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) != 1) {
+        LOG_TRACE("Not a kerberos user\n");
+        slapi_value_free(&objectclass);
+        goto done;
+    }
+    slapi_value_free(&objectclass);
+
+    /* Only update if there is a password policy */
+    policy_dn = slapi_entry_attr_get_charptr(target_entry, "krbPwdPolicyReference");
+    if (policy_dn == NULL) {
+        LOG_TRACE("No kerberos password policy\n");
+        goto done;
+    } else {
+        pdn = slapi_sdn_new_dn_byref(policy_dn);
+        ldrc = slapi_search_internal_get_entry(pdn, NULL, &policy_entry,
+                getPluginID());
+        slapi_sdn_free(&pdn);
+        if (ldrc != LDAP_SUCCESS) {
+            LOG_FATAL("Failed to retrieve entry \"%s\": %d\n", policy_dn, ldrc);
+            errstr = "Failed to retrieve entry.";
+            ret = LDAP_OPERATIONS_ERROR;
+            goto done;
+        } 
+    }
+
+    failedcount = slapi_entry_attr_get_ulong(target_entry, "krbLoginFailedCount");
+    failcnt_interval = slapi_entry_attr_get_uint(policy_entry, "krbPwdFailureCountInterval");
+    lastfail = slapi_entry_attr_get_charptr(target_entry, "krbLastFailedAuth");
+    time_now = time(NULL);
+    if (lastfail != NULL) {
+        struct tm tm;
+        int ret = 0;
+
+        memset(&tm, 0, sizeof(struct tm));
+        ret = sscanf(lastfail,
+                     "%04u%02u%02u%02u%02u%02u",
+                     &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+                     &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+        if (ret == 6) {
+            tm.tm_year -= 1900;
+            tm.tm_mon -= 1;
+
+            if (time_now > timegm(&tm) + failcnt_interval) {
+                failedcount = 0;
+            }
+        }
+        slapi_ch_free_string(&lastfail);
+    }
+
+    smods = slapi_mods_new();
+
+    if (failed_bind) {
+        failedcount += 1;
+        PR_snprintf(failedcountstr, sizeof(failedcountstr), "%lu", failedcount);
+        if (!gmtime_r(&(time_now), &utctime)) {
+            errstr = "failed to parse current date (buggy gmtime_r ?)\n";
+            LOG_FATAL("%s", errstr);
+            ret = LDAP_OPERATIONS_ERROR;
+            goto done;
+        }
+        strftime(timestr, GENERALIZED_TIME_LENGTH+1,
+                 "%Y%m%d%H%M%SZ", &utctime);
+        slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLoginFailedCount", failedcountstr);
+        slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLastFailedAuth", timestr);
+    } else {
+        PR_snprintf(failedcountstr, sizeof(failedcountstr), "%lu", 0L);
+        time_now = time(NULL);
+        if (!gmtime_r(&(time_now), &utctime)) {
+            errstr = "failed to parse current date (buggy gmtime_r ?)\n";
+            LOG_FATAL("%s", errstr);
+            ret = LDAP_OPERATIONS_ERROR;
+            goto done;
+        }
+        strftime(timestr, GENERALIZED_TIME_LENGTH+1,
+                 "%Y%m%d%H%M%SZ", &utctime);
+        slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLoginFailedCount", failedcountstr);
+        slapi_mods_add_string(smods, LDAP_MOD_REPLACE, "krbLastSuccessfulAuth", timestr);
+    }
+
+    pbtm = slapi_pblock_new();
+    slapi_modify_internal_set_pb (pbtm, slapi_entry_get_dn_const(target_entry),
+        slapi_mods_get_ldapmods_byref(smods),
+        NULL, /* Controls */
+        NULL, /* UniqueID */
+        getPluginID(), /* PluginID */
+        0); /* Flags */
+
+    rc = slapi_modify_internal_pb (pbtm);
+    if (rc) {
+        LOG_TRACE("WARNING: modify error %d on entry '%s'\n",
+                  rc, slapi_entry_get_dn_const(target_entry));
+    } else {
+        slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+
+        if (rc != LDAP_SUCCESS){
+            LOG_TRACE("WARNING: modify error %d on entry '%s'\n",
+                      rc, slapi_entry_get_dn_const(target_entry));
+        } else {
+            LOG_TRACE("<= apply mods: Successful\n");
+        }
+    }
+
+done:
+    if (!failed_bind && dn != NULL) slapi_ch_free_string(&dn);
+    slapi_entry_free(target_entry);
+    if (policy_dn) {
+        slapi_ch_free_string(&policy_dn);
+        slapi_entry_free(policy_entry);
+    }
+    if (sdn) slapi_sdn_free(&sdn);
+    if (smods) slapi_mods_free(&smods);
+
+    if (pbtm) {
+        slapi_pblock_destroy(pbtm);
+    }
+
+    LOG("postop returning %d: %s\n", ret, errstr ? errstr : "success\n");
+
+    if (ret) {
+        slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL);
+    }
+
+    LOG_TRACE("<--out--\n");
+
+    return (ret == 0 ? EOK : 1);
+}
+
+/*
+ * In the pre-op stage the bind hasn't occurred yet. It is here that
+ * we do the lockout enforcement.
+ */
+static int ipalockout_preop(Slapi_PBlock *pb)
+{
+    char *dn = NULL;
+    char *policy_dn = NULL;
+    Slapi_Entry *target_entry = NULL;
+    Slapi_Entry *policy_entry = NULL;
+    Slapi_DN *sdn = NULL;
+    Slapi_DN *pdn = NULL;
+    Slapi_Value *objectclass = NULL;
+    char *errstr = NULL;
+    int ldrc = 0;
+    int ret = LDAP_SUCCESS;
+    unsigned long failedcount = 0;
+    time_t time_now;
+    unsigned int failcnt_interval = 0;
+    unsigned int max_fail = 0;
+    unsigned int lockout_duration = 0;
+    time_t last_failed = 0;
+    char *lastfail = NULL;
+    char *unlock_time = NULL;
+
+    LOG_TRACE("--in-->\n");
+
+    /* Just bail if we aren't ready to service requests yet. */
+    if (!g_plugin_started) {
+        goto done;
+    }
+
+    if (slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn) != 0) {
+        LOG_FATAL("Error retrieving target DN\n");
+        ret = LDAP_OPERATIONS_ERROR;
+        goto done;
+    }
+
+    /* Client is anonymously bound */
+    if (dn == NULL) {
+        LOG_TRACE("anonymous bind\n");
+        goto done;
+    }
+
+    /* Get the entry */
+    sdn = slapi_sdn_new_dn_byref(dn);
+    if (sdn == NULL) {
+        LOG_OOM();
+        errstr = "Out of memory.\n";
+        ret = LDAP_OPERATIONS_ERROR;
+        goto done;
+    }
+
+    ldrc = slapi_search_internal_get_entry(sdn, NULL, &target_entry,
+            getPluginID());
+
+    if (ldrc != LDAP_SUCCESS) {
+            LOG_FATAL("Failed to retrieve entry \"%s\": %d\n", dn, ldrc);
+            errstr = "Failed to retrieve entry.";
+            ret = LDAP_OPERATIONS_ERROR;
+            goto done;
+    } 
+
+    /* Only handle kerberos principal entries */
+    objectclass = slapi_value_new_string("krbPrincipalAux");
+    if ((slapi_entry_attr_has_syntax_value(target_entry, SLAPI_ATTR_OBJECTCLASS, objectclass)) != 1) {
+        LOG_TRACE("Not a kerberos user\n");
+        slapi_value_free(&objectclass);
+        goto done;
+    }
+    slapi_value_free(&objectclass);
+
+    /* Only continue if there is a password policy */
+    policy_dn = slapi_entry_attr_get_charptr(target_entry, "krbPwdPolicyReference");
+    if (policy_dn == NULL) {
+        LOG_TRACE("No kerberos password policy\n");
+        goto done;
+    } else {
+        pdn = slapi_sdn_new_dn_byref(policy_dn);
+        ldrc = slapi_search_internal_get_entry(pdn, NULL, &policy_entry,
+                getPluginID());
+        slapi_sdn_free(&pdn);
+        if (ldrc != LDAP_SUCCESS) {
+            LOG_FATAL("Failed to retrieve entry \"%s\": %d\n", policy_dn, ldrc);
+            errstr = "Failed to retrieve entry.";
+            ret = LDAP_OPERATIONS_ERROR;
+            goto done;
+        } 
+    }
+
+    failedcount = slapi_entry_attr_get_ulong(target_entry, "krbLoginFailedCount");
+    time_now = time(NULL);
+    failcnt_interval = slapi_entry_attr_get_uint(policy_entry, "krbPwdFailureCountInterval");
+    lastfail = slapi_entry_attr_get_charptr(target_entry, "krbLastFailedAuth");
+    unlock_time = slapi_entry_attr_get_charptr(target_entry, "krbLastAdminUnlock");
+    if (lastfail != NULL) {
+        struct tm tm;
+        int ret = 0;
+
+        memset(&tm, 0, sizeof(struct tm));
+        ret = sscanf(lastfail,
+                     "%04u%02u%02u%02u%02u%02u",
+                     &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+                     &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+        if (ret == 6) {
+            tm.tm_year -= 1900;
+            tm.tm_mon -= 1;
+
+            last_failed = timegm(&tm);
+            LOG("%d > %d ?\n", time_now, last_failed + failcnt_interval);
+            LOG("diff %d\n", (last_failed + failcnt_interval) - time_now);
+            if (time_now > last_failed + failcnt_interval) {
+                failedcount = 0;
+            }
+        }
+        if (unlock_time) {
+            time_t unlock;
+
+            memset(&tm, 0, sizeof(struct tm));
+            ret = sscanf(lastfail,
+                         "%04u%02u%02u%02u%02u%02u",
+                         &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+                         &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+
+            if (ret == 6) {
+                tm.tm_year -= 1900;
+                tm.tm_mon -= 1;
+
+                unlock = timegm(&tm);
+                if (last_failed <= unlock) {
+                    goto done;
+                }
+            }
+            slapi_ch_free_string(&unlock_time);
+        }
+        slapi_ch_free_string(&lastfail);
+    }
+
+    max_fail = slapi_entry_attr_get_uint(policy_entry, "krbPwdMaxFailure");
+    if (max_fail == 0) {
+        goto done;
+    }
+
+    lockout_duration = slapi_entry_attr_get_uint(policy_entry, "krbPwdLockoutDuration");
+    if (lockout_duration == 0) {
+        errstr = "Entry permanently locked.\n";
+        ret = LDAP_UNWILLING_TO_PERFORM;
+        goto done;
+    }
+
+    if (failedcount > max_fail) {
+        if (time_now < last_failed + lockout_duration) {
+            /* Too many failures */
+            LOG_TRACE("Too many failed logins. %lu out of %d\n", failedcount, max_fail);
+            errstr = "Too many failed logins.\n";
+            ret = LDAP_UNWILLING_TO_PERFORM;
+        }
+    }
+
+done:
+    slapi_entry_free(target_entry);
+    slapi_entry_free(policy_entry);
+    if (policy_dn) slapi_ch_free_string(&policy_dn);
+    if (sdn) slapi_sdn_free(&sdn);
+
+    LOG("preop returning %d: %s\n", ret, errstr ? errstr : "success\n");
+
+    if (ret) {
+        slapi_send_ldap_result(pb, ret, NULL, errstr, 0, NULL);
+    }
+
+    LOG_TRACE("<--out--\n");
+
+    return (ret == 0 ? EOK : 1);
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-lockout/lockout-conf.ldif b/daemons/ipa-slapi-plugins/ipa-lockout/lockout-conf.ldif
new file mode 100644
index 0000000..8a13fc9
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-lockout/lockout-conf.ldif
@@ -0,0 +1,15 @@
+dn: cn=IPA Lockout,cn=plugins,cn=config
+changetype: add
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: IPA Lockout
+nsslapd-pluginpath: libipa_lockout
+nsslapd-plugininitfunc: ipalockout_init
+nsslapd-plugintype: object
+nsslapd-pluginenabled: on
+nsslapd-pluginid: ipalockout_version
+nsslapd-pluginversion: 1.0
+nsslapd-pluginvendor: Red Hat, Inc.
+nsslapd-plugindescription: IPA Lockout plugin
+nsslapd-plugin-depends-on-type: database
diff --git a/ipa.spec.in b/ipa.spec.in
index 6323e25..db0e90c 100644
--- a/ipa.spec.in
+++ b/ipa.spec.in
@@ -272,6 +272,7 @@ rm %{buildroot}/%{plugin_dir}/libipa_winsync.la
 rm %{buildroot}/%{plugin_dir}/libipa_repl_version.la
 rm %{buildroot}/%{plugin_dir}/libipa_uuid.la
 rm %{buildroot}/%{plugin_dir}/libipa_modrdn.la
+rm %{buildroot}/%{plugin_dir}/libipa_lockout.la
 
 # Some user-modifiable HTML files are provided. Move these to /etc
 # and link back.
@@ -428,6 +429,7 @@ fi
 %attr(755,root,root) %{plugin_dir}/libipa_repl_version.so
 %attr(755,root,root) %{plugin_dir}/libipa_uuid.so
 %attr(755,root,root) %{plugin_dir}/libipa_modrdn.so
+%attr(755,root,root) %{plugin_dir}/libipa_lockout.so
 %dir %{_localstatedir}/lib/ipa
 %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysrestore
 %dir %{_localstatedir}/cache/ipa
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 73bc8b0..8c8bee7 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -204,6 +204,7 @@ class DsInstance(service.Service):
         self.step("configuring uuid plugin", self.__config_uuid_module)
         self.step("configuring modrdn plugin", self.__config_modrdn_module)
         self.step("enabling entryUSN plugin", self.__enable_entryusn)
+        self.step("configuring lockout plugin", self.__config_lockout_module)
         self.step("creating indices", self.__create_indices)
         self.step("configuring ssl for ds instance", self.__enable_ssl)
         self.step("configuring certmap.conf", self.__certmap_conf)
@@ -462,6 +463,9 @@ class DsInstance(service.Service):
         self._ldap_mod("modrdn-conf.ldif")
         self._ldap_mod("modrdn-krbprinc.ldif", self.sub_dict)
 
+    def __config_lockout_module(self):
+        self._ldap_mod("lockout-conf.ldif")
+
     def __user_private_groups(self):
         if not has_managed_entries(self.fqdn, self.dm_password):
             raise errors.NotFound(reason='Missing Managed Entries Plugin')
-- 
1.7.3.4

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to