From 19fddbde4971d3a96c2f5d678bd146786642ed07 Mon Sep 17 00:00:00 2001
From: Howard Chu <hyc@openldap.org>
Date: Fri, 14 Aug 2015 15:19:46 +0100
Subject: [PATCH] ITS#8185 add pwdMaxRecordedFailure

Limit the number of pwdFailureTime stamps to record, regardless
of lockout settings.
---
 doc/man/man5/slapo-ppolicy.5        | 23 ++++++++++-
 servers/slapd/overlays/ppolicy.c    | 59 ++++++++++++++++++++++++++++-
 servers/slapd/schema/ppolicy.ldif   |  5 ++-
 servers/slapd/schema/ppolicy.schema | 17 ++++++++-
 4 files changed, 100 insertions(+), 4 deletions(-)

diff --git a/doc/man/man5/slapo-ppolicy.5 b/doc/man/man5/slapo-ppolicy.5
index e352b1154f..3d4d0fa3b4 100644
--- a/doc/man/man5/slapo-ppolicy.5
+++ b/doc/man/man5/slapo-ppolicy.5
@@ -104,7 +104,7 @@ object class.  The definition of that class is as follows:
         pwdLockout $ pwdLockoutDuration $
         pwdMaxFailure $ pwdFailureCountInterval $
         pwdMustChange $ pwdAllowUserChange $
-        pwdSafeModify ) )
+        pwdSafeModify 4 pwdMaxRecordedFailure ) )
 .RE
 
 This implementation also provides an additional
@@ -365,6 +365,25 @@ and
    SINGLE\-VALUE )
 .RE
 
+.B pwdMaxRecordedFailure
+.P
+This attribute contains the maximum number of failed bind
+attempts to store in a user's entry.
+If
+.B pwdMaxRecordedFailure
+is not present, or its value is zero (0), then it defaults
+to the value of
+.BR pwdMaxFailure .
+If that value is also 0, the default is 5.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.16
+   NAME 'pwdMaxRecordedFailure'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE\-VALUE )
+.RE
+
 .B pwdFailureCountInterval
 .P
 This attribute contains the number of seconds after which old
@@ -641,6 +660,8 @@ account may be locked.
 password policy attribute.)
 Excess timestamps beyond those allowed by
 .B pwdMaxFailure
+or
+.B pwdMaxRecordedFailure
 may also be purged.  If a successful authentication is made to this
 DN (i.e. to this user account), then
 .B pwdFailureTime   
diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c
index 5958a4117e..7af3ca2059 100644
--- a/servers/slapd/overlays/ppolicy.c
+++ b/servers/slapd/overlays/ppolicy.c
@@ -45,6 +45,10 @@
 #define MODULE_NAME_SZ 256
 #endif
 
