[Date Prev][Date Next]
[Chronological]
[Thread]
[Top]
ppolicy: pardon password history
Hi list,
I have made a tiny modification to the ppolicy-module. The aim is to
go easy on people who forgot their password, or forgot to deploy their
recently changed password to all devices (think of laptops,
smartphones, etc.). Whenever a login fails due to a invalid password,
the ppolicy-module will count this as a failure. After a configurable
number of password failures in a given time, ppolicy will take action
and - for example - lock the acount. I have tried to tweak this
behaviour: When the password is found in the password history, the
ppolicy-module will not count this as a password failure. If anyone is
interested in this, please find the attached patch which also includes
a working example configuration/testcase.
Best regards,
Florian
--
Florian Hercher | Hochschulrechenzentrum | Philipps-Universität Marburg
Hans-Meerwein-Str. 6, 35032 Marburg | fon +49-(0)6421-28-21036
diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c
index b92f352..3ffbe21 100644
--- a/servers/slapd/overlays/ppolicy.c
+++ b/servers/slapd/overlays/ppolicy.c
@@ -81,6 +81,10 @@ typedef struct pass_policy {
int pwdMaxFailure; /* number of failed binds allowed before lockout */
int pwdFailureCountInterval; /* number of seconds before failure
counts are zeroed */
+ int pwdPardonHistory; /* 0 = count every failed bind for password failure count,
+ 1 = ignore failed bind for failure count
+ if password is found in the history of old passwords */
+
int pwdMustChange; /* 0 = users can use admin set password
1 = users must change password after admin set */
int pwdAllowUserChange; /* 0 = users cannot change their passwords
@@ -175,10 +179,10 @@ static struct schema_info {
/* User attributes */
static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdInHistory,
- *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxFailure,
+ *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxFailure,
*ad_pwdGraceAuthNLimit, *ad_pwdExpireWarning, *ad_pwdLockoutDuration,
- *ad_pwdFailureCountInterval, *ad_pwdCheckModule, *ad_pwdLockout,
- *ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
+ *ad_pwdFailureCountInterval, *ad_pwdPardonHistory, *ad_pwdCheckModule,
+ *ad_pwdLockout, *ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
*ad_pwdAttribute;
#define TAB(name) { #name, &ad_##name }
@@ -196,6 +200,7 @@ static struct schema_info pwd_UsSchema[] = {
TAB(pwdLockout),
TAB(pwdLockoutDuration),
TAB(pwdFailureCountInterval),
+ TAB(pwdPardonHistory),
TAB(pwdCheckModule),
TAB(pwdMustChange),
TAB(pwdAllowUserChange),
@@ -554,7 +559,9 @@ ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
if ((a = attr_find( pe->e_attrs, ad_pwdSafeModify )))
pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );
-
+ if ((a = attr_find( pe->e_attrs, ad_pwdPardonHistory )))
+ pp->pwdPardonHistory = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
op->o_bd->bd_info = (BackendInfo *)on->on_info;
be_entry_release_r( op, pe );
op->o_bd->bd_info = (BackendInfo *)on;
@@ -912,6 +919,38 @@ ppolicy_ctrls_cleanup( Operation *op, SlapReply *rs )
}
static int
+ppolicy_check_list( Operation *op, Entry *e ) {
+ ppbind *ppb = op->o_callback->sc_private;
+ int i, rc;
+ Attribute *ha;
+
+ if ( ppb->pp.pwdInHistory > 0 && (ha = attr_find( e->e_attrs, ad_pwdHistory ))) {
+ struct berval oldpw;
+ time_t oldtime;
+ const char *txt;
+
+ for(i=0; ha->a_nvals[i].bv_val; i++) {
+ oldpw.bv_val = NULL;
+ oldpw.bv_len = 0;
+
+ rc = parse_pwdhistory( &(ha->a_nvals[i]), NULL,
+ &oldtime, &oldpw );
+
+ if (rc != LDAP_SUCCESS) continue; /* invalid history entry */
+
+ rc = lutil_passwd( &oldpw, &op->orb_cred,
+ NULL, &txt );
+
+ if ( rc == LDAP_SUCCESS) {
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+
+ return LDAP_UNWILLING_TO_PERFORM;
+}
+
+static int
ppolicy_bind_response( Operation *op, SlapReply *rs )
{
ppbind *ppb = op->o_callback->sc_private;
@@ -959,6 +998,19 @@ ppolicy_bind_response( Operation *op, SlapReply *rs )
if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) {
int i = 0, fc = 0;
+ /*
+ * if the given password is found in the
+ * password history, don't increase the
+ * password failure count
+ */
+ if ( ppb->pp.pwdPardonHistory ) {
+ rc = ppolicy_check_list( op, e );
+ if ( rc == LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_STATS, "ppolicy: ignore failed login attempt with old password\n", 0, 0, 0);
+ goto done;
+ }
+ }
+
m = ch_calloc( sizeof(Modifications), 1 );
m->sml_op = LDAP_MOD_ADD;
m->sml_flags = 0;
@@ -1021,6 +1073,7 @@ ppolicy_bind_response( Operation *op, SlapReply *rs )
ber_dupbv( &m->sml_nvalues[0], ×tamp );
m->sml_next = mod;
mod = m;
+ Statslog( LDAP_DEBUG_STATS, "ppolicy: account[%s] locked for [%d]\n", op->o_req_dn.bv_val, ppb->pp.pwdLockoutDuration, 0, 0, 0);
}
} else if ( rs->sr_err == LDAP_SUCCESS ) {
if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL)
diff --git a/servers/slapd/schema/ppolicy.schema b/servers/slapd/schema/ppolicy.schema
index 18bb1e6..301a04b 100644
--- a/servers/slapd/schema/ppolicy.schema
+++ b/servers/slapd/schema/ppolicy.schema
@@ -325,6 +325,18 @@ attributetype ( 1.3.6.1.4.1.42.2.27.8.1.15
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE )
+# Pardon History Exstension
+# This attribute specifies whether or not a failed login attempt
+# should be counted when the password is found in the password
+# history. If this attribute is not present, a "FALSE" value is assumed.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.77
+ NAME 'pwdPardonHistory'
+ EQUALITY booleanMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+ SINGLE-VALUE )
+
+
# HP extensions
#
# pwdCheckModule
@@ -366,7 +378,7 @@ objectclass ( 1.3.6.1.4.1.42.2.27.8.2.1
MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $
pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLockout
$ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $
- pwdMustChange $ pwdAllowUserChange $ pwdSafeModify ) )
+ pwdMustChange $ pwdAllowUserChange $ pwdSafeModify $ pwdPardonHistory ) )
#5.3 Attribute Types for Password Policy State Information
#
diff --git a/tests/scripts/conf.sh b/tests/scripts/conf.sh
old mode 100755
new mode 100644
diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh
old mode 100755
new mode 100644
index 96e3d9b..c26454e
--- a/tests/scripts/defines.sh
+++ b/tests/scripts/defines.sh
@@ -224,6 +224,7 @@ LDIFPASSWD=$DATADIR/passwd.ldif
LDIFWHOAMI=$DATADIR/test-whoami.ldif
LDIFPASSWDOUT=$DATADIR/passwd-out.ldif
LDIFPPOLICY=$DATADIR/ppolicy.ldif
+LDIFPPARDON=$DATADIR/ppardon.ldif
LDIFLANG=$DATADIR/test-lang.ldif
LDIFLANGOUT=$DATADIR/lang-out.ldif
LDIFREF=$DATADIR/referrals.ldif
diff --git a/tests/data/ppardon.ldif b/tests/data/ppardon.ldif
index e69de29..886e026 100644
--- a/tests/data/ppardon.ldif
+++ b/tests/data/ppardon.ldif
@@ -0,0 +1,69 @@
+dn: dc=example, dc=com
+objectClass: top
+objectClass: organization
+objectClass: dcObject
+o: Example
+dc: example
+
+dn: ou=People, dc=example, dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: People
+
+dn: ou=Policies, dc=example, dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: Policies
+
+dn: cn=Standard Policy, ou=Policies, dc=example, dc=com
+objectClass: top
+objectClass: device
+objectClass: pwdPolicy
+cn: Standard Policy
+pwdAttribute: 2.5.4.35
+pwdLockoutDuration: 15
+pwdInHistory: 5
+pwdCheckQuality: 0
+pwdExpireWarning: 0
+pwdMaxAge: 0
+pwdMinLength: 5
+pwdGraceAuthnLimit: 0
+pwdAllowUserChange: TRUE
+pwdMustChange: FALSE
+pwdMaxFailure: 3
+pwdFailureCountInterval: 120
+pwdSafeModify: TRUE
+pwdLockout: TRUE
+pwdPardonHistory: TRUE
+
+dn: uid=nd, ou=People, dc=example, dc=com
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+cn: Neil Dunbar
+uid: nd
+sn: Dunbar
+givenName: Neil
+userPassword: testpassword
+
+dn: uid=ndadmin, ou=People, dc=example, dc=com
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+cn: Neil Dunbar (Admin)
+uid: ndadmin
+sn: Dunbar
+givenName: Neil
+userPassword: testpw
+
+dn: uid=test, ou=People, dc=example, dc=com
+objectClass: top
+objectClass: person
+objectClass: inetOrgPerson
+cn: test test
+uid: test
+sn: Test
+givenName: Test
+userPassword: kfhgkjhfdgkfd
+pwdPolicySubEntry: cn=No Policy, ou=Policies, dc=example, dc=com
+
diff --git a/tests/scripts/test077-ppolicy-pardon b/tests/scripts/test077-ppolicy-pardon
index e69de29..734133b 100755
--- a/tests/scripts/test077-ppolicy-pardon
+++ b/tests/scripts/test077-ppolicy-pardon
@@ -0,0 +1,200 @@
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2012 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+if test $PPOLICY = ppolicyno; then
+ echo "Password policy overlay not available, test skipped"
+ exit 0
+fi
+
+mkdir -p $TESTDIR $DBDIR1
+
+$SLAPPASSWD -g -n >$CONFIGPWF
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >$TESTDIR/configpw.conf
+
+echo "Starting slapd on TCP/IP port $PORT1..."
+. $CONFFILTER $BACKEND $MONITORDB < $PPOLICYCONF > $CONF1
+$SLAPD -f $CONF1 -h $URI1 -d $LVL $TIMING > $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+ echo PID $PID
+ read foo
+fi
+KILLPIDS="$PID"
+
+USER="uid=nd, ou=People, dc=example, dc=com"
+PASS=testpassword
+
+sleep 1
+
+echo "Using ldapsearch to check that slapd is running..."
+for i in 0 1 2 3 4 5; do
+ $LDAPSEARCH -s base -b "$MONITOR" -h $LOCALHOST -p $PORT1 \
+ 'objectclass=*' > /dev/null 2>&1
+ RC=$?
+ if test $RC = 0 ; then
+ break
+ fi
+ echo "Waiting 5 seconds for slapd to start..."
+ sleep 5
+done
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo /dev/null > $TESTOUT
+
+echo "Using ldapadd to populate the database..."
+# may need "-e relax" for draft 09, but not yet.
+$LDAPADD -D "$MANAGERDN" -h $LOCALHOST -p $PORT1 -w $PASSWD < \
+ $LDIFPPARDON >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapadd failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Filling password history..."
+$LDAPMODIFY -v -D "$USER" -h $LOCALHOST -p $PORT1 -w $PASS >> \
+ $TESTOUT 2>&1 << EOMODS
+dn: uid=nd, ou=People, dc=example, dc=com
+changetype: modify
+delete: userpassword
+userpassword: $PASS
+-
+replace: userpassword
+userpassword: 20urgle12-1
+
+dn: uid=nd, ou=People, dc=example, dc=com
+changetype: modify
+delete: userpassword
+userpassword: 20urgle12-1
+-
+replace: userpassword
+userpassword: 20urgle12-2
+
+dn: uid=nd, ou=People, dc=example, dc=com
+changetype: modify
+delete: userpassword
+userpassword: 20urgle12-2
+-
+replace: userpassword
+userpassword: 20urgle12-3
+
+dn: uid=nd, ou=People, dc=example, dc=com
+changetype: modify
+delete: userpassword
+userpassword: 20urgle12-3
+-
+replace: userpassword
+userpassword: 20urgle12-4
+
+dn: uid=nd, ou=People, dc=example, dc=com
+changetype: modify
+delete: userpassword
+userpassword: 20urgle12-4
+-
+replace: userpassword
+userpassword: 20urgle12-5
+
+dn: uid=nd, ou=People, dc=example, dc=com
+changetype: modify
+delete: userpassword
+userpassword: 20urgle12-5
+-
+replace: userpassword
+userpassword: 20urgle12-6
+
+dn: uid=nd, ou=People, dc=example, dc=com
+changetype: modify
+delete: userpassword
+userpassword: 20urgle12-6
+-
+replace: userpassword
+userpassword: $PASS
+EOMODS
+
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapmodify failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "check access..."
+
+$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w $PASS \
+ -b "$BASEDN" -s base >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "test lockout with unseen password..."
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -D "$USER" -w wrongpw >$SEARCHOUT 2>&1
+sleep 2
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -D "$USER" -w wrongpw >>$SEARCHOUT 2>&1
+sleep 2
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -D "$USER" -w wrongpw >>$SEARCHOUT 2>&1
+sleep 2
+$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w wrongpw >> $SEARCHOUT 2>&1
+sleep 2
+
+$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w $PASS \
+ -b "$BASEDN" -s base >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 49 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Waiting 20 seconds for lockout to reset..."
+sleep 20
+
+echo "test lockout with password from history..."
+
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -D "$USER" -w 20urgle12-3 >$SEARCHOUT 2>&1
+sleep 2
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -D "$USER" -w 20urgle12-3 >>$SEARCHOUT 2>&1
+sleep 2
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -D "$USER" -w 20urgle12-3 >>$SEARCHOUT 2>&1
+sleep 2
+$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w 20urgle12-3 >> $SEARCHOUT 2>&1
+sleep 2
+
+$LDAPSEARCH -e ppolicy -h $LOCALHOST -p $PORT1 -D "$USER" -w $PASS \
+ -b "$BASEDN" -s base >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0