summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2011-01-18 14:58:58 -0500
committerRob Crittenden <rcritten@redhat.com>2011-01-21 13:59:24 -0500
commitcf9ec1c4271e1f2b35f9a4377550064bad0387c3 (patch)
tree5f586544af7a8ca1f701d182d1584374b864d27b
parent4361cd02422d8a6b30d67bb6869af9c67f7ec9c0 (diff)
downloadfreeipa-cf9ec1c4271e1f2b35f9a4377550064bad0387c3.tar.gz
freeipa-cf9ec1c4271e1f2b35f9a4377550064bad0387c3.tar.xz
freeipa-cf9ec1c4271e1f2b35f9a4377550064bad0387c3.zip
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
-rw-r--r--daemons/configure.ac1
-rw-r--r--daemons/ipa-slapi-plugins/Makefile.am1
-rw-r--r--daemons/ipa-slapi-plugins/ipa-lockout/Makefile.am46
-rw-r--r--daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c643
-rw-r--r--daemons/ipa-slapi-plugins/ipa-lockout/lockout-conf.ldif15
-rw-r--r--ipa.spec.in2
-rw-r--r--ipaserver/install/dsinstance.py4
7 files changed, 712 insertions, 0 deletions
diff --git a/daemons/configure.ac b/daemons/configure.ac
index c024c12cf..d15a5c70c 100644
--- a/daemons/configure.ac
+++ b/daemons/configure.ac
@@ -291,6 +291,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 1ae2351dd..25f50d5f7 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 000000000..fea3fe67d
--- /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 000000000..0284872b8
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-lockout/ipa_lockout.c
@@ -0,0 +1,643 @@
+/** 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_PBlock *pbtm = NULL;
+ Slapi_Mods *smods = NULL;
+ Slapi_Value *objectclass = 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;
+ int tries = 0;
+ int failure = 1;
+
+ 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);
+ 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 account policy.";
+ 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;
+ }
+ }
+ }
+
+ while (tries < 5) {
+ smods = slapi_mods_new();
+
+ /* On failures try very hard to update the entry so that failures
+ * are counted properly. This involves doing a DELETE of the value
+ * we expect and an ADD of the new one in the same update. If the
+ * record has changed while we were handling the request our
+ * update will fail and we will try again.
+ *
+ * On a successful bind just do a replace and set failurecount to 0.
+ */
+ if (failed_bind) {
+ 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_DELETE, "krbLoginFailedCount", failedcountstr);
+ failedcount += 1;
+ PR_snprintf(failedcountstr, sizeof(failedcountstr), "%lu", failedcount);
+ slapi_mods_add_string(smods, LDAP_MOD_ADD, "krbLoginFailedCount", failedcountstr);
+ if (lastfail)
+ slapi_mods_add_string(smods, LDAP_MOD_DELETE, "krbLastFailedAuth", lastfail);
+ slapi_mods_add_string(smods, LDAP_MOD_ADD, "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 */
+
+ slapi_modify_internal_pb (pbtm);
+ 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));
+
+ 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);
+ goto done;
+ }
+ slapi_mods_free(&smods);
+ slapi_pblock_destroy(pbtm);
+ if (lastfail) slapi_ch_free_string(&lastfail);
+ smods = NULL;
+ pbtm = NULL;
+ lastfail = NULL;
+ tries += 1;
+ } else {
+ LOG_TRACE("<= apply mods: Successful\n");
+ failure = 0;
+ break;
+ }
+ } /* while */
+
+ if (failure) {
+ ret = LDAP_OPERATIONS_ERROR;
+ }
+
+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 (lastfail) slapi_ch_free_string(&lastfail);
+ if (pbtm) slapi_pblock_destroy(pbtm);
+ if (smods) slapi_mods_free(&smods);
+
+ 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 : EFAIL);
+}
+
+/*
+ * 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);
+ 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 account policy.";
+ 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 : EFAIL);
+}
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 000000000..8a13fc94b
--- /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 d43a029c4..7d7ce7787 100644
--- a/ipa.spec.in
+++ b/ipa.spec.in
@@ -235,6 +235,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.
@@ -392,6 +393,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 378e01234..9a121ea62 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)
@@ -459,6 +460,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')