+#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE
+#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE	5
+#endif
+
 /* Per-instance configuration information */
 typedef struct pp_info {
 	struct berval def_policy;	/* DN of default policy subentry */
@@ -79,6 +83,7 @@ typedef struct pass_policy {
 	int pwdLockout; /* 0 = do not lockout passwords, 1 = lock them out */
 	int pwdLockoutDuration; /* time in seconds a password is locked out for */
 	int pwdMaxFailure; /* number of failed binds allowed before lockout */
+	int pwdMaxRecordedFailure;	/* number of failed binds to store */
 	int pwdFailureCountInterval; /* number of seconds before failure
 									counts are zeroed */
 	int pwdMustChange; /* 0 = users can use admin set password
@@ -179,7 +184,7 @@ static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdInHistory,
 	*ad_pwdGraceAuthNLimit, *ad_pwdExpireWarning, *ad_pwdLockoutDuration,
 	*ad_pwdFailureCountInterval, *ad_pwdCheckModule, *ad_pwdLockout,
 	*ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
-	*ad_pwdAttribute;
+	*ad_pwdAttribute, *ad_pwdMaxRecordedFailure;
 
 #define TAB(name)	{ #name, &ad_##name }
 
@@ -523,6 +528,9 @@ ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
 	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxFailure ) )
 			&& lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 )
 		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxRecordedFailure ) )
+			&& lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
 	if ( ( a = attr_find( pe->e_attrs, ad_pwdGraceAuthNLimit ) )
 			&& lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 )
 		goto defaultpol;
@@ -551,6 +559,11 @@ ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
 	if ((a = attr_find( pe->e_attrs, ad_pwdSafeModify )))
 	    	pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );
     
+	if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure )
+		pp->pwdMaxRecordedFailure = pp->pwdMaxFailure;
+	if ( !pp->pwdMaxRecordedFailure )
+		pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;
+
 	op->o_bd->bd_info = (BackendInfo *)on->on_info;
 	be_entry_release_r( op, pe );
 	op->o_bd->bd_info = (BackendInfo *)on;
@@ -995,6 +1008,50 @@ ppolicy_bind_response( Operation *op, SlapReply *rs )
 				 * which are not due to expire.
 				 */
 			}
+			/* Do we have too many timestamps? If so, delete some values.
+			 * We don't bother to sort the values here. OpenLDAP keeps the
+			 * values in order by default. Fundamentally, relying on the
+			 * information here is wrong anyway; monitoring systems should
+			 * be tracking Bind failures in syslog, not here.
+			 */
+			if (a->a_numvals >= ppb->pp.pwdMaxRecordedFailure) {
+				int j = ppb->pp.pwdMaxRecordedFailure-1;
+				/* If more than 2x, cheaper to perform a Replace */
+				if (a->a_numvals >= 2 * ppb->pp.pwdMaxRecordedFailure) {
+					struct berval v, nv;
+
+					/* Change the mod we constructed above */
+					m->sml_op = LDAP_MOD_REPLACE;
+					m->sml_numvals = ppb->pp.pwdMaxRecordedFailure;
+					v = m->sml_values[0];
+					nv = m->sml_nvalues[0];
+					ch_free(m->sml_values);
+					ch_free(m->sml_nvalues);
+					m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+					m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+					for (i=0; i<j; i++) {
+						ber_dupbv(&m->sml_values[i], &a->a_vals[a->a_numvals-j+i]);
+						ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[a->a_numvals-j+i]);
+					}
+					m->sml_values[i] = v;
+					m->sml_nvalues[i] = nv;
+				} else {
+				/* else just delete some */
+					m = ch_calloc( sizeof(Modifications), 1 );
+					m->sml_op = LDAP_MOD_DELETE;
+					m->sml_type = ad_pwdFailureTime->ad_cname;
+					m->sml_desc = ad_pwdFailureTime;
+					m->sml_numvals = a->a_numvals - j;
+					m->sml_values = ch_calloc( sizeof(struct berval), m->sml_numvals );
+					m->sml_nvalues = ch_calloc( sizeof(struct berval), m->sml_numvals );
+					for (i=0; i<m->sml_numvals; i++) {
+						ber_dupbv(&m->sml_values[i], &a->a_vals[i]);
+						ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[i]);
+					}
+					m->sml_next = mod;
+					mod = m;
+				}
+			}
 		}
 		
 		if ((ppb->pp.pwdMaxFailure > 0) &&
diff --git a/servers/slapd/schema/ppolicy.ldif b/servers/slapd/schema/ppolicy.ldif
index 8b37196277..1aa9c33b3f 100644
--- a/servers/slapd/schema/ppolicy.ldif
+++ b/servers/slapd/schema/ppolicy.ldif
@@ -75,10 +75,13 @@ olcAttributeTypes: {14}( 1.3.6.1.4.1.42.2.27.8.1.15 NAME 'pwdSafeModify' EQUAL
 olcAttributeTypes: {15}( 1.3.6.1.4.1.4754.1.99.1 NAME 'pwdCheckModule' DESC 'L
  oadable module that instantiates "check_password() function' EQUALITY caseExa
  ctIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+olcAttributeTypes: {16}( 1.3.6.1.4.1.42.2.27.8.1.16 NAME 'pwdMaxRecordedFailur
+ e' EQUALITY integerMatch ORDERING integerOrderingMatch  SYNTAX 1.3.6.1.4.1.
+ 1466.115.121.1.27 SINGLE-VALUE )
 olcObjectClasses: {0}( 1.3.6.1.4.1.4754.2.99.1 NAME 'pwdPolicyChecker' SUP top
   AUXILIARY MAY pwdCheckModule )
 olcObjectClasses: {1}( 1.3.6.1.4.1.42.2.27.8.2.1 NAME 'pwdPolicy' SUP top AUXI
  LIARY MUST pwdAttribute MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheck
  Quality $ pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLockout $
   pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ pwdMustChange
-  $ pwdAllowUserChange $ pwdSafeModify ) )
+  $ pwdAllowUserChange $ pwdSafeModify $ pwdMaxRecordedFailure ) )
diff --git a/servers/slapd/schema/ppolicy.schema b/servers/slapd/schema/ppolicy.schema
index 18bb1e60bf..6d27e3bfa2 100644
--- a/servers/slapd/schema/ppolicy.schema
+++ b/servers/slapd/schema/ppolicy.schema
@@ -325,6 +325,20 @@ 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 )
 
+#ITS#8185  pwdMaxRecordedFailure
+#
+#   This attribute specifies the maximum number of consecutive failed bind
+#   attempts to record.  If this attribute is not present, or if the value
+#	is 0, it defaults to the value of pwdMaxFailure. If that value is also
+#	0, this value defaults to 5.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.16
+      NAME 'pwdMaxRecordedFailure'
+      EQUALITY integerMatch
+      ORDERING integerOrderingMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
 # HP extensions
 #
 # pwdCheckModule
@@ -366,7 +380,8 @@ 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 $
+      pwdMaxRecordedFailure ) )
 
 #5.3  Attribute Types for Password Policy State Information
 #
-- 
GitLab