2.4.11 password policy enhancements 2.4.11
authoraravind@localhost
Sat, 30 May 2009 07:50:01 -0700
changeset 0 69729ffba85c
child 1 656a462522b1
push id1
push useraravind@mozilla.com
push date2009-05-30 14:51 +0000
bugs2
2.4.11 password policy enhancements
README
draft-mozilla-ldap-password-policy-05.doc
draft-mozilla-ldap-password-policy-05.txt
ppolicy.c
ppolicy.schema
slapo-ppolicy.5
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,111 @@
+ppolicy enhancement
+===================
+
+OpenLDAP Verion 2.4.11
+
+Description:
+============
+
+The OpenLDAP ppolicy overlay provides many powerful features to LDAP 
+administrators that significantly enhance security including password aging 
+which forces users to regularly change passwords. 
+
+Many LDAP client applications cache user passwords and in most cases error 
+recovery strategies will involve repeated attempts. Following a password change 
+this frequently means multiple bind attempts using an incorrect password. The 
+standard OpenLDAP ppolicy overlay counts all incorrect passwords attempts as 
+errors and when the count of such errors reaches the value defined by 
+pwdMaxFailure the user's account is locked. This can lead to a significant 
+number of false account lockout conditions increasing user frustration and 
+administrator workloads. To minimise such false positive lockouts
+administrators may raise the value of pwdMaxFailure which in turn may increase 
+the likehood of a dictionary attack succeeding. 
+
+The ppolicy enhancement allows administrators to optionally differentiate 
+between login attempts using a repeat password and those using a unique 
+password. The administrator may optionally choose to set a limit to the number
+of login attempts using a repeat password to provide DoS protection. The 
+enhanced ppolicy behavior is invoked through the use of an addition pwdPolicy 
+user attribute (pwdMaxTotalAttempts).  
+
+Acknowledgment:
+===============
+
+Funding for the enhancement was generously provided by Mozilla Corporation.
+
+Contents:
+=========
+
+This enhancement consists of the following items:
+
+1. replacement ppolicy.c
+2. replacement ppolicy.schema
+3. replacement man page slapo-ppolicy.5
+4. draft-mozilla-ldap-password-policy-05.doc
+5. draft-mozilla-ldap-password-policy-05.txt
+
+Installation:
+=============
+
+The enhancement may be installed to either a source distribution (tarball) or 
+BSD port as follows:
+
+If using a tarball:
+-------------------
+1. Unpack the source to a suitable directly 
+2. Replace the following:
+  a. openldap-2.4.11/servers/slapd/overlays/ppolicy.c
+  b. openldap-2.4.11/servers/slapd/schema/ppolicy.schema
+  c. openldap-2.4.11/doc/man/man5/slapo-ppolicy.5
+3. Configure, build and install OpenLDAP in the normal manner
+
+If using a BSD Port:
+--------------------
+
+1. cd to the port directory (normally /usr/ports/net/openldap24-server)
+2. make configure
+3. when the configure has stopped replace the following modules with those 
+   supplied with this enhancement
+  a. work/openldap-2.4.11/servers/slapd/overlays/ppolicy.c
+  b. work/openldap-2.4.11/servers/slapd/schema/ppolicy.schema
+  c. work/openldap-2.4.11/doc/mam/man5/slapo-ppolicy.5
+4. make install clean 
+
+If enhancing an existing installation you can either:
+
+ a. repeat the full install process as above using any special flags necessary 
+    to override any package management utilities.
+ b. Complete the build but do not intstall the full OpenLDAP system:
+    i. if using dynamic overlays copy ppolicy.la (and/or ppolicy.so) from 
+        openldap-2.4.11/servers/slapd/.libs/ppolicy.la to the appropriate 
+       location
+    ii. if using static (compiled in) overlays copy the openldap application 
+		    from openldap-2.4.11/servers/slapd/.libs/slapd to the executable 
+        location (use locate slapd to find)
+    iii. copy ppolicy.schema to your normal schema location
+    iv.  tar the updated man file from 
+         openldap-2.4.111/doc/man/man5/slapo-ppolicy.5.tmp to your normal man5 
+         location (typical Linux = /usr/man/man5 BSD = /usr/local/man/man5) 
+         using a command like:
+         tar -czvf man/man5/slapo-ppolicy.5.gz doc/man/man5/slapo-ppolicy.5.tmp
+         
+To invoke repeat password checking:
+====================================
+
+The user attribute pwdMaxTotalAttempts (in objectclass pwdPolicy) defines what, 
+if any, processing occurs when a bind attempts fails when using a repeat 
+password . The attribute may take one of three values. If pwdMaxTotalAttempts 
+is zero (0) or not defined then no repeat password checking is perfomed. If 
+pwdMaxTotalAttempts is -1 repeat password checking is performed and an 
+unlimited number of attempts with any number (up to the limit defined by 
+pwdMaxFailure) of repeat passwords are allowed. It shuld be noted that allowing
+an unlimited number of repeat password attempts may increase the effectivness 
+of a DoS attack using large numbers of unsuccessful bind attempts. If 
+pwdMaxTotalAttempts is set to any positive number then this number defines the 
+maximum number of unique plus repeat password attempts allowed before the 
+account is locked.
+
+The operationalal attribute pwdUniqueAttempts is used to store all unique 
+failed password attempts and will appear only if pwdMaxTotalAttempts is either 
+-1 or a positive number. The failed password is maintained in hashed format in 
+this attribute.
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3779d00aadc46c19512668c22582e898de2cf9a1
GIT binary patch
literal 221184
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
C4*>80
new file mode 100644
--- /dev/null
+++ b/draft-mozilla-ldap-password-policy-05.txt
@@ -0,0 +1,2373 @@
+
+
+
+
+Network Working Group                                     J. Sermersheim
+Internet-Draft                                               Novell, Inc
+Expires: January 18, 2006                                      L. Poitou
+                                                        Sun Microsystems
+                                                           July 17, 2005
+Modification dated April 20, 2009
+
+                  Password Policy for LDAP Directories
+                draft-mozilla-ldap-password-policy-05.txt
+(modified version of draft-behera-ldap-password-policy-09.txt by Aravind 
+Gottpati, Mozzilla Corporation, Jeff Clowser, Fannie Mae, Ron Aitchison, 
+Zytrax, Inc.)
+
+Status of this Memo
+
+   By submitting this Internet-Draft, each author represents that any
+   applicable patent or other IPR claims of which he or she is aware
+   have been or will be disclosed, and any of which he or she becomes
+   aware will be disclosed, in accordance with Section 6 of BCP 79.
+
+   Internet-Drafts are working documents of the Internet Engineering
+   Task Force (IETF), its areas, and its working groups.  Note that
+   other groups may also distribute working documents as Internet-
+   Drafts.
+
+   Internet-Drafts are draft documents valid for a maximum of six months
+   and may be updated, replaced, or obsoleted by other documents at any
+   time.  It is inappropriate to use Internet-Drafts as reference
+   material or to cite them other than as "work in progress."
+
+   The list of current Internet-Drafts can be accessed at
+   http://www.ietf.org/ietf/1id-abstracts.txt.
+
+   The list of Internet-Draft Shadow Directories can be accessed at
+   http://www.ietf.org/shadow.html.
+
+   This Internet-Draft will expire on January 18, 2006.
+
+Copyright Notice
+
+   Copyright (C) The Internet Society (2005).
+
+Abstract
+
+   Password policy as described in this document is a set of rules that
+   controls how passwords are used and administered in Lightweight
+   Directory Access Protocol (LDAP) based directories.  In order to
+   improve the security of LDAP directories and make it difficult for
+   password cracking programs to break into directories, it is desirable
+   to enforce a set of rules on password usage.  These rules are made to
+   ensure that users change their passwords periodically, passwords meet
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 1]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   construction requirements, the re-use of old password is restricted,
+   and users are locked out after a certain number of failed attempts.
+
+Discussion Forum
+
+   Technical discussion of this document will take place on the IETF
+   LDAP Extensions mailing list <ldapext@ietf.org>.  Please send
+   editorial comments directly to the authors.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 2]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+Table of Contents
+
+   1.  Overview . . . . . . . . . . . . . . . . . . . . . . . . . . .  4
+   2.  Conventions  . . . . . . . . . . . . . . . . . . . . . . . . .  5
+   3.  Application of password policy . . . . . . . . . . . . . . . .  6
+   4.  Articles of password policy  . . . . . . . . . . . . . . . . .  7
+   4.1 Password Usage Policy  . . . . . . . . . . . . . . . . . . . .  7
+   4.2 Password Modification Policy . . . . . . . . . . . . . . . . .  7
+   4.3 Restriction of the Password Policy . . . . . . . . . . . . . . 10
+   5.  Schema used for Password Policy  . . . . . . . . . . . . . . . 11
+   5.1 The pwdPolicy Object Class . . . . . . . . . . . . . . . . . . 11
+   5.2 Attribute Types used in the pwdPolicy ObjectClass  . . . . . . 11
+   5.3 Attribute Types for Password Policy State Information  . . . . 16
+   6.  Controls used for Password Policy  . . . . . . . . . . . . . . 21
+   6.1 Request Control  . . . . . . . . . . . . . . . . . . . . . . . 21
+   6.2 Response Control . . . . . . . . . . . . . . . . . . . . . . . 21
+   7.  Policy Decision Points . . . . . . . . . . . . . . . . . . . . 23
+   7.1 Locked Account Check . . . . . . . . . . . . . . . . . . . . . 23
+   7.2 Password Must be Changed Now Check . . . . . . . . . . . . . . 23
+   7.3 Password Expiration Check  . . . . . . . . . . . . . . . . . . 23
+   7.4 Remaining Grace AuthN Check  . . . . . . . . . . . . . . . . . 23
+   7.5 Time Before Expiration Check . . . . . . . . . . . . . . . . . 24
+   7.6 Intruder Detection Check . . . . . . . . . . . . . . . . . . . 24
+   7.7 Password Too Young Check . . . . . . . . . . . . . . . . . . . 24
+   8.  Server Policy Enforcement Points . . . . . . . . . . . . . . . 25
+   8.1 Password-based Authentication  . . . . . . . . . . . . . . . . 25
+   8.2 Password Update Operations . . . . . . . . . . . . . . . . . . 27
+   8.3 Other Operations . . . . . . . . . . . . . . . . . . . . . . . 30
+   9.  Client Policy Enforcement Points . . . . . . . . . . . . . . . 31
+   9.1 Bind Operation . . . . . . . . . . . . . . . . . . . . . . . . 31
+   9.2 Modify Operations  . . . . . . . . . . . . . . . . . . . . . . 32
+   9.3 Add Operation  . . . . . . . . . . . . . . . . . . . . . . . . 33
+   9.4 Compare Operation  . . . . . . . . . . . . . . . . . . . . . . 33
+   9.5 Other Operations . . . . . . . . . . . . . . . . . . . . . . . 34
+   10. Administration of the Password Policy  . . . . . . . . . . . . 35
+   11. Password Policy and Replication  . . . . . . . . . . . . . . . 36
+   12. Security Considerations  . . . . . . . . . . . . . . . . . . . 37
+   13. IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 38
+   14. Acknowledgement  . . . . . . . . . . . . . . . . . . . . . . . 39
+   15. Normative References . . . . . . . . . . . . . . . . . . . . . 39
+       Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . 40
+       Intellectual Property and Copyright Statements . . . . . . . . 41
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 3]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+1.  Overview
+
+   LDAP-based directory services are currently accepted by many
+   organizations as the access protocol for directories.  The ability to
+   ensure the secure read and update access to directory information
+   throughout the network is essential to the successful deployment.
+   Most LDAP implementations support many authentication schemes - the
+   most basic and widely used is the simple authentication i.e., user DN
+   and password.  In this case, many LDAP servers have implemented some
+   kind of policy related to the password used to authenticate.  Among
+   other things, this policy includes:
+
+   o  Whether and when passwords expire.
+
+   o  Whether failed bind attempts cause the account to be locked.
+
+   o  If and how users are able to change their passwords.
+
+   In order to achieve greater security protection and ensure
+   interoperability in a heterogeneous environment, LDAP needs to
+   standardize on a common password policy model.  This is critical to
+   the successful deployment of LDAP directories.
+
+Initial implementations of the password policy defined in the original version 
+of this document (draft-behara-ldap-password-policy-09.txt) have shown 
+themselves to be vulnerable to an unacceptably high number of false-positive 
+account lockouts especially in cases where LDAP is used to provide general 
+access security to a significant number of users. The effect of an excessive 
+number of false-positive account lockout conditions is to cause significant 
+operational work coupled with user delay and frustration. Both conditions may 
+result in a tendency toward, or demand for, decreased access security to the 
+detriment of the original objective of this document. It is vital for the 
+maintenance of good security that false-positive account lockout conditions be 
+minimized whenever possible.
+
+A primary cause of the high number account lockouts has been traced to the use 
+of applications which cache passwords and which may then retry the 
+authentication operation multiple times. Following a mandated password 
+modification the next time a user accesses the LDAP controlled system a cached 
+password can and frequently does trigger the account lockout threshold 
+(pwdMaxFailure) leading to a false positive. Authentication requests from such 
+applications are characterized by their repeated use of a single password which 
+may be safely differentiated from a classic dictionary password attack which is 
+characterized by a constantly changing password value.
+
+The revised version of this policy provides optional granularity to allow a 
+password administrator to optionally:
+?	Differentiate between LDAP server behavior when the user supplies a repeated 
+password value versus a constantly changing value
+
+?	Control the behavior of the LDAP server when an excessive number of repeat 
+password attempts have been received (to prevent DoS attacks)
+
+In addition a number of minor clarifications are introduced to minimize use of 
+system resources.
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 4]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+2.  Conventions
+
+   Imperative keywords defined in [RFC2119] are used in this document,
+   and carry the meanings described there.
+
+   All Basic Encoding Rules (BER) [X690] encodings follow the
+   conventions found in Section 5.1 of [RFC2251].
+
+   The term "password administrator" refers to a user that has
+   sufficient access control privileges to modify users' passwords.  The
+   term "password policy administrator" refers to a user that has
+   sufficient access control privileges to modify the pwdPolicy object
+   defined in this document.  The access control that is used to
+   determine whether an identity is a password administrator or password
+   policy administrator is beyond the scope of this document, but
+   typically implies that the password administrator has 'write'
+   privileges to the password attribute.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 5]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+3.  Application of password policy
+
+The password policy defined in this document can be applied to any attribute 
+holding a user's password used for an authenticated LDAP bind operation. In 
+this document, the term "user" represents any LDAP client application that has 
+an identity in the directory.
+
+This policy is typically applied to the userPassword attribute in the case of 
+the LDAP simple authentication method [RFC2251] or the case of password based 
+SASL [RFC2222] authentication such as CRAM-MD5 [RFC2195] and DIGEST-MD5 
+[RFC2831].
+
+The policy described in this document assumes that the password attribute holds 
+a single value.  No considerations are made for directories or systems that 
+allow a user to maintain multi-valued password attributes.
+
+Server implementations MAY institute internal policy whereby certain identities 
+(such as directory administrators) are not forced to comply with any of 
+password policy.  In this case, the password for a directory administrator 
+never expires; the account is never locked, etc.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 6]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+4.  Articles of password policy
+
+The following sections explain in general terms each aspect of the password 
+policy defined in this document as well as the need for each.  These policies 
+are subdivided into the general groups of password usage and password 
+modification.  Implementation details are presented in Section 8 and Section 9. 
+If the additional attributes defined in section 5.1 are not defined then 
+processing is unchanged from the original definition of this policy. That is 
+the user must take explicit action to invoke the additional checks.
+
+4.1  Password Usage Policy
+
+This section describes policy enforced when a password is used to authenticate.  
+The general focus of this policy is to minimize the threat of intruders once a 
+password is in use.
+
+4.1.1  Password Guessing Limit
+
+In order to prevent intruders from guessing a user's password, a mechanism 
+exists to track the number of consecutive failed authentication attempts, and 
+take action when a limit is reached. This policy consists of seven parts:
+
+o  A configurable limit on failed authentication attempts where a different 
+password has been used.
+
+o  A configurable limit on failed authentication attempts where a repeated 
+password has been used.
+
+o  A counter to track the number of failed authentication attempts where 
+different passwords have been used.
+
+o  A counter to track the number of failed authentication attempts where a 
+repeated password has been used.
+
+o  A counter to track the number of failed authentication attempts where 
+different passwords have been used.
+
+o  A timeframe in which the limit of consecutive failed authentication attempts 
+must happen before action is taken.
+
+o  The action to be taken when the limit is reached.  The action will either be 
+nothing, or the account will be locked.
+
+o  An amount of time the account is locked (if it is to be locked). This can be 
+indefinite.
+
+
+4.2  Password Modification Policy
+
+   This section describes policy enforced while users are modifying
+   passwords.  The general focus of this policy is to ensure that when
+   users add or change their passwords, the security and effectiveness
+   of their passwords is maximized.  In this document, the term "modify
+   password operation" refers to any operation that is used to add or
+   modify a password attribute.  Often this is done by updating the
+   password attribute during an add or modify operation, but MAY be done
+   by other means such as an extended operation.
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 7]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+4.2.1  Password Expiration, Expiration Warning, and Grace
+       Authentications
+
+   One of the key properties of a password is the fact that it is not
+   well known.  If a password is frequently changed, the chances of that
+   user's account being broken into are minimized.
+
+   Password policy administrators may deploy a password policy that
+   causes passwords to expire after a given amount of time - thus
+   forcing users to change their passwords periodically.
+
+   As a side effect, there needs to be a way in which users are made
+   aware of this need to change their password before actually being
+   locked out of their accounts.  One or both of the following methods
+   handle this:
+
+   o  A warning may be returned to the user sometime before his password
+      is due to expire.  If the user fails to heed this warning before
+      the expiration time, his account will be locked.
+
+   o  The user may bind to the directory a preset number of times after
+      her password has expired.  If she fails to change her password
+      during one of her 'grace' authentications, her account will be
+      locked.
+
+
+4.2.2  Password History
+
+   When the Password Expiration policy is used, an additional mechanism
+   may be employed to prevent users from simply re-using a previous
+   password (as this would effectively circumvent the expiration
+   policy).
+
+   In order to do this; a history of used passwords is kept.  The
+   password policy administrator sets the number of passwords to be
+   stored at any given time.  Passwords are stored in this history
+   whenever the password is changed.  Users aren't allowed to specify
+   any passwords that are in the history list while changing passwords.
+
+4.2.3  Password Minimum Age
+
+   Users may circumvent the Password History mechanism by quickly
+   performing a series of password changes.  If they change their
+   password enough times, their 'favorite' password will be pushed out
+   of the history list.
+
+   This process may be made less attractive to users by employing a
+   minimum age for passwords.  If users are forced to wait 24 hours
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 8]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   between password changes, they may be less likely to cycle through a
+   history of 10 passwords.
+
+4.2.4  Password Quality and Minimum length
+
+   In order to prevent users from creating or updating passwords that
+   are easy to guess, a password quality policy may be employed.  This
+   policy consists of two general mechanisms - ensuring that passwords
+   conform to a defined quality criterion and ensuring that they are of
+   a minimum length.
+
+   Forcing a password to comply with the quality policy may imply a
+   variety of things including:
+
+   o  Disallowing trivial or well-known words make up the password.
+
+   o  Forcing a certain number of digits be used.
+
+   o  Disallowing anagrams of the user's name.
+
+   The implementation of this policy meets with the following problems:
+
+   o  If the password to be added or updated is encrypted by the client
+      before being sent, the server has no way of enforcing this policy.
+      Therefore, the onus of enforcing this policy falls upon client
+      implementations.
+
+   o  There are no specific definitions of what 'quality checking'
+      means.  This can lead to unexpected behavior in a heterogeneous
+      environment.
+
+
+4.2.5  User Defined Passwords
+
+   In some cases, it is desirable to disallow users from adding and
+   updating their own passwords.  This policy makes this functionality
+   possible.
+
+4.2.6  Password Change after Reset
+
+   This policy forces the user to update her password after it has been
+   set for the first time, or has been reset by a password
+   administrator.
+
+   This is needed in scenarios where a password administrator has set or
+   reset the password to a well-known value.
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006                [Page 9]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+4.2.7  Safe Modification
+
+   As directories become more commonly used, it will not be unusual for
+   clients to connect to a directory and leave the connection open for
+   an extended period.  This opens up the possibility for an intruder to
+   make modifications to a user's password while that user's computer is
+   connected but unattended.
+
+   This policy forces the user to prove his identity by specifying the
+   old password during a password modify operation.
+
+   {TODO: This allows a dictionary attack unless we specify that this is
+   also subject to intruder detection.  One solution is to require users
+   to authN prior to changing password.  Another solution is to perform
+   intruder detection checks when the password for a non-authenticated
+   identity is being updated}
+
+4.3  Restriction of the Password Policy
+
+   The password policy defined in this document can apply to any
+   attribute containing a password.  Password policy state information
+   is held in the user's entry, and applies to a password attribute, not
+   a particular password attribute value.  Thus the server SHOULD
+   enforce that the password attribute subject to password policy,
+   contains one and only one password value.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 10]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+5.  Schema used for Password Policy
+
+   The schema elements defined here fall into two general categories.  A
+   password policy object class is defined which contains a set of
+   administrative password policy attributes, and a set of operational
+   attributes are defined that hold general password policy state
+   information for each user.
+
+5.1  The pwdPolicy Object Class
+
+   This object class contains the attributes defining a password policy
+   in effect for a set of users.  Section 10 describes the
+   administration of this object, and the relationship between it and
+   particular objects.
+
+      ( 1.3.6.1.4.1.42.2.27.8.2.1
+      NAME 'pwdPolicy'
+      SUP top
+      AUXILIARY
+      MUST ( pwdAttribute )
+      MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $
+      pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLockout
+      $ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $
+      pwdMustChange $ pwdAllowUserChange $ pwdSafeModify 
+      pwdMaxTotalAttempts) )
+
+
+5.2  Attribute Types used in the pwdPolicy ObjectClass
+
+   Following are the attribute types used by the pwdPolicy object class.
+
+5.2.1  pwdAttribute
+
+   This holds the name of the attribute to which the password policy is
+   applied.  For example, the password policy may be applied to the
+   userPassword attribute.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.1
+      NAME 'pwdAttribute'
+      EQUALITY objectIdentifierMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
+
+
+5.2.2  pwdMinAge
+
+   This attribute holds the number of seconds that must elapse between
+   modifications to the password.  If this attribute is not present, 0
+   seconds is assumed.
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 11]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.2
+      NAME 'pwdMinAge'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.3  pwdMaxAge
+
+   This attribute holds the number of seconds after which a modified
+   password will expire.
+
+   If this attribute is not present, or if the value is 0 the password
+   does not expire.  If not 0, the value must be greater than or equal
+   to the value of the pwdMinAge.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.3
+      NAME 'pwdMaxAge'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.4  pwdInHistory
+
+   This attribute specifies the maximum number of used passwords stored
+   in the pwdHistory attribute.
+
+   If this attribute is not present, or if the value is 0, used
+   passwords are not stored in the pwdHistory attribute and thus may be
+   reused.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.4
+      NAME 'pwdInHistory'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.5  pwdCheckQuality
+
+   {TODO: Consider changing the syntax to OID.  Each OID will list a
+   quality rule (like min len, # of special characters, etc).  These
+   rules can be specified outside this document.}
+
+   {TODO: Note that even though this is meant to be a check that happens
+   during password modification, it may also be allowed to happen during
+   authN.  This is useful for situations where the password is encrypted
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 12]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   when modified, but decrypted when used to authN.}
+
+   This attribute indicates how the password quality will be verified
+   while being modified or added.  If this attribute is not present, or
+   if the value is '0', quality checking will not be enforced.  A value
+   of '1' indicates that the server will check the quality, and if the
+   server is unable to check it (due to a hashed password or other
+   reasons) it will be accepted.  A value of '2' indicates that the
+   server will check the quality, and if the server is unable to verify
+   it, it will return an error refusing the password.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.5
+      NAME 'pwdCheckQuality'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.6  pwdMinLength
+
+   When quality checking is enabled, this attribute holds the minimum
+   number of characters that must be used in a password.  If this
+   attribute is not present, no minimum password length will be
+   enforced.  If the server is unable to check the length (due to a
+   hashed password or otherwise), the server will, depending on the
+   value of the pwdCheckQuality attribute, either accept the password
+   without checking it ('0' or '1') or refuse it ('2').
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.6
+      NAME 'pwdMinLength'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.7  pwdExpireWarning
+
+   This attribute specifies the maximum number of seconds before a
+   password is due to expire that expiration warning messages will be
+   returned to an authenticating user.
+
+   If this attribute is not present, or if the value is 0 no warnings
+   will be returned.  If not 0, the value must be smaller than the value
+   of the pwdMaxAge attribute.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.7
+      NAME 'pwdExpireWarning'
+      EQUALITY integerMatch
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 13]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+       SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.8  pwdGraceAuthNLimit
+
+   This attribute specifies the number of times an expired password can
+   be used to authenticate.  If this attribute is not present or if the
+   value is 0, authentication will fail.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.8
+      NAME 'pwdGraceAuthNLimit'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.9  pwdLockout
+
+   This attribute indicates, when its value is "TRUE", that the password
+   may not be used to authenticate after a specified number of
+   consecutive failed bind attempts.  The maximum number of consecutive
+   failed bind attempts is specified in pwdMaxFailure.
+
+   If this attribute is not present, or if the value is "FALSE", the
+   password may be used to authenticate when the number of failed bind
+   attempts has been reached.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.9
+      NAME 'pwdLockout'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+
+5.2.10  pwdLockoutDuration
+
+   This attribute holds the number of seconds that the password cannot
+   be used to authenticate due to too many failed bind attempts.  If
+   this attribute is not present, or if the value is 0 the password
+   cannot be used to authenticate until reset by a password
+   administrator.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.10
+      NAME 'pwdLockoutDuration'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 14]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+5.2.11  pwdMaxFailure
+
+This attribute specifies the number of consecutive failed bind attempts after 
+which the password may not be used to authenticate. If this attribute is not 
+present, or if the value is 0, this policy is not checked, and the value of 
+pwdLockout will be ignored. If pwdMaxTotalAttempts is present and non 0 then 
+pwdMaxFailure will represent the maximum number of unique password attempts 
+allowed. If pwdMaxTotalAttempts is either not present or is 0 then the meaning 
+of this attribute is unchanged.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.11
+      NAME 'pwdMaxFailure'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.12  pwdFailureCountInterval
+
+This attribute holds the number of seconds after which the password failures 
+are purged from the failure counter, even though no successful authentication 
+occurred.
+
+If this attribute is not present, or if its value is 0, the failure counter is 
+only reset by a successful authentication.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.12
+      NAME 'pwdFailureCountInterval'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+5.2.13  pwdMustChange
+
+   This attribute specifies with a value of "TRUE" that users must
+   change their passwords when they first bind to the directory after a
+   password is set or reset by a password administrator.  If this
+   attribute is not present, or if the value is "FALSE", users are not
+   required to change their password upon binding after the password
+   administrator sets or resets the password.  This attribute is not set
+   due to any actions specified by this document, it is typically set by
+   a password administrator after resetting a user's password.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.13
+      NAME 'pwdMustChange'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 15]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+5.2.14  pwdAllowUserChange
+
+   This attribute indicates whether users can change their own
+   passwords, although the change operation is still subject to access
+   control.  If this attribute is not present, a value of "TRUE" is
+   assumed.  This attribute is intended to be used in the absense of an
+   access control mechanism.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.14
+      NAME 'pwdAllowUserChange'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+
+5.2.15  pwdSafeModify
+
+   This attribute specifies whether or not the existing password must be
+   sent along with the new password when being changed.  If this
+   attribute is not present, a "FALSE" value is assumed.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.15
+      NAME 'pwdSafeModify'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+5.2.16  pwdMaxTotalAttempts
+
+This attribute may take one of three values:
+a.	Zero (0) This indicates that repeat password checking is not invoked. The 
+value 0 is defaulted if the attribute is not present.
+b.	A positive number defines the maximum number of failed bind attempts using 
+any combination of repeat and unique passwords after which any password may 
+not be used to authenticate. 
+c.	1 (minus 1). This indicates that repeat password detection features are 
+required but an unlimited number of repeat password attempts are allowed.
+When repeat password detection is invoked (pwdMaxTotalAttempts is either 1 or 
+positive) each failed password attempt is evaluated against the list maintained 
+in pwdUnigueAttempts and if not present will be added to pwdUniqueAttempts and 
+also to pwdFailureTime. Thus the first attempt to use any password will count 
+toward the value defined in pwdMaxFailure. If the failed password is present in 
+pwdUniqueAttempts it will only increment the appropriate counter in 
+pwdUniqueAttempts if pwdMaxTotalAttempts is positive. When the sum of all non-
+expired counts in pwdUniqueAttempts equals pwdMaxTotalAttempts then the action 
+defined by pwdLockout is invoked. If this attribute is not present (defaults to 
+0), or if the value is 0, no repeat password detection is invoked and any 
+failed password attempt (whether repeat or unique) will count toward the value 
+defined by pwdMaxFailure. If the value is set to -1 then an unlimited number of 
+repeat password attempts are allowed.
+
+      (1.3.6.1.4.1.13769.1.3.1
+      NAME 'pwdMaxTotalAttempts'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+5.3  Attribute Types for Password Policy State Information
+
+   Password policy state information must be maintained for each user.
+   The information is located in each user entry as a set of operational
+   attributes.  These operational attributes are: pwdChangedTime,
+   pwdAccountLockedTime, pwdFailureTime, pwdHistory, pwdGraceUseTime,
+   pwdReset, pwdPolicySubEntry.
+
+5.3.1  Password Policy State Attribute Option
+
+   Since the password policy could apply to several attributes used to
+   store passwords, each of the above operational attributes must have
+   an option to specify which pwdAttribute it applies to.  The password
+   policy option is defined as the following:
+
+   pwd-<passwordAttribute>
+
+   where passwordAttribute a string following the OID syntax
+   (1.3.6.1.4.1.1466.115.121.1.38).  The attribute type descriptor
+   (short name) MUST be used.
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 16]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   For example, if the pwdPolicy object has for pwdAttribute
+   "userPassword" then the pwdChangedTime operational attribute, in a
+   user entry, will be:
+
+   pwdChangedTime;pwd-userPassword: 20000103121520Z
+
+   This attribute option follows sub-typing semantics.  If a client
+   requests a password policy state attribute to be returned in a search
+   operation, and does not specify an option, all subtypes of that
+   policy state attribute are returned.
+
+5.3.2  pwdChangedTime
+
+   This attribute specifies the last time the entry's password was
+   changed.  This is used by the password expiration policy.  If this
+   attribute does not exist, the password will never expire.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.16
+      NAME 'pwdChangedTime'
+      DESC 'The time the password was last changed'
+      EQUALITY generalizedTimeMatch
+      ORDERING generalizedTimeOrderingMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+      SINGLE-VALUE
+      NO-USER-MODIFICATION
+      USAGE directoryOperation )
+
+
+5.3.3  pwdAccountLockedTime
+
+   This attribute holds the time that the user's account was locked.  A
+   locked account means that the password may no longer be used to
+   authenticate.  A 000001010000Z value means that the account has been
+   locked permanently, and that only a password administrator can unlock
+   the account.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.17
+      NAME 'pwdAccountLockedTime'
+      DESC 'The time an user account was locked'
+      EQUALITY generalizedTimeMatch
+      ORDERING generalizedTimeOrderingMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+      SINGLE-VALUE
+      NO-USER-MODIFICATION
+      USAGE directoryOperation )
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 17]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+5.3.4  pwdFailureTime
+
+   This attribute holds the timestamps of the consecutive authentication
+   failures.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.19
+      NAME 'pwdFailureTime'
+      DESC 'The timestamps of the last consecutive authentication
+      failures'
+      EQUALITY generalizedTimeMatch
+      ORDERING generalizedTimeOrderingMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+      NO-USER-MODIFICATION
+      USAGE directoryOperation )
+
+Implementer's note: When an account is locked this attribute is not added to 
+until either pwdLockoutDuration has expired or the account has been reset by an 
+administrator. The values of the attribute are purged on successful 
+authentication or expiry of pwdFailureCountInterval.
+5.3.5  pwdHistory
+
+   This attribute holds a history of previously used passwords.  Values
+   of this attribute are transmitted in string format as given by the
+   following ABNF:
+
+   pwdHistory = time "#" syntaxOID "#" length "#" data
+
+   time       = <generalizedTimeString as specified in 6.14
+                 of [RFC2252]>
+
+   syntaxOID  = numericoid    ; the string representation of the
+                              ; dotted-decimal OID that defines the
+                              ; syntax used to store the password.
+                              ; numericoid is described in 4.1
+                              ; of [RFC2252].
+
+   length     = numericstring ; the number of octets in data.
+                              ; numericstring is described in 4.1
+                              ; of [RFC2252].
+
+   data       = <octets representing the password in the format
+                 specified by syntaxOID>.
+
+   This format allows the server to store, and transmit a history of
+   passwords that have been used.  In order for equality matching to
+   function properly, the time field needs to adhere to a consistent
+   format.  For this purpose, the time field MUST be in GMT format.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.20
+      NAME 'pwdHistory'
+      DESC 'The history of user s passwords'
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 18]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+      EQUALITY octetStringMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+      NO-USER-MODIFICATION
+      USAGE directoryOperation )
+
+
+5.3.6  pwdGraceUseTime
+
+   This attribute holds the timestamps of grace authentications after a
+   password has expired.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.21
+      NAME 'pwdGraceUseTime'
+      DESC 'The timestamps of the grace authentication after the
+      password has expired'
+      EQUALITY generalizedTimeMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+      NO-USER-MODIFICATION
+      USAGE directoryOperation )
+
+
+5.3.7  pwdReset
+
+   This attribute holds a flag to indicate (when TRUE) that the password
+   has been updated by the password administrator and must be changed by
+   the user.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.22
+      NAME 'pwdReset'
+      DESC 'The indication that the password has been reset'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE
+      USAGE directoryOperation )
+
+
+5.3.8  pwdPolicySubentry
+
+   This attribute points to the pwdPolicy subentry in effect for this
+   object.
+
+      ( 1.3.6.1.4.1.42.2.27.8.1.23
+      NAME 'pwdPolicySubentry'
+      DESC 'The pwdPolicy subentry in effect for this object'
+      EQUALITY distinguishedNameMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+      SINGLE-VALUE
+      NO-USER-MODIFICATION
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 19]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+      USAGE directoryOperation )
+
+5.3.10  pwdUniqueAttempts
+
+This attribute holds a history of previously failed passwords attempts up to 
+the limit defined by pwdMaxFailure. Values of this attribute are transmitted in 
+string format as given by the following ABNF:
+
+   pwdUniqueAttempts = time "#" count "#" length "#" data
+
+   time       = <generalizedTimeString as specified in 6.14
+                 of [RFC2252]>.
+
+   count  = Integer    ;       the count of uses of this password
+                              ;Integer is described in 6.16 of [RFC2252]
+
+   length     = numericstring ; the number of octets in data.
+                              ; numericstring is described in 4.1
+                              ; of [RFC2252].
+
+   data       = <octets representing the password in the default hash format
+              for the server
+
+In order for equality matching to function properly, the time field needs to 
+adhere to a consistent format.  For this purpose, the time field MUST be in GMT 
+(UCT) format. The time field is set only when the attribute is initially added.
+
+      (1.3.6.1.4.1.13769.1.3.2
+      NAME 'pwdUniqueAttempts'
+      DESC 'History of unique passwords attempts'
+      EQUALITY octetStringMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+      NO-USER-MODIFICATION
+      USAGE directoryOperation )
+When a failed password attempt occurs and the value of pwdMaxTotalAttempts is 
+non 0 then pwdUniqueAttempts is searched for a match. If a match occurs then 
+the count field of this entry is incremented only if the value of 
+pwdMaxTotalAttempts is positive. If no match occurs then an entry is made in 
+pwdFailureTime and in pwdUniqueAttempts (with a count field value of 1). 
+If pwdMaxTotalAttempts is positive then when the sum of the count field values 
+of all items in pwdUniqueAttempts equals pwdMaxTotalAttempts the action taken 
+is defined by pwdLockout.
+Implementer's note: In order to minimize the exposure of such passwords entries 
+in pwdAttempts all entries will be removed when the user successfully 
+authenticates, the account is locked or reset by an administrator and 
+individual items are inspected on each password attempt and removed on expiry 
+of pwdFailureCountInterval if pwdMaxTotalAttempts is positive.
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 20]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+6.  Controls used for Password Policy
+
+   This section details the controls used while enforcing password
+   policy.  A request control is defined that is sent by a client with a
+   request operation in order to elicit a response control.  The
+   response control contains various warnings and errors associated with
+   password policy.
+
+   {TODO: add a note about advertisement and discovery}
+
+6.1  Request Control
+
+   This control MAY be sent with any LDAP request message in order to
+   convey to the server that this client is aware of, and can process
+   the response control described in this document.  When a server
+   receives this control, it will return the response control when
+   appropriate and with the proper data.
+
+   The controlType is 1.3.6.1.4.1.42.2.27.8.5.1 and the criticality may
+   be TRUE or FALSE.  There is no controlValue.
+
+6.2  Response Control
+
+   If the client has sent a passwordPolicyRequest control, the server
+   (when solicited by the inclusion of the request control) sends this
+   control with the following operation responses: bindResponse,
+   modifyResponse, addResponse, compareResponse and possibly
+   extendedResponse, to inform of various conditions, and MAY be sent
+   with other operations (in the case of the changeAfterReset error).
+   The controlType is 1.3.6.1.4.1.42.2.27.8.5.1 and the controlValue is
+   the BER encoding of the following type:
+
+   PasswordPolicyResponseValue ::= SEQUENCE {
+      warning [0] CHOICE {
+         timeBeforeExpiration [0] INTEGER (0 .. maxInt),
+         graceAuthNsRemaining [1] INTEGER (0 .. maxInt) } OPTIONAL,
+      error   [1] ENUMERATED {
+         passwordExpired             (0),
+         accountLocked               (1),
+         changeAfterReset            (2),
+         passwordModNotAllowed       (3),
+         mustSupplyOldPassword       (4),
+         insufficientPasswordQuality (5),
+         passwordTooShort            (6),
+         passwordTooYoung            (7),
+         passwordInHistory           (8) } OPTIONAL }
+
+   The timeBeforeExpiration warning specifies the number of seconds
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 21]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   before a password will expire.  The graceAuthNsRemaining warning
+   specifies the remaining number of times a user will be allowed to
+   authenticate with an expired password.  The passwordExpired error
+   signifies that the password has expired and must be reset.  The
+   changeAfterReset error signifies that the password must be changed
+   before the user will be allowed to perform any operation other than
+   bind and modify.  The passwordModNotAllowed error is set when a user
+   is restricted from changing her password.  The
+   insufficientPasswordQuality error is set when a password doesn't pass
+   quality checking.  The passwordTooYoung error is set if the age of
+   the password to be modified is not yet old enough.
+
+   Typically, only either a warning or an error will be encoded though
+   there may be exceptions.  For example, if the user is required to
+   change a password after the password administrator set it, and the
+   password will expire in a short amount of time, the control may
+   include the timeBeforeExpiration warning and the changeAfterReset
+   error.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 22]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+7.  Policy Decision Points
+
+   Following are a number of procedures used to make policy decisions.
+   These procedures are typically performed by the server while
+   processing an operation.
+
+   The following sections contain detailed instructions that refer to
+   attributes of the pwdPolicy object class.  When doing so, the
+   attribute of the pwdPolicy object that governs the entry being
+   discussed is implied.
+
+7.1  Locked Account Check
+
+   A status of true is returned to indicate that the account is locked
+   if any of these conditions are met:
+
+   o  The value of the pwdAccountLockedTime attribute is 000001010000Z.
+
+   o  The current time is less than the value of the
+      pwdAccountLockedTime attribute added to the value of the
+      pwdLockoutDuration.
+
+   Otherwise a status of false is returned.
+
+7.2  Password Must be Changed Now Check
+
+   A status of true is returned to indicate that the account is locked
+   if all of these conditions are met:
+
+      The pwdMustChange attribute is set to TRUE.
+
+      The pwdReset attribute is set to TRUE.
+
+   Otherwise a status of false is returned.
+
+7.3  Password Expiration Check
+
+   A status of true is returned indicating that the password has expired
+   if the current time minus the value of pwdChangedTime is greater than
+   the value of the pwdMaxAge.
+
+   Otherwise, a status of false is returned.
+
+7.4  Remaining Grace AuthN Check
+
+   If the pwdGraceUseTime attribute is present, the number of values in
+   that attribute subtracted from the value of pwdGraceAuthNLimit is
+   returned.  Otherwise zero is returned.  A positive result specifies
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 23]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   the number of remaining grace authentications.
+
+7.5  Time Before Expiration Check
+
+   If the pwdExpireWarning attribute is not present a zero status is
+   returned.  Otherwise the following steps are followed:
+
+   Subtract the time stored in pwdChangedTime from the current time to
+   arrive at the password's age.  If the password's age is greater than
+   than the value of the pwdMaxAge attribute, a zero status is returned.
+   Subtract the value of the pwdExpireWarning attribute from the value
+   of the pwdMaxAge attribute to arrive at the warning age.  If the
+   password's age is equal to or greater than the warning age, the value
+   of pwdMaxAge minus the password's age is returned.
+
+7.6  Intruder Detection Check
+
+   A status of true indicating that an intruder has been detected is
+   returned if the following conditions are met:
+
+      The pwdLockout attribute is TRUE.
+
+      The number of values in the pwdFailureTime attribute that are
+      younger than pwdFailureCountInterval is greater or equal to the
+      pwdMaxFailure attribute.
+
+   Otherwise a status of false is returned.
+
+   While performing this check, values of pwdFailureTime that are old by
+   more than pwdFailureCountInterval are purged and not counted.
+
+7.7  Password Too Young Check
+
+   A status of true indicating that not enough time has passed since the
+   password was last updated is returned if:
+
+      The value of pwdMinAge is non-zero and pwdChangedTime is present.
+
+      The value of pwdMinAge is greater than the current time minus the
+      value of pwdChangedTime.
+
+   Otherwise a false status is returned.
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 24]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+8.  Server Policy Enforcement Points
+
+   The server SHOULD enforce that the password attribute subject to a
+   password policy as defined in this document, contains one and only
+   one password value.
+
+   The scenarios in the following operations assume that the client has
+   attached a passwordPolicyRequest control to the request message of
+   the operation.  In the event that the passwordPolicyRequest control
+   was not sent, no passwordPolicyResponse control is returned.  All
+   other instructions remain the same.
+
+   For successfuly completed operations, unless otherwise stated, no
+   passwordPolicyResponse control is returned.
+
+8.1  Password-based Authentication
+
+   This section contains the policy enforcement rules and policy data
+   updates used while validating a password.  Operations that validate
+   passwords include, but are not limited to, the Bind operation where
+   the simple choice specifies a password, and the compare operation
+   where the attribute being compared holds a password.  Note that while
+   the compare operation does not authenticate a user to the LDAP
+   server, it may be used by an external application for purposes of
+   authentication.
+
+8.1.1  Fail if the account is locked
+
+   If the account is locked as specified in Section 7.1, the server
+   fails the operation with an appropriate resultCode (i.e.
+   invalidCredentials (49) in the case of a bind operation, compareFalse
+   (5) in the case of a compare operation, etc.).  The server MAY set
+   the error: accountLocked (1) in the passwordPolicyResponse in the
+   controls field of the message.
+
+8.1.2  Validated Password Procedures
+
+   If the validation operation indicates that the password validated,
+   these procedures are followed in order:
+
+8.1.2.1  Policy state updates
+
+   Delete the pwdFailureTime and pwdAccountLockedTime attributes.
+
+8.1.2.2  Password must be changed now
+
+   If the decision in Section 7.2 returns true, the server sends to the
+   client a response with an appropriate successful resultCode (i.e.
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 25]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   success (0), compareTrue (6), etc.), and includes the
+   passwordPolicyResponse in the controls field of the bindResponse
+   message with the warning: changeAfterReset specified.
+
+   For bind, the server MUST then disallow all operations issued by this
+   user except modify password, bind, unbind, abandon and StartTLS
+   extended operation.
+
+8.1.2.3  Expired password
+
+   If the password has expired as per Section 7.3, the server either
+   returns a success or failure based on the state of grace
+   authentications.
+
+8.1.2.3.1  Remaining Grace Authentications
+
+   If there are remaining grace authentications as per Section 7.4, the
+   server adds a new value with the current time in pwdGraceUseTime.
+   Then it sends to the client a response with an appropriate successful
+   resultCode (i.e. success (0), compareTrue (6), etc.), and includes
+   the passwordPolicyResponse in the controls field of the response
+   message with the warning: graceAuthNsRemaining choice set to the
+   number of grace authentications left.
+
+   Implementor's note: The system time of the host machine may be more
+   granular than is needed to ensure unique values of this attribute.
+   It is recommended that a mechanism is used to ensure unique
+   generalized time values.  The fractional seconds field may be used
+   for this purpose.
+
+8.1.2.3.2  No Remaining Grace Authentications
+
+   If there are no remaining grace authentications, the server fails the
+   operation with an appropriate resultCode (invalidCredentials (49),
+   compareFalse (5), etc.), and includes the passwordPolicyResponse in
+   the controls field of the bindResponse message with the error:
+   passwordExpired (0) set.
+
+8.1.2.4  Expiration Warning
+
+   If the result of Section 7.5 is a positive number, the server sends
+   to the client a response with an appropriate successful resultCode
+   (i.e. success (0), compareTrue (6), etc.), and includes the
+   passwordPolicyResponse in the controls field of the bindResponse
+   message with the warning: timeBeforeExiration set to the value as
+   described above.  Otherwise, the server sends a successful response,
+   and omits the passwordPolicyResponse.
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 26]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+8.1.2.5  AuthN Failed Procedures
+
+   If the authentication process indicates that the password failed
+   validation due to invalid credentials, these procedures are followed:
+
+8.1.2.5.1  Policy state update
+
+   Add the current time as a value of the pwdFailureTime attribute.
+
+   Implementor's note: The system time of the host machine may be more
+   granular than is needed to ensure unique values of this attribute.
+   It is recommended that a mechanism is used to ensure unique
+   generalized time values.  The fractional seconds field may be used
+   for this purpose.
+
+8.1.2.5.2  Lock on intruder detection
+
+   If the check in Section 7.6 returns a true state, the server locks
+   the account by setting the value of the pwdAccountLockedTime
+   attribute to the current time.  After locking the account, the server
+   fails the operation with an appropriate resultCode
+   (invalidCredentials (49), compareFalse (5), etc.), and includes the
+   passwordPolicyResponse in the controls field of the message with the
+   error: accountLocked (1).
+
+8.2  Password Update Operations
+
+   Because the password is stored in an attribute, various operations
+   (like add and modify) may be used to create or update a password.
+   But some alternate mechanisms have been defined or may be defined,
+   such as the LDAP Password Modify Extended Operation [RFC3062].
+
+   While processing a password update, the server performs the following
+   steps:
+
+8.2.1  Safe Modification
+
+   If pwdSafeModify is set to TRUE and if there is an existing password
+   value, the server ensures that the password update operation includes
+   the user's existing password.
+
+   When the LDAP modify operation is used to modify a password, this is
+   done by specifying both a delete action and an add or replace action,
+   where the delete action specifies the existing password, and the add
+   or replace action specifies the new password.  Other password update
+   operations SHOULD employ a similar mechanism.  Otherwise this policy
+   will fail.
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 27]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   If the existing password is not specified, the server does not
+   process the operation and sends the appropriate response message to
+   the client with the resultCode: insufficientAccessRights (50), and
+   includes the passwordPolicyResponse in the controls field of the
+   response message with the error: mustSupplyOldPassword (4).
+
+8.2.2  Change After Reset
+
+   If the decision in Section 7.2 returns true, the server ensures that
+   the password update operation contains no modifications other than
+   the modification of the password attribute.  If other modifications
+   exist, the server sends a response message to the client with the
+   resultCode: insufficientAccessRights (50), and includes the
+   passwordPolicyResponse in the controls field of the response message
+   with the error: changeAfterReset (2).
+
+8.2.3  Rights Check
+
+   Check to see whether the bound identity has sufficient rights to
+   update the password.  If the bound identity is a user changing its
+   own password, this MAY be done by checking the pwdAllowUserChange
+   attribute or using an access control mechanism.  The determination of
+   this is implementation specific.  If the user is not allowed to
+   update her password, the server sends a response message to the
+   client with the resultCode: insufficientAccessRights (50), and
+   includes the passwordPolicyResponse in the controls field of the
+   response message with the error: passwordModNotAllowed (3).
+
+8.2.4  Too Early to Update
+
+   If the check in Section 7.7 results in a true status The server sends
+   a response message to the client with the resultCode:
+   constraintViolation (19), and includes the passwordPolicyResponse in
+   the controls field of the response message with the error:
+   passwordTooYoung (7).
+
+8.2.5  Password Quality
+
+   Check the value of the pwdCheckQuality attribute.  If the value is
+   non-zero, the server:
+
+   o  Ensure that the password meets the quality criteria enforced by
+      the server.  This enforcement is implementation specific.
+      If the server is unable to check the quality (due to a hashed
+      password or otherwise), the value of pwdCheckQuality is evaluated.
+      If the value is 1, operation continues.  If the value is 2, the
+      server sends a response message to the client with the resultCode:
+      constraintViolation (19), and includes the passwordPolicyResponse
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 28]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+      in the controls field of the response message with the error:
+      insufficientPasswordQuality (5).
+      If the server is able to check the password quality, and the check
+      fails, the server sends a response message to the client with the
+      resultCode: constraintViolation (19), and includes the
+      passwordPolicyResponse in the controls field of the response
+      message with the error: insufficientPasswordQuality (5).
+
+   o  checks the value of the pwdMinLength attribute.  If the value is
+      non-zero, it ensures that the new password is of at least the
+      minimum length.
+      If the server is unable to check the length (due to a hashed
+      password or otherwise), the value of pwdCheckQuality is evaluated.
+      If the value is 1, operation continues.  If the value is 2, the
+      server sends a response message to the client with the resultCode:
+      constraintViolation (19), and includes the passwordPolicyResponse
+      in the controls field of the response message with the error:
+      passwordTooShort (6).
+      If the server is able to check the password length, and the check
+      fails, the server sends a response message to the client with the
+      resultCode: constraintViolation (19), and includes the
+      passwordPolicyResponse in the controls field of the response
+      message with the error: passwordTooShort (6).
+
+
+8.2.6  Invalid Reuse
+
+   If pwdInHistory is present and its value is non-zero, the server
+   checks whether this password exists in the entry's pwdHistory
+   attribute or in the current password attribute.  If the password does
+   exist in the pwdHistory attribute or in the current password
+   attribute, the server sends a response message to the client with the
+   resultCode: constraintViolation (19), and includes the
+   passwordPolicyResponse in the controls field of the response message
+   with the error: passwordInHistory (8).
+
+8.2.7  Policy State Updates
+
+   If the steps have completed without causing an error condition, the
+   server performs the following steps in order to update the necessary
+   password policy state attributes:
+
+   If the value of either pwdMaxAge or pwdMinAge is non-zero, the server
+   updates the pwdChangedTime attribute on the entry to the current
+   time.
+
+   If the value of pwdInHistory is non-zero, the server adds the
+   previous password (if one existed) to the pwdHistory attribute.  If
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 29]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+   the number of attributes held in the pwdHistory attribute exceeds the
+   value of pwdInHistory, the server removes the oldest excess
+   passwords.
+
+   If the value the pwdMustChange is TRUE and the modification is
+   performed by a password administrator, then the pwdReset attribute is
+   set to TRUE.  Otherwise, the pwdReset is removed from the user's
+   entry if it exists.
+
+   The pwdFailureTime and pwdGraceUseTime attributes is removed from the
+   user's entry if they exist.
+
+8.3  Other Operations
+
+   For operations other than bind, password update, unbind, abandon or
+   StartTLS, if the decision in Section 7.2 returns true, the server
+   sends a response message to the client with the resultCode:
+   insufficientAccessRights (50), and includes the
+   passwordPolicyResponse in the controls field of the response message
+   with the error: changeAfterReset (2).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 30]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+9.  Client Policy Enforcement Points
+
+   These sections illustrate possible scenarios for each LDAP operation
+   and define the types of responses that identify those scenarios.
+
+   The scenarios in the following operations assume that the client
+   attached a passwordPolicyRequest control to the request message of
+   the operation, and thus may receive a passwordPolicyResponse control
+   in the response message.  In the event that the passwordPolicyRequest
+   control was not sent, no passwordPolicyResponse control is returned.
+   All other instructions remain the same.
+
+9.1  Bind Operation
+
+   For every bind response received, the client checks the resultCode of
+   the bindResponse and checks for a passwordPolicyResponse control to
+   determine if any of the following conditions are true and MAY prompt
+   the user accordingly.
+
+   o  bindResponse.resultCode = insufficientAccessRights (50),
+      passwordPolicyResponse.error = accountLocked (1): The password
+      failure limit has been reached and the account is locked.  The
+      user needs to retry later or contact the password administrator to
+      reset the password.
+
+   o  bindResponse.resultCode = success (0),
+      passwordPolicyResponse.error = changeAfterReset (2): The user is
+      binding for the first time after the password administrator set
+      the password.  In this scenario, the client SHOULD prompt the user
+      to change his password immediately.
+
+   o  bindResponse.resultCode = success (0),
+      passwordPolicyResponse.warning = graceAuthNsRemaining: The
+      password has expired but there are remaining grace
+      authentications.  The user needs to change it.
+
+   o  bindResponse.resultCode = invalidCredentials (49),
+      passwordPolicyResponse.error = passwordExpired (0): The password
+      has expired and there are no more grace authentications.  The user
+      contacts the password administrator in order to have its password
+      reset.
+
+   o  bindResponse.resultCode = success (0),
+      passwordPolicyResponse.warning = timeBeforeExpiration: The user's
+      password will expire in n number of seconds.
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 31]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+9.2  Modify Operations
+
+9.2.1  Modify Request
+
+   If the application or client encrypts the password prior to sending
+   it in a password modification operation (whether done through
+   modifyRequest or another password modification mechanism), it SHOULD
+   check the values of the pwdMinLength, and pwdCheckQuality attributes
+   and SHOULD enforce these policies.
+
+9.2.2  Modify Response
+
+   If the modifyRequest operation was used to change the password, or if
+   another mechanism is used --such as an extendedRequest-- the
+   modifyResponse or other appropriate response MAY contain information
+   pertinent to password policy.  The client checks the resultCode of
+   the response and checks for a passwordPolicyResponse control to
+   determine if any of the following conditions are true and optionally
+   notify the user of the condition.
+
+   o  <pwdModResponse>.resultCode = insufficientAccessRights (50),
+      passwordPolicyResponse.error = mustSupplyOldPassword (4): The user
+      attempted to change her password without specifying the old
+      password but the password policy requires this.
+
+   o  <pwdModResponse>.resultCode = insufficientAccessRights (50),
+      passwordPolicyResponse.error = changeAfterReset (2): The user must
+      change her password before submitting any other LDAP requests.
+
+   o  <pwdModResponse>.resultCode = insufficientAccessRights (50),
+      passwordPolicyResponse.error = passwordModNotAllowed (3): The user
+      doesn't have sufficient rights to change his password.
+
+   o  <pwdModResponse>.resultCode = constraintViolation (19),
+      passwordPolicyResponse.error = passwordTooYoung (7): It is too
+      soon after the last password modification to change the password.
+
+   o  <pwdModResponse>.resultCode = constraintViolation (19),
+      passwordPolicyResponse.error = insufficientPasswordQuality (5):
+      The password failed quality checking.
+
+   o  <pwdModResponse>.resultCode = constraintViolation (19),
+      passwordPolicyResponse.error = passwordTooShort (6): The length of
+      the password is too short.
+
+   o  <pwdModResponse>.resultCode = constraintViolation (19),
+      passwordPolicyResponse.error = passwordInHistory (8): The password
+      has already been used; the user must choose a different one.
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 32]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+9.3  Add Operation
+
+   If a password is specified in an addRequest, the client checks the
+   resultCode of the addResponse and checks for a passwordPolicyResponse
+   control to determine if any of the following conditions are true and
+   may prompt the user accordingly.
+
+   o  addResponse.resultCode = insufficientAccessRights (50),
+      passwordPolicyResponse.error = passwordModNotAllowed (3): The user
+      doesn't have sufficient rights to add this password.
+
+   o  addResponse.resultCode = constraintViolation (19),
+      passwordPolicyResponse.error = insufficientPasswordQuality (5):
+      The password failed quality checking.
+
+   o  addResponse.resultCode = constraintViolation (19),
+      passwordPolicyResponse.error = passwordTooShort (6): The length of
+      the password is too short.
+
+
+9.4  Compare Operation
+
+   When a compare operation is used to compare a password, the client
+   checks the resultCode of the compareResponse and checks for a
+   passwordPolicyResponse to determine if any of the following
+   conditions are true and MAY prompt the user accordingly.  These
+   conditions assume that the result of the comparison was true.
+
+   o  compareResponse.resultCode = compareFalse (5),
+      passwordPolicyResponse.error = accountLocked (1): The password
+      failure limit has been reached and the account is locked.  The
+      user needs to retry later or contact the password administrator to
+      reset the password.
+
+   o  compareResponse.resultCode = compareTrue (6),
+      passwordPolicyResponse.warning = graceAuthNsRemaining: The
+      password has expired but there are remaining grace
+      authentications.  The user needs to change it.
+
+   o  compareResponse.resultCode = compareFalse (5),
+      passwordPolicyResponse.error = passwordExpired (0): The password
+      has expired and there are no more grace authentications.  The user
+      must contact the password administrator to reset the password.
+
+   o  compareResponse.resultCode = compareTrue (6),
+      passwordPolicyResponse.warning = timeBeforeExpiration: The user's
+      password will expire in n number of seconds.
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 33]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+9.5  Other Operations
+
+   For operations other than bind, unbind, abandon or StartTLS, the
+   client checks the following result code and control to determine if
+   the user needs to change the password immediately.
+
+   o  <Response>.resultCode = insufficientAccessRights (50),
+      passwordPolicyResponse.error = : changeAfterReset (2)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 34]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+10.  Administration of the Password Policy
+
+   {TODO: Need to define an administrativeRole (need OID).  Need to
+   describe whether pwdPolicy admin areas can overlap}
+
+   A password policy is defined for a particular subtree of the DIT by
+   adding to an LDAP subentry whose immediate superior is the root of
+   the subtree, the pwdPolicy auxiliary object class.  The scope of the
+   password policy is defined by the SubtreeSpecification attribute of
+   the LDAP subentry as specified in [RFC3672].
+
+   It is possible to define password policies for different password
+   attributes within the same pwdPolicy entry, by specifying multiple
+   values of the pwdAttribute.  But password policies could also be in
+   separate sub entries as long as they are contained under the same
+   LDAP subentry.
+
+   Modifying the password policy MUST NOT result in any change in users'
+   entries to which the policy applies.
+
+   It SHOULD be possible to overwrite the password policy for one user
+   by defining a new policy in a subentry of the user entry.
+
+   Each object that is controlled by password policy advertises the
+   subentry that is being used to control its policy in its
+   pwdPolicySubentry attribute.  Clients wishing to examine or manage
+   password policy for an object may interrogate the pwdPolicySubentry
+   for that object in order to arrive at the proper pwdPolicy subentry.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 35]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+11.  Password Policy and Replication
+
+   {TODO: This section needs to be changed to highlight the pitfals of
+   replication, sugest some implementation choices to overcome those
+   pitfals, but remove prescriptive language relating to the update of
+   state information}
+
+   The pwdPolicy object defines the password policy for a portion of the
+   DIT and MUST be replicated on all the replicas of this subtree, as
+   any subentry would be, in order to have a consistent policy among all
+   replicated servers.
+
+   The elements of the password policy that are related to the users are
+   stored in the entry themselves as operational attributes.  As these
+   attributes are subject to modifications even on a read-only replica,
+   replicating them must be carefully considered.
+
+   The pwdChangedTime attribute MUST be replicated on all replicas, to
+   allow expiration of the password.
+
+   The pwdReset attribute MUST be replicated on all replicas, to deny
+   access to operations other than bind and modify password.
+
+   The pwdHistory attribute MUST be replicated to writable replicas.  It
+   doesn't have to be replicated to a read-only replica, since the
+   password will never be directly modified on this server.
+
+The pwdAccountLockedTime, pwdFailureTime pwdUniqueAttempts and pwdGraceUseTime, 
+attributes MUST be replicated to writable replicas, making the password policy 
+global for all servers.  When the user entry is replicated to a read-only 
+replica, these attributes SHOULD NOT be replicated.  This means that the number 
+of failures, of grace authentications and the locking will take place on each 
+replicated server.  For example, the effective number of failed attempts on a 
+user password will be N x M (where N is the number of servers and M the value 
+of pwdMaxFailure attribute).  Replicating these attributes to a read-only 
+replica MAY reduce the number of tries globally but MAY also introduce some 
+inconstancies in the way the password policy is applied.
+
+Servers participating in a loosely consistent multi-master replication 
+agreement SHOULD employ a mechanism which ensures uniqueness of values when 
+populating the attributes pwdFailureTime, pwdUniqueAttempts and 
+pwdGraceUseTime.  The method of achieving this is a local matter and may 
+consist of using a single authoritative source for the generation of unique 
+time values, or may consist of the use of the fractional seconds part to hold a 
+replica identifier.
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 36]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+12.  Security Considerations
+
+   This document defines a set of rules to implement in an LDAP server,
+   in order to mitigate some of the security risks associated with the
+   use of passwords and to make it difficult for password cracking
+   programs to break into directories.
+
+   Authentication with a password MUST follow the recommendations made
+   in [RFC2829].
+
+   Modifications of passwords SHOULD only occur when the connection is
+   protected with confidentiality and secure authentication.
+
+   Access controls SHOULD be used to restrict access to the password
+   policy attributes.  The attributes defined to maintain the password
+   policy state information SHOULD only be modifiable by the password
+   administrator or higher authority.  The pwdHistory attribute MUST be
+   subject to the same level of access control as the attrbute holding
+   the password.
+
+   As it is possible to define a password policy for one specific user
+   by adding a subentry immediately under the user's entry, Access
+   Controls SHOULD be used to restrict the use of the pwdPolicy object
+   class or the LDAP subentry object class.
+
+   When the intruder detection password policy is enforced, the LDAP
+   directory is subject to a denial of service attack.  A malicious user
+   could deliberately lock out one specific user's account (or all of
+   them) by sending bind requests with wrong passwords.  There is no way
+   to protect against this kind of attack.  The LDAP directory server
+   SHOULD log as much information as it can (such as client IP address)
+   whenever an account is locked, in order to be able to identify the
+   origin of the attack.  Denying anonymous access to the LDAP directory
+   is also a way to restrict this kind of attack.
+
+Generation of false-positive account lockout conditions, caused by the use of 
+cached expired passwords among other conditions has a potential negative effect 
+on security. Turning what should be an serious event (account lockout) into an 
+annoying administrative function an thereby potentially allowing genuine 
+malicious lockouts caused by dictionary attacks to be lost in sheer volume. The 
+repeat password detection granularity should assist in minimizing false-
+positives lockouts.
+
+The repeat password detection process relies on storing password attempts (in 
+pwdUniqueAttempts in a hashed format). While by definition these are failed 
+passwords and should thus represent no security risk even if a malicious third 
+party discovers them they may however lead to analysis of password construction 
+methods making a subsequent dictionary attack more likely to succeed (this 
+weakeness is also true for pwdHistory). Additionally a user may have 
+inadvertently used a favorite password during an authentication attempt thus 
+making discovery by a malicious third party potentially more serious. The third 
+party however would have to find the account to which this password was 
+applicable before it was useful.
+
+Systems which invoke repeat password detection with pwdMaxTotalAttempts set to 
+1 (allowing an unlimited number of repeat password attempts) may be subjected 
+to Denial of Service attacks by a malicious third party using repeated retries 
+of any arbitrary password.
+
+   Returning certain status codes (such as passwordPolicyResponse.error
+   = accountLocked) allows a denial of service attacker to know that it
+   has successfully denied service to an account.  Servers SHOULD
+   implement additional checks which return the same status when it is
+   sensed that some number of failed authentication requests has occured
+   on a single connection, or from a client address.  Server
+   implementors are encouraged to invent other checks similar to this in
+   order to thwart this type of DoS attack.
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 37]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+13.  IANA Considerations
+
+   <<<TBD>>>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 38]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+14.  Acknowledgement
+
+   This document is based in part on prior work done by Valerie Chu from
+   Netscape Communications Corp, published as
+   draft-vchu-ldap-pwd-policy-00.txt (December 1998).  Prasanta Behera
+   participated in early revisions of this document.
+
+15.  Normative References
+
+   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
+              Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+   [RFC2195]  Klensin, J., Catoe, R., and P. Krumviede, "IMAP/POP
+              AUTHorize Extension for Simple Challenge/Response",
+              RFC 2195, September 1997.
+
+   [RFC2222]  Myers, J., "Simple Authentication and Security Layer
+              (SASL)", RFC 2222, October 1997.
+
+   [RFC2251]  Wahl, M., Howes, T., and S. Kille, "Lightweight Directory
+              Access Protocol (v3)", RFC 2251, December 1997.
+
+   [RFC2252]  Wahl, M., Coulbeck, A., Howes, T., and S. Kille,
+              "Lightweight Directory Access Protocol (v3): Attribute
+              Syntax Definitions", RFC 2252, December 1997.
+
+   [RFC2829]  Wahl, M., Alvestrand, H., Hodges, J., and R. Morgan,
+              "Authentication Methods for LDAP", RFC 2829, May 2000.
+
+   [RFC2831]  Leach, P. and C. Newman, "Using Digest Authentication as a
+              SASL Mechanism", RFC 2831, May 2000.
+
+   [RFC3062]  Zeilenga, K., "LDAP Password Modify Extended Operation",
+              RFC 3062, February 2001.
+
+   [RFC3383]  Zeilenga, K., "Internet Assigned Numbers Authority (IANA)
+              Considerations for the Lightweight Directory Access
+              Protocol (LDAP)", BCP 64, RFC 3383, September 2002.
+
+   [RFC3672]  Zeilenga, K., "Subentries in the Lightweight Directory
+              Access Protocol (LDAP)", RFC 3672, December 2003.
+
+   [X680]     International Telecommunications Union, "Abstract Syntax
+              Notation One (ASN.1): Specification of basic notation",
+              ITU-T Recommendation X.680, July 2002.
+
+   [X690]     International Telecommunications Union, "Information
+              Technology - ASN.1 encoding rules: Specification of Basic
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 39]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+              Encoding Rules (BER),  Canonical Encoding Rules (CER) and
+              Distinguished Encoding Rules (DER)", ITU-T Recommendation
+              X.690, July 2002.
+
+
+Authors' Addresses
+
+   Jim Sermersheim
+   Novell, Inc
+   1800 South Novell Place
+   Provo, Utah  84606
+   USA
+
+   Phone: +1 801 861-3088
+   Email: jimse@novell.com
+
+
+   Ludovic Poitou
+   Sun Microsystems
+   180, Avenue de l'Europe
+   Zirst de Montbonnot, 38334 Saint Ismier cedex
+   France
+
+   Phone: +33 476 188 212
+   Email: ludovic.poitou@sun.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 40]
+
+Internet-Draft    Password Policy for LDAP Directories         July 2005
+
+
+Intellectual Property Statement
+
+   The IETF takes no position regarding the validity or scope of any
+   Intellectual Property Rights or other rights that might be claimed to
+   pertain to the implementation or use of the technology described in
+   this document or the extent to which any license under such rights
+   might or might not be available; nor does it represent that it has
+   made any independent effort to identify any such rights.  Information
+   on the procedures with respect to rights in RFC documents can be
+   found in BCP 78 and BCP 79.
+
+   Copies of IPR disclosures made to the IETF Secretariat and any
+   assurances of licenses to be made available, or the result of an
+   attempt made to obtain a general license or permission for the use of
+   such proprietary rights by implementers or users of this
+   specification can be obtained from the IETF on-line IPR repository at
+   http://www.ietf.org/ipr.
+
+   The IETF invites any interested party to bring to its attention any
+   copyrights, patents or patent applications, or other proprietary
+   rights that may cover technology that may be required to implement
+   this standard.  Please address the information to the IETF at
+   ietf-ipr@ietf.org.
+
+
+Disclaimer of Validity
+
+   This document and the information contained herein are provided on an
+   "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
+   OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
+   ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
+   INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+   INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+   WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+
+Copyright Statement
+
+   Copyright (C) The Internet Society (2005).  This document is subject
+   to the rights, licenses and restrictions contained in BCP 78, and
+   except as set forth therein, the authors retain all their rights.
+
+
+Acknowledgment
+
+   Funding for the RFC Editor function is currently provided by the
+   Internet Society.
+
+
+
+
+Sermersheim & Poitou    Expires January 18, 2006               [Page 41]
+
+
+
new file mode 100644
--- /dev/null
+++ b/ppolicy.c
@@ -0,0 +1,2624 @@
+/* $OpenLDAP: pkg/ldap/servers/slapd/overlays/ppolicy.c,v 1.75.2.14 2008/07/10 00:55:07 quanah Exp $ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2008 The OpenLDAP Foundation.
+ * Portions Copyright 2004-2005 Howard Chu, Symas Corporation.
+ * Portions Copyright 2004 Hewlett-Packard Company.
+ * 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>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Howard Chu for inclusion in
+ * OpenLDAP Software, based on prior work by Neil Dunbar (HP).
+ * This work was sponsored by the Hewlett-Packard Company.
+ * April 2009 Enhanced to allow for multiple attempts with repeated passwords 
+ * a. code written by Ron Aitchison
+ * b. work sponsored by Mozilla Corporation
+ * v.0.2 - 2.4.11
+*/
+
+#include "portable.h"
+
+/* This file implements "Password Policy for LDAP Directories",
+ * based on draft behera-ldap-password-policy-09
+ */
+
+#ifdef SLAPD_OVER_PPOLICY
+
+#include <ldap.h>
+#include "lutil.h"
+#include "slap.h"
+#ifdef SLAPD_MODULES
+#define LIBLTDL_DLL_IMPORT	/* Win32: don't re-export libltdl's symbols */
+#include <ltdl.h>
+#endif
+#include <ac/errno.h>
+#include <ac/time.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include "config.h"
+
+#ifndef MODULE_NAME_SZ
+#define MODULE_NAME_SZ 256
+#endif
+
+/* Per-instance configuration information */
+typedef struct pp_info {
+	struct berval def_policy;	/* DN of default policy subentry */
+	int use_lockout;		/* send AccountLocked result? */
+	int hash_passwords;		/* transparently hash cleartext pwds */
+} pp_info;
+
+/* Our per-connection info - note, it is not per-instance, it is 
+ * used by all instances
+ */
+typedef struct pw_conn {
+	struct berval dn;	/* DN of restricted user */
+} pw_conn;
+
+static pw_conn *pwcons;
+static int ppolicy_cid;
+static int ov_count;
+
+typedef struct pass_policy {
+	AttributeDescription *ad; /* attribute to which the policy applies */
+	int pwdMinAge; /* minimum time (seconds) until passwd can change */
+	int pwdMaxAge; /* time in seconds until pwd will expire after change */
+	int pwdInHistory; /* number of previous passwords kept */
+	int pwdCheckQuality; /* 0 = don't check quality, 1 = check if possible,
+						   2 = check mandatory; fail if not possible */
+	int pwdMinLength; /* minimum number of chars in password */
+	int pwdExpireWarning; /* number of seconds that warning controls are
+							sent before a password expires */
+	int pwdGraceAuthNLimit; /* number of times you can log in with an
+							expired password */
+	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 pwdFailureCountInterval; /* number of seconds before failure
+									counts are zeroed */
+	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
+								1 = users can change them */
+	int pwdSafeModify; /* 0 = old password doesn't need to come
+								with password change request
+							1 = password change must supply existing pwd */
+    int pwdMaxTotalAttempts ; /* RGFA number of failed binds including repeats before lockout
+							  0 = (default) no repeat checking (current behaviour)
+                              <0 = unlimited
+							  >0 = defines the number of repeats allowed */
+	char pwdCheckModule[MODULE_NAME_SZ]; /* name of module to dynamically
+										    load to check password */
+} PassPolicy;
+
+typedef struct pw_hist {
+	time_t t;	/* timestamp of history entry */
+	struct berval pw;	/* old password hash */
+	struct berval bv;	/* text of entire entry */
+	struct pw_hist *next;
+} pw_hist;
+/* RGFA */
+typedef struct pw_unique {
+	time_t t;	/* timestamp of unique entry */
+	int    count;		/* usage counter */
+	struct berval pw;	/* unique password hash */
+	struct pw_unique *next;
+} pw_unique;
+
+/* Operational attributes - RGFA */
+static AttributeDescription *ad_pwdChangedTime, *ad_pwdAccountLockedTime,
+	*ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset, *ad_pwdUniqueAttempts,
+	*ad_pwdPolicySubentry;
+
+static struct schema_info {
+	char *def;
+	AttributeDescription **ad;
+} pwd_OpSchema[] = {
+	{	"( 1.3.6.1.4.1.42.2.27.8.1.16 "
+		"NAME ( 'pwdChangedTime' ) "
+		"DESC 'The time the password was last changed' "
+		"EQUALITY generalizedTimeMatch "
+		"ORDERING generalizedTimeOrderingMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+		"SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+		&ad_pwdChangedTime },
+	{	"( 1.3.6.1.4.1.42.2.27.8.1.17 "
+		"NAME ( 'pwdAccountLockedTime' ) "
+		"DESC 'The time an user account was locked' "
+		"EQUALITY generalizedTimeMatch "
+		"ORDERING generalizedTimeOrderingMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+		"SINGLE-VALUE "
+#if 0
+		/* Not until Relax control is released */
+		"NO-USER-MODIFICATION "
+#endif
+		"USAGE directoryOperation )",
+		&ad_pwdAccountLockedTime },
+	{	"( 1.3.6.1.4.1.42.2.27.8.1.19 "
+		"NAME ( 'pwdFailureTime' ) "
+		"DESC 'The timestamps of the last consecutive authentication failures' "
+		"EQUALITY generalizedTimeMatch "
+		"ORDERING generalizedTimeOrderingMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+		"NO-USER-MODIFICATION USAGE directoryOperation )",
+		&ad_pwdFailureTime },
+	{	"( 1.3.6.1.4.1.42.2.27.8.1.20 "
+		"NAME ( 'pwdHistory' ) "
+		"DESC 'The history of users passwords' "
+		"EQUALITY octetStringMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+		"NO-USER-MODIFICATION USAGE directoryOperation )",
+		&ad_pwdHistory },
+	{	"( 1.3.6.1.4.1.42.2.27.8.1.21 "
+		"NAME ( 'pwdGraceUseTime' ) "
+		"DESC 'The timestamps of the grace login once the password has expired' "
+		"EQUALITY generalizedTimeMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+		"NO-USER-MODIFICATION USAGE directoryOperation )",
+		&ad_pwdGraceUseTime }, 
+	{	"( 1.3.6.1.4.1.42.2.27.8.1.22 "
+		"NAME ( 'pwdReset' ) "
+		"DESC 'The indication that the password has been reset' "
+		"EQUALITY booleanMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+		"SINGLE-VALUE USAGE directoryOperation )",
+		&ad_pwdReset },
+	/* RGFA */
+	{	"( 1.3.6.1.4.1.13769.1.3.2 "
+		"NAME ( 'pwdUniqueAttempts' ) "
+		"DESC 'History of unique password attempts' "
+		"EQUALITY octetStringMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+		"NO-USER-MODIFICATION USAGE directoryOperation )",
+		&ad_pwdUniqueAttempts },
+	{	"( 1.3.6.1.4.1.42.2.27.8.1.23 "
+		"NAME ( 'pwdPolicySubentry' ) "
+		"DESC 'The pwdPolicy subentry in effect for this object' "
+		"EQUALITY distinguishedNameMatch "
+		"SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+		"SINGLE-VALUE "
+#if 0
+		/* Not until Relax control is released */
+		"NO-USER-MODIFICATION "
+#endif
+		"USAGE directoryOperation )",
+		&ad_pwdPolicySubentry },
+	{ NULL, NULL }
+};
+
+/* User attributes - RGFA */
+static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdInHistory,
+	*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_pwdMaxTotalAttempts,
+	*ad_pwdAttribute;
+
+#define TAB(name)	{ #name, &ad_##name }
+
+static struct schema_info pwd_UsSchema[] = {
+	TAB(pwdAttribute),
+	TAB(pwdMinAge),
+	TAB(pwdMaxAge),
+	TAB(pwdInHistory),
+	TAB(pwdCheckQuality),
+	TAB(pwdMinLength),
+	TAB(pwdMaxFailure),
+	TAB(pwdGraceAuthNLimit),
+	TAB(pwdExpireWarning),
+	TAB(pwdLockout),
+	TAB(pwdLockoutDuration),
+	TAB(pwdFailureCountInterval),
+	TAB(pwdCheckModule),
+	TAB(pwdMustChange),
+	TAB(pwdAllowUserChange),
+	TAB(pwdSafeModify),
+	TAB(pwdMaxTotalAttempts), /* RGFA */
+	{ NULL, NULL }
+};
+
+static ldap_pvt_thread_mutex_t chk_syntax_mutex;
+
+enum {
+	PPOLICY_DEFAULT = 1,
+	PPOLICY_HASH_CLEARTEXT,
+	PPOLICY_USE_LOCKOUT
+};
+
+static ConfigDriver ppolicy_cf_default;
+
+static ConfigTable ppolicycfg[] = {
+	{ "ppolicy_default", "policyDN", 2, 2, 0,
+	  ARG_DN|ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT, ppolicy_cf_default,
+	  "( OLcfgOvAt:12.1 NAME 'olcPPolicyDefault' "
+	  "DESC 'DN of a pwdPolicy object for uncustomized objects' "
+	  "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+	{ "ppolicy_hash_cleartext", "on|off", 1, 2, 0,
+	  ARG_ON_OFF|ARG_OFFSET|PPOLICY_HASH_CLEARTEXT,
+	  (void *)offsetof(pp_info,hash_passwords),
+	  "( OLcfgOvAt:12.2 NAME 'olcPPolicyHashCleartext' "
+	  "DESC 'Hash passwords on add or modify' "
+	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+	{ "ppolicy_use_lockout", "on|off", 1, 2, 0,
+	  ARG_ON_OFF|ARG_OFFSET|PPOLICY_USE_LOCKOUT,
+	  (void *)offsetof(pp_info,use_lockout),
+	  "( OLcfgOvAt:12.3 NAME 'olcPPolicyUseLockout' "
+	  "DESC 'Warn clients with AccountLocked' "
+	  "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs ppolicyocs[] = {
+	{ "( OLcfgOvOc:12.1 "
+	  "NAME 'olcPPolicyConfig' "
+	  "DESC 'Password Policy configuration' "
+	  "SUP olcOverlayConfig "
+	  "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ "
+	  "olcPPolicyUseLockout ) )",
+	  Cft_Overlay, ppolicycfg },
+	{ NULL, 0, NULL }
+};
+
+static int
+ppolicy_cf_default( ConfigArgs *c )
+{
+	slap_overinst *on = (slap_overinst *)c->bi;
+	pp_info *pi = (pp_info *)on->on_bi.bi_private;
+	int rc = ARG_BAD_CONF;
+
+	assert ( c->type == PPOLICY_DEFAULT );
+	Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default\n", 0, 0, 0);
+
+	switch ( c->op ) {
+	case SLAP_CONFIG_EMIT:
+		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n", 0, 0, 0);
+		rc = 0;
+		if ( !BER_BVISEMPTY( &pi->def_policy )) {
+			rc = value_add_one( &c->rvalue_vals,
+					    &pi->def_policy );
+			if ( rc ) return rc;
+			rc = value_add_one( &c->rvalue_nvals,
+					    &pi->def_policy );
+		}
+		break;
+	case LDAP_MOD_DELETE:
+		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default delete\n", 0, 0, 0);
+		if ( pi->def_policy.bv_val ) {
+			ber_memfree ( pi->def_policy.bv_val );
+			pi->def_policy.bv_val = NULL;
+		}
+		pi->def_policy.bv_len = 0;
+		rc = 0;
+		break;
+	case SLAP_CONFIG_ADD:
+		/* fallthrough to LDAP_MOD_ADD */
+	case LDAP_MOD_ADD:
+		Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n", 0, 0, 0);
+		if ( pi->def_policy.bv_val ) {
+			ber_memfree ( pi->def_policy.bv_val );
+		}
+		pi->def_policy = c->value_ndn;
+		ber_memfree( c->value_dn.bv_val );
+		BER_BVZERO( &c->value_dn );
+		BER_BVZERO( &c->value_ndn );
+		rc = 0;
+		break;
+	default:
+		abort ();
+	}
+
+	return rc;
+}
+
+static time_t
+parse_time( char *atm )
+{
+	struct lutil_tm tm;
+	struct lutil_timet tt;
+	time_t ret = (time_t)-1;
+
+	if ( lutil_parsetime( atm, &tm ) == 0) {
+		lutil_tm2time( &tm, &tt );
+		ret = tt.tt_sec;
+	}
+	return ret;
+}
+
+static int
+account_locked( Operation *op, Entry *e,
+		PassPolicy *pp, Modifications **mod ) 
+{
+	Attribute       *la;
+
+	assert(mod != NULL);
+
+	if ( (la = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) {
+		BerVarray vals = la->a_nvals;
+
+		/*
+		 * there is a lockout stamp - we now need to know if it's
+		 * a valid one.
+		 */
+		if (vals[0].bv_val != NULL) {
+			time_t then, now;
+			Modifications *m;
+
+			if (!pp->pwdLockoutDuration)
+				return 1;
+
+			if ((then = parse_time( vals[0].bv_val )) == (time_t)0)
+				return 1;
+
+			now = slap_get_time();
+
+			if (now < then + pp->pwdLockoutDuration)
+				return 1;
+
+			m = ch_calloc( sizeof(Modifications), 1 );
+			m->sml_op = LDAP_MOD_DELETE;
+			m->sml_flags = 0;
+			m->sml_type = ad_pwdAccountLockedTime->ad_cname;
+			m->sml_desc = ad_pwdAccountLockedTime;
+			m->sml_next = *mod;
+			*mod = m;
+		}
+	}
+
+	return 0;
+}
+
+/* IMPLICIT TAGS, all context-specific */
+#define PPOLICY_WARNING 0xa0L	/* constructed + 0 */
+#define PPOLICY_ERROR 0x81L		/* primitive + 1 */
+ 
+#define PPOLICY_EXPIRE 0x80L	/* primitive + 0 */
+#define PPOLICY_GRACE  0x81L	/* primitive + 1 */
+
+static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;
+
+static LDAPControl *
+create_passcontrol( int exptime, int grace, LDAPPasswordPolicyError err )
+{
+	char berbuf[LBER_ELEMENT_SIZEOF], bb2[LBER_ELEMENT_SIZEOF];
+	BerElement *ber = (BerElement *)berbuf, *b2 = (BerElement *)bb2;
+	LDAPControl *c;
+	struct berval bv;
+
+	c = ch_calloc( sizeof( LDAPControl ), 1 );
+	if ( c == NULL ) {
+		return NULL;
+	}
+	c->ldctl_oid = (char *)ppolicy_ctrl_oid;
+	c->ldctl_iscritical = 0;
+	BER_BVZERO( &c->ldctl_value );
+
+	ber_init2( ber, NULL, LBER_USE_DER );
+	ber_printf( ber, "{" /*}*/ );
+
+	if ( exptime >= 0 ) {
+		ber_init2( b2, NULL, LBER_USE_DER );
+		ber_printf( b2, "ti", PPOLICY_EXPIRE, exptime );
+		ber_flatten2( b2, &bv, 1 );
+		(void)ber_free_buf(b2);
+		ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
+		ch_free( bv.bv_val );
+	} else if ( grace > 0 ) {
+		ber_init2( b2, NULL, LBER_USE_DER );
+		ber_printf( b2, "ti", PPOLICY_GRACE, grace );
+		ber_flatten2( b2, &bv, 1 );
+		(void)ber_free_buf(b2);
+		ber_printf( ber, "tO", PPOLICY_WARNING, &bv );
+		ch_free( bv.bv_val );
+	}
+
+	if (err != PP_noError ) {
+		ber_printf( ber, "te", PPOLICY_ERROR, err );
+	}
+	ber_printf( ber, /*{*/ "N}" );
+
+	if (ber_flatten2( ber, &(c->ldctl_value), 1 ) == LBER_DEFAULT) {
+		ch_free(c);
+		c = NULL;
+	}
+	(void)ber_free_buf(ber);
+	return c;
+}
+
+static LDAPControl **
+add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl )
+{
+	LDAPControl **ctrls, **oldctrls = rs->sr_ctrls;
+	int n;
+
+	n = 0;
+	if ( oldctrls ) {
+		for ( ; oldctrls[n]; n++ )
+			;
+	}
+	n += 2;
+
+	ctrls = op->o_tmpcalloc( sizeof( LDAPControl * ), n, op->o_tmpmemctx );
+
+	n = 0;
+	if ( oldctrls ) {
+		for ( ; oldctrls[n]; n++ ) {
+			ctrls[n] = oldctrls[n];
+		}
+	}
+	ctrls[n] = ctrl;
+	ctrls[n+1] = NULL;
+
+	rs->sr_ctrls = ctrls;
+
+	return oldctrls;
+}
+
+static void
+ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
+{
+	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+	pp_info *pi = on->on_bi.bi_private;
+	Attribute *a;
+	BerVarray vals;
+	int rc;
+	Entry *pe = NULL;
+#if 0
+	const char *text;
+#endif
+
+	memset( pp, 0, sizeof(PassPolicy) );
+
+	pp->ad = slap_schema.si_ad_userPassword;
+
+	/* Users can change their own password by default */
+    	pp->pwdAllowUserChange = 1;
+
+	if ((a = attr_find( e->e_attrs, ad_pwdPolicySubentry )) == NULL) {
+		/*
+		 * entry has no password policy assigned - use default
+		 */
+		vals = &pi->def_policy;
+		if ( !vals->bv_val )
+			goto defaultpol;
+	} else {
+		vals = a->a_nvals;
+		if (vals[0].bv_val == NULL) {
+			Debug( LDAP_DEBUG_ANY,
+				"ppolicy_get: NULL value for policySubEntry\n", 0, 0, 0 );
+			goto defaultpol;
+		}
+	}
+
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	rc = be_entry_get_rw( op, vals, NULL, NULL, 0, &pe );
+	op->o_bd->bd_info = (BackendInfo *)on;
+
+	if ( rc ) goto defaultpol;
+
+#if 0	/* Only worry about userPassword for now */
+	if ((a = attr_find( pe->e_attrs, ad_pwdAttribute )))
+		slap_bv2ad( &a->a_vals[0], &pp->ad, &text );
+#endif
+
+	Debug( LDAP_DEBUG_TRACE,
+					"ppolicy_get: begin\n", 0, 0, 0 ); /* TESTTTEST */
+	pp->pwdMaxTotalAttempts = 0; /* RGFA just default safely to zero - no repeat checking 
+	                                override if we have an explicit value 
+	                              We do this here because we can drop out with any failure */
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdMinAge ) )
+			&& lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxAge ) )
+			&& lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdInHistory ) )
+			&& lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckQuality ) )
+			&& lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdMinLength ) )
+			&& lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	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_pwdGraceAuthNLimit ) )
+			&& lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdExpireWarning ) )
+			&& lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdFailureCountInterval ) )
+			&& lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdLockoutDuration ) )
+			&& lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 )
+		goto defaultpol;
+	/* RGFA */
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxTotalAttempts ) ))
+		lutil_atoi( &pp->pwdMaxTotalAttempts, a->a_vals[0].bv_val);
+
+	if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckModule ) ) ) {
+		strncpy( pp->pwdCheckModule, a->a_vals[0].bv_val,
+			sizeof(pp->pwdCheckModule) );
+		pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0';
+	}
+
+	if ((a = attr_find( pe->e_attrs, ad_pwdLockout )))
+    		pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv );
+	if ((a = attr_find( pe->e_attrs, ad_pwdMustChange )))
+    		pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
+	if ((a = attr_find( pe->e_attrs, ad_pwdAllowUserChange )))
+	    	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 );
+    
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	be_entry_release_r( op, pe );
+	op->o_bd->bd_info = (BackendInfo *)on;
+
+	return;
+
+defaultpol:
+	Debug( LDAP_DEBUG_TRACE,
+		"ppolicy_get: using default policy\n", 0, 0, 0 );
+	return;
+}
+
+static int
+password_scheme( struct berval *cred, struct berval *sch )
+{
+	int e;
+    
+	assert( cred != NULL );
+
+	if (sch) {
+		sch->bv_val = NULL;
+		sch->bv_len = 0;
+	}
+    
+	if ((cred->bv_len == 0) || (cred->bv_val == NULL) ||
+		(cred->bv_val[0] != '{')) return LDAP_OTHER;
+
+	for(e = 1; cred->bv_val[e] && cred->bv_val[e] != '}'; e++);
+	if (cred->bv_val[e]) {
+		int rc;
+		rc = lutil_passwd_scheme( cred->bv_val );
+		if (rc) {
+			if (sch) {
+				sch->bv_val = cred->bv_val;
+				sch->bv_len = e;
+			}
+			return LDAP_SUCCESS;
+		}
+	}
+	return LDAP_OTHER;
+}
+
+static int
+check_password_quality( struct berval *cred, PassPolicy *pp, LDAPPasswordPolicyError *err, Entry *e )
+{
+	int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
+	char *ptr = cred->bv_val;
+	struct berval sch;
+
+	assert( cred != NULL );
+	assert( pp != NULL );
+
+	if ((cred->bv_len == 0) || (pp->pwdMinLength > cred->bv_len)) {
+		rc = LDAP_CONSTRAINT_VIOLATION;
+		if ( err ) *err = PP_passwordTooShort;
+		return rc;
+	}
+
+        /*
+         * We need to know if the password is already hashed - if so
+         * what scheme is it. The reason being that the "hash" of
+         * {cleartext} still allows us to check the password.
+         */
+	rc = password_scheme( cred, &sch );
+	if (rc == LDAP_SUCCESS) {
+		if ((sch.bv_val) && (strncasecmp( sch.bv_val, "{cleartext}",
+			sch.bv_len ) == 0)) {
+			/*
+			 * We can check the cleartext "hash"
+			 */
+			ptr = cred->bv_val + sch.bv_len;
+		} else {
+			/* everything else, we can't check */
+			if (pp->pwdCheckQuality == 2) {
+				rc = LDAP_CONSTRAINT_VIOLATION;
+				if (err) *err = PP_insufficientPasswordQuality;
+				return rc;
+			}
+			/*
+			 * We can't check the syntax of the password, but it's not
+			 * mandatory (according to the policy), so we return success.
+			 */
+		    
+			return LDAP_SUCCESS;
+		}
+	}
+
+	rc = LDAP_SUCCESS;
+
+	if (pp->pwdCheckModule[0]) {
+#ifdef SLAPD_MODULES
+		lt_dlhandle mod;
+		const char *err;
+		
+		if ((mod = lt_dlopen( pp->pwdCheckModule )) == NULL) {
+			err = lt_dlerror();
+
+			Debug(LDAP_DEBUG_ANY,
+			"check_password_quality: lt_dlopen failed: (%s) %s.\n",
+				pp->pwdCheckModule, err, 0 );
+			ok = LDAP_OTHER; /* internal error */
+		} else {
+			int (*prog)( char *passwd, char **text, Entry *ent );
+
+			if ((prog = lt_dlsym( mod, "check_password" )) == NULL) {
+				err = lt_dlerror();
+			    
+				Debug(LDAP_DEBUG_ANY,
+					"check_password_quality: lt_dlsym failed: (%s) %s.\n",
+					pp->pwdCheckModule, err, 0 );
+				ok = LDAP_OTHER;
+			} else {
+				char *txt = NULL;
+
+				ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
+				ok = prog( cred->bv_val, &txt, e );
+				ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
+				if (ok != LDAP_SUCCESS) {
+					Debug(LDAP_DEBUG_ANY,
+						"check_password_quality: module error: (%s) %s.[%d]\n",
+						pp->pwdCheckModule, txt ? txt : "", ok );
+					free(txt);
+				}
+			}
+			    
+			lt_dlclose( mod );
+		}
+#else
+	Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
+		"supported. pwdCheckModule ignored.\n", 0, 0, 0);
+#endif /* SLAPD_MODULES */
+	}
+		
+		    
+	if (ok != LDAP_SUCCESS) {
+		rc = LDAP_CONSTRAINT_VIOLATION;
+		if (err) *err = PP_insufficientPasswordQuality;
+	}
+	
+	return rc;
+}
+typedef struct ppbind {
+	slap_overinst *on;
+	int send_ctrl;
+	struct berval pw;	/* RGFA saved password from bind */
+	LDAPControl **oldctrls;
+	Modifications *mod;
+	LDAPPasswordPolicyError pErr;
+	PassPolicy pp;
+} ppbind;
+
+static int
+parse_pwdhistory( struct berval *bv, char **oid, time_t *oldtime, struct berval *oldpw )
+{
+	char *ptr;
+	struct berval nv, npw;
+	int i, j;
+	
+	assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw );
+
+	if ( oid ) {
+		*oid = 0;
+	}
+	*oldtime = (time_t)-1;
+	BER_BVZERO( oldpw );
+	
+	ber_dupbv( &nv, bv );
+
+	/* first get the time field */
+	for ( i = 0; (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+		;
+	if ( i == nv.bv_len ) {
+		goto exit_failure; /* couldn't locate the '#' separator */
+	}
+	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+	ptr = nv.bv_val;
+	*oldtime = parse_time( ptr );
+	if (*oldtime == (time_t)-1) {
+		goto exit_failure;
+	}
+
+	/* get the OID field */
+	for (ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+		;
+	if ( i == nv.bv_len ) {
+		goto exit_failure; /* couldn't locate the '#' separator */
+	}
+	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+	if ( oid ) {
+		*oid = ber_strdup( ptr );
+	}
+	
+	/* get the length field */
+	for ( ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+		;
+	if ( i == nv.bv_len ) {
+		goto exit_failure; /* couldn't locate the '#' separator */
+	}
+	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+	oldpw->bv_len = strtol( ptr, NULL, 10 );
+	if (errno == ERANGE) {
+		goto exit_failure;
+	}
+
+	/* lastly, get the octets of the string */
+	for ( j = i, ptr = &(nv.bv_val[i]); i < nv.bv_len; i++ )
+		;
+	if ( i - j != oldpw->bv_len) {
+		goto exit_failure; /* length is wrong */
+	}
+
+	npw.bv_val = ptr;
+	npw.bv_len = oldpw->bv_len;
+	ber_dupbv( oldpw, &npw );
+	ber_memfree( nv.bv_val );
+	
+	return LDAP_SUCCESS;
+
+exit_failure:;
+	if ( oid && *oid ) {
+		ber_memfree(*oid);
+		*oid = NULL;
+	}
+	if ( oldpw->bv_val ) {
+		ber_memfree( oldpw->bv_val);
+		BER_BVZERO( oldpw );
+	}
+	ber_memfree( nv.bv_val );
+
+	return LDAP_OTHER;
+}
+
+#ifndef MAX_PWD_HISTORY_SZ
+#define MAX_PWD_HISTORY_SZ 1024
+#endif /* MAX_PWD_HISTORY_SZ */
+
+
+/* RGFA - make pwdUniqueAttempts entry 
+ */
+
+static void
+make_unique_value( char *timebuf, int count, 
+				  struct berval *pw, struct berval *dstbv, int hash )
+{
+	char str[ MAX_PWD_HISTORY_SZ ];
+	int nlen;
+	struct berval hpw;
+    const char *txt;
+	
+	if (hash){
+		slap_passwd_hash( pw, &hpw, &txt );
+	}else{
+		ber_dupbv( &hpw, pw ); /* move hashed pw */
+	}
+	snprintf( str, MAX_PWD_HISTORY_SZ,
+		  "%s#%ld#%lu#", timebuf,count,(unsigned long) hpw.bv_len );
+	str[MAX_PWD_HISTORY_SZ-1] = 0; /* defensive */
+	nlen = strlen(str);
+	AC_MEMCPY( str + nlen, hpw.bv_val, hpw.bv_len );
+	nlen += hpw.bv_len;
+	dstbv->bv_val = ch_malloc( nlen + 1 );
+	AC_MEMCPY( dstbv->bv_val, str, nlen );
+	dstbv->bv_val[nlen] = '\0';
+	dstbv->bv_len = nlen;
+	ber_memfree(hpw.bv_val); /* hashed password */
+	Debug( LDAP_DEBUG_TRACE,
+					"ppolicy_bind_rep: unique=%s\n", dstbv->bv_val, 0, 0 ); /* TESTTEST */
+}
+
+/* build replacement attributes from linked list */
+static void
+unique_replace_attr(pw_unique **pull, Modifications ** mod, int upc)
+{
+
+	
+	Modifications *m;
+	pw_unique *p = *pull;
+	int i;
+	char tts[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+	struct berval timestamp;
+	
+	m = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+	m->sml_op = LDAP_MOD_REPLACE;
+	m->sml_flags = SLAP_MOD_INTERNAL;
+	m->sml_desc = ad_pwdUniqueAttempts;
+	m->sml_type = ad_pwdUniqueAttempts->ad_cname;
+	m->sml_numvals = upc;
+	m->sml_values = ch_calloc( sizeof( struct berval ),upc + 1 );
+	m->sml_values[ upc ].bv_val = NULL;
+	m->sml_values[ upc ].bv_len = 0;
+	
+	for(i=0; i < upc; i++, p=p->next) {
+		timestamp.bv_val = tts;
+		timestamp.bv_len = sizeof(tts);
+		slap_timestamp( &p->t, &timestamp );
+		make_unique_value( tts, p->count,&p->pw, &m->sml_values[i], 0 );
+	}
+	
+	m->sml_next = *mod;
+	*mod = m;
+	
+}
+static void 
+unique_add_attr(Modifications **mod, 
+				char * time, struct berval *pw)
+{				
+	Modifications *m;
+
+	m = ch_calloc( sizeof(Modifications), 1 );
+	m->sml_op = LDAP_MOD_ADD;
+	m->sml_flags = SLAP_MOD_INTERNAL;
+	m->sml_type = ad_pwdUniqueAttempts->ad_cname;
+	m->sml_desc = ad_pwdUniqueAttempts;
+	m->sml_numvals = 1;
+	m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+	m->sml_nvalues = NULL;
+	m->sml_values[ 1 ].bv_val = NULL;
+	m->sml_values[ 1 ].bv_len = 0;
+				
+	make_unique_value( time, 1,pw, &m->sml_values[0], 1 );
+				
+	m->sml_next = *mod;
+	*mod = m;
+
+}
+
+
+/* RGFA - add pwdUniqueAttempts attribute to linked list
+*  if not expired. Return 0 if added else 1 
+ */
+static int
+add_to_unique( pw_unique **head, int *count, time_t now, 
+			  ppbind *ppb, struct berval *ua )
+{
+	
+	char *ptr;
+	struct berval nv, pw;
+	int i, j, add = 0;
+	pw_unique *p, *current = *head, *next;
+	int err; /* TESTTEST */
+
+	/* assert (head && ua && (ua->bv_len > 0) && (ua->bv_val) ); */
+	if (!head) return 1; /* oops */
+	p = ch_malloc( sizeof( pw_unique ));
+
+	ber_dupbv( &nv, ua ); /* parse is destructive */
+	
+	/* first get the time field */
+	for ( i = 0; (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+		;
+	err = 1;	/* TESTTEST */
+	if ( i == nv.bv_len ) {
+		goto exit_failure; /* couldn't locate the '#' separator */
+	}
+	nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+	ptr = nv.bv_val;
+	p->t = parse_time( ptr );
+	err = 2;	/* TESTTEST */
+	
+	/* need to add or not */
+	if(ppb->pp.pwdFailureCountInterval == 0){
+		add = 1;
+	} else if ( now <= (p->t + ppb->pp.pwdFailureCountInterval) ) {
+		add = 1;
+	}
+	if (add){
+
+		/* get the counter field */
+		for (ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+			;
+		err = 3;	/* TESTTEST */
+		if ( i == nv.bv_len ) {
+			goto exit_failure; /* couldn't locate the '#' separator */
+		}
+		nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+		lutil_atoi( &p->count, ptr );
+	
+		/* get the length field */
+		for ( ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+			;
+		
+		err = 4;	/* TESTTEST */
+		if ( i == nv.bv_len ) {
+			goto exit_failure; /* couldn't locate the '#' separator */
+		}
+		nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+		pw.bv_len = strtol( ptr, NULL, 10 );
+		if (errno == ERANGE) {
+			goto exit_failure;
+		}
+
+		/* lastly, get the octets of the string */
+		for ( j = i, ptr = &(nv.bv_val[i]); i < nv.bv_len; i++ )
+		;
+		err = 5;	/* TESTTEST */
+		if ( i - j != pw.bv_len) {
+		
+			goto exit_failure; /* length is wrong */
+		}
+
+		pw.bv_val = ptr;
+		ber_dupbv( &p->pw, &pw );
+		ber_memfree( nv.bv_val );
+		/* add to linked list */
+		p->next = NULL;
+		*count += p->count;		/* add to count */
+		if (*head == NULL) {
+			/* empty list */
+			*head = p;
+			return 0;
+		}
+		/*
+		 * find end and add
+	 */
+		while (current->next){
+			current = current->next;
+		}
+		current->next = p;
+		return 0;
+	}
+
+exit_failure:;
+	Debug( LDAP_DEBUG_TRACE,
+					"ppolicy_add_unique: error=%d\n", err, 0, 0 ); /* TESTTEST */
+	ber_memfree( nv.bv_val );
+	free(p);
+
+	return 1;
+}
+/* RGFA returns 0 if found else 1
+*  increments count of found entry 
+*/
+static int 
+check_for_unique (pw_unique **head, struct berval *pw)
+{
+	const char *text;
+	int   rc;
+
+	pw_unique *p, *next;
+	if(!head ) return 1;
+	p = *head;
+	while(p){
+		next = p->next;
+		if((rc = lutil_passwd(&p->pw, pw, NULL, &text)) == 0){
+			/* match */
+			p->count++; /* increment count for entry */
+			return 0;
+		}
+		p = next;
+	}
+	return 1;
+}
+static void
+free_unique_list( pw_unique **head )
+{
+	pw_unique *p, *next;
+    
+	if (!head || !*head) return;
+	p = *head;
+	while (p) {
+		next = p->next;
+		ber_memfree(p->pw.bv_val);
+		free(p);
+		p = next;
+	}
+	*head = NULL;
+}
+static void
+add_to_pwd_history( pw_hist **l, time_t t,
+                    struct berval *oldpw, struct berval *bv )
+{
+	pw_hist *p, *p1, *p2;
+    
+	if (!l) return;
+
+	p = ch_malloc( sizeof( pw_hist ));
+	p->pw = *oldpw;
+	ber_dupbv( &p->bv, bv );
+	p->t = t;
+	p->next = NULL;
+	
+	if (*l == NULL) {
+		/* degenerate case */
+		*l = p;
+		return;
+	}
+	/*
+	 * advance p1 and p2 such that p1 is the node before the
+	 * new one, and p2 is the node after it
+	 */
+	for (p1 = NULL, p2 = *l; p2 && p2->t <= t; p1 = p2, p2=p2->next );
+	p->next = p2;
+	if (p1 == NULL) { *l = p; return; }
+	p1->next = p;
+}
+
+
+static void
+make_pwd_history_value( char *timebuf, struct berval *bv, Attribute *pa )
+{
+	char str[ MAX_PWD_HISTORY_SZ ];
+	int nlen;
+
+	snprintf( str, MAX_PWD_HISTORY_SZ,
+		  "%s#%s#%lu#", timebuf,
+		  pa->a_desc->ad_type->sat_syntax->ssyn_oid,
+		  (unsigned long) pa->a_nvals[0].bv_len );
+	str[MAX_PWD_HISTORY_SZ-1] = 0;
+	nlen = strlen(str);
+
+        /*
+         * We have to assume that the string is a string of octets,
+         * not readable characters. In reality, yes, it probably is
+         * a readable (ie, base64) string, but we can't count on that
+         * Hence, while the first 3 fields of the password history
+         * are definitely readable (a timestamp, an OID and an integer
+         * length), the remaining octets of the actual password
+         * are deemed to be binary data.
+         */
+	AC_MEMCPY( str + nlen, pa->a_nvals[0].bv_val, pa->a_nvals[0].bv_len );
+	nlen += pa->a_nvals[0].bv_len;
+	bv->bv_val = ch_malloc( nlen + 1 );
+	AC_MEMCPY( bv->bv_val, str, nlen );
+	bv->bv_val[nlen] = '\0';
+	bv->bv_len = nlen;
+}
+
+static void
+free_pwd_history_list( pw_hist **l )
+{
+	pw_hist *p;
+    
+	if (!l) return;
+	p = *l;
+	while (p) {
+		pw_hist *pp = p->next;
+
+		free(p->pw.bv_val);
+		free(p->bv.bv_val);
+		free(p);
+		p = pp;
+	}
+	*l = NULL;
+}
+
+
+
+static void
+ctrls_cleanup( Operation *op, SlapReply *rs, LDAPControl **oldctrls )
+{
+	int n;
+
+	assert( rs->sr_ctrls != NULL );
+	assert( rs->sr_ctrls[0] != NULL );
+
+	for ( n = 0; rs->sr_ctrls[n]; n++ ) {
+		if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid ) {
+			ch_free( rs->sr_ctrls[n]->ldctl_value.bv_val );
+			ch_free( rs->sr_ctrls[n] );
+			rs->sr_ctrls[n] = (LDAPControl *)(-1);
+			break;
+		}
+	}
+
+	if ( rs->sr_ctrls[n] == NULL ) {
+		/* missed? */
+	}
+
+	op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx );
+
+	rs->sr_ctrls = oldctrls;
+}
+
+static int
+ppolicy_ctrls_cleanup( Operation *op, SlapReply *rs )
+{
+	ppbind *ppb = op->o_callback->sc_private;
+	if ( ppb->send_ctrl ) {
+		ctrls_cleanup( op, rs, ppb->oldctrls );
+	}
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_bind_response( Operation *op, SlapReply *rs )
+{
+	ppbind *ppb = op->o_callback->sc_private;
+	slap_overinst *on = ppb->on;
+	Modifications *mod = ppb->mod, *m;
+	int pwExpired = 0; 
+	int ngut = -1, warn = -1, age, rc;
+	Attribute *a;
+	time_t now, pwtime = (time_t)-1;
+	char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+	struct berval timestamp;
+	BackendInfo *bi = op->o_bd->bd_info;
+	Entry *e;
+	pw_unique *pull = NULL;
+
+	/* If we already know it's locked, just get on with it */
+	if ( ppb->pErr != PP_noError ) {
+		goto locked;
+	}
+
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+	op->o_bd->bd_info = bi;
+
+	if ( rc != LDAP_SUCCESS ) {
+		return SLAP_CB_CONTINUE;
+	}
+
+	now = slap_get_time(); /* stored for later consideration */
+	timestamp.bv_val = nowstr;
+	timestamp.bv_len = sizeof(nowstr);
+	slap_timestamp( &now, &timestamp );
+
+	if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) {
+		int i = 0, fc = 0, repeat = 0, upc = 0, count = 0;
+
+		/* RGFA - check if we are doing repeat counting - else behave
+		as before */
+		if (ppb->pp.pwdMaxTotalAttempts != 0 && (ppb->pw.bv_val)) {
+			/* check if this a repeat - if not add to pwdUniqueAttempts
+			   and pwdFailureTimes, else optionally add to count then verify
+			   if the count is maxed out */
+			if ((a = attr_find( e->e_attrs, ad_pwdUniqueAttempts )) != NULL) {
+				/* place all unexpired entries on linked list 
+				*/
+				
+				for(i=0; a->a_nvals[i].bv_val; i++) {
+					if( (add_to_unique(&pull,&count, now, ppb, &(a->a_nvals[i]) )) == 0){
+						upc++;
+					}
+				}
+				if((rc = check_for_unique(&pull, &ppb->pw)) == 0){
+				/* match - repeat */
+					repeat = 1; /* inhibit pwdFailureTime checks */
+					
+					if(ppb->pp.pwdMaxTotalAttempts > 0){ 	
+						/* now check for threshold */
+						if(count >= (ppb->pp.pwdMaxTotalAttempts -1)){
+							/* lock account  and delete pwdUniqueAttempts */
+							m = ch_calloc( sizeof(Modifications), 1 );
+							m->sml_op = LDAP_MOD_REPLACE;
+							m->sml_flags = 0;
+							m->sml_type = ad_pwdAccountLockedTime->ad_cname;
+							m->sml_desc = ad_pwdAccountLockedTime;
+							m->sml_numvals = 1;
+							m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+							m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+							ber_dupbv( &m->sml_values[0], &timestamp );
+							ber_dupbv( &m->sml_nvalues[0], &timestamp );
+							m->sml_next = mod;
+							mod = m;
+							/* delete pwdUniqueAttempts */
+							m = ch_calloc( sizeof(Modifications), 1 );
+							m->sml_op = LDAP_MOD_DELETE;
+							m->sml_flags = SLAP_MOD_INTERNAL;
+							m->sml_flags = 0;
+							m->sml_type = ad_pwdUniqueAttempts->ad_cname;
+							m->sml_desc = ad_pwdUniqueAttempts;
+							m->sml_next = mod;
+							mod = m;
+
+						} else {
+						
+							unique_replace_attr(&pull, &mod, upc);
+						}
+					}
+				} else {
+					/* no match */
+					/* are we going to fail in pwdFailureTime check? if so 
+					*  delete all attributes */
+					
+					if(upc >= ppb->pp.pwdMaxFailure){
+						m = ch_calloc( sizeof(Modifications), 1 );
+						m->sml_op = LDAP_MOD_DELETE;
+						m->sml_flags = SLAP_MOD_INTERNAL;
+						m->sml_flags = 0;
+						m->sml_type = ad_pwdUniqueAttempts->ad_cname;
+						m->sml_desc = ad_pwdUniqueAttempts;
+						m->sml_next = mod;
+						mod = m;
+					} else {
+						
+						/* we do not perform the check for pwdMaxTotalAttempts 
+						*  threshold here since eventually it will get failed 
+						*  by pwdFailureTime/pwdMaxFailure check
+						*/
+						
+						unique_add_attr(&mod, nowstr, &ppb->pw);
+					}
+				}
+			}else{
+				/* first one - just add pwdUniqueAttempts entry */
+				unique_add_attr(&mod, nowstr, &ppb->pw);
+			}
+			/* RGFA - clean up */
+			free_unique_list(&pull);
+			
+		}
+		/* RGFA - clean up saved password from bind */
+		if(ppb->pw.bv_val){
+			memset(ppb->pw.bv_val, 0, ppb->pw.bv_len);
+			ber_memfree(ppb->pw.bv_val);
+		}
+		if (!repeat){
+			m = ch_calloc( sizeof(Modifications), 1 );
+			m->sml_op = LDAP_MOD_ADD;
+			m->sml_flags = 0;
+			m->sml_type = ad_pwdFailureTime->ad_cname;
+			m->sml_desc = ad_pwdFailureTime;
+			m->sml_numvals = 1;
+			m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+			m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+
+			ber_dupbv( &m->sml_values[0], &timestamp );
+			ber_dupbv( &m->sml_nvalues[0], &timestamp );
+			m->sml_next = mod;
+			mod = m;
+
+			/*
+			 * Count the pwdFailureTimes - if it's
+			 * greater than the policy pwdMaxFailure,
+			 * then lock the account.
+			 */
+			if ((a = attr_find( e->e_attrs, ad_pwdFailureTime )) != NULL) {
+				for(i=0; a->a_nvals[i].bv_val; i++) {
+
+					/*
+					 * If the interval is 0, then failures
+					 * stay on the record until explicitly
+					 * reset by successful authentication.
+					 */
+					if (ppb->pp.pwdFailureCountInterval == 0) {
+						fc++;
+					} else if (now <=
+								parse_time(a->a_nvals[i].bv_val) +
+								ppb->pp.pwdFailureCountInterval) {
+
+						fc++;
+					}
+					/*
+					 * We only count those failures
+					 * which are not due to expire.
+					 */
+				}
+			}
+			
+			if ((ppb->pp.pwdMaxFailure > 0) &&
+				(fc >= ppb->pp.pwdMaxFailure - 1)) {
+
+				/*
+				 * We subtract 1 from the failure max
+				 * because the new failure entry hasn't
+				 * made it to the entry yet.
+				 */
+				m = ch_calloc( sizeof(Modifications), 1 );
+				m->sml_op = LDAP_MOD_REPLACE;
+				m->sml_flags = 0;
+				m->sml_type = ad_pwdAccountLockedTime->ad_cname;
+				m->sml_desc = ad_pwdAccountLockedTime;
+				m->sml_numvals = 1;
+				m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+				m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+				ber_dupbv( &m->sml_values[0], &timestamp );
+				ber_dupbv( &m->sml_nvalues[0], &timestamp );
+				m->sml_next = mod;
+				mod = m;
+			}
+		}
+	} else if ( rs->sr_err == LDAP_SUCCESS ) {
+		/* RGFA - clean up saved password from bind */
+		if(ppb->pw.bv_val){
+			memset(ppb->pw.bv_val, 0, ppb->pw.bv_len);
+			ber_memfree(ppb->pw.bv_val);
+		}
+		if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL)
+			pwtime = parse_time( a->a_nvals[0].bv_val );
+
+		/* delete all pwdFailureTimes */
+		if ( attr_find( e->e_attrs, ad_pwdFailureTime )) {
+			m = ch_calloc( sizeof(Modifications), 1 );
+			m->sml_op = LDAP_MOD_DELETE;
+			m->sml_flags = 0;
+			m->sml_type = ad_pwdFailureTime->ad_cname;
+			m->sml_desc = ad_pwdFailureTime;
+			m->sml_next = mod;
+			mod = m;
+		}
+		/* RGFA if there are pwdUniqueAttempts - delete them all */
+		if ( attr_find( e->e_attrs, ad_pwdUniqueAttempts )) {
+			m = ch_calloc( sizeof(Modifications), 1 );
+			m->sml_op = LDAP_MOD_DELETE;
+			m->sml_flags = SLAP_MOD_INTERNAL;
+			m->sml_flags = 0;
+			m->sml_type = ad_pwdUniqueAttempts->ad_cname;
+			m->sml_desc = ad_pwdUniqueAttempts;
+			m->sml_next = mod;
+			mod = m;
+		}
+
+		/*
+		 * check to see if the password must be changed
+		 */
+		if ( ppb->pp.pwdMustChange &&
+			(a = attr_find( e->e_attrs, ad_pwdReset )) &&
+			bvmatch( &a->a_nvals[0], &slap_true_bv ) )
+		{
+			/*
+			 * need to inject client controls here to give
+			 * more information. For the moment, we ensure
+			 * that we are disallowed from doing anything
+			 * other than change password.
+			 */
+			ber_dupbv( &pwcons[op->o_conn->c_conn_idx].dn,
+				&op->o_conn->c_ndn );
+
+			ppb->pErr = PP_changeAfterReset;
+
+		} else {
+			/*
+			 * the password does not need to be changed, so
+			 * we now check whether the password has expired.
+			 *
+			 * We can skip this bit if passwords don't age in
+			 * the policy. Also, if there was no pwdChangedTime
+			 * attribute in the entry, the password never expires.
+			 */
+			if (ppb->pp.pwdMaxAge == 0) goto grace;
+
+			if (pwtime != (time_t)-1) {
+				/*
+				 * Check: was the last change time of
+				 * the password older than the maximum age
+				 * allowed. (Ignore case 2 from I-D, it's just silly.)
+				 */
+				if (now - pwtime > ppb->pp.pwdMaxAge ) pwExpired = 1;
+			}
+		}
+
+grace:
+		if (!pwExpired) goto check_expiring_password;
+		
+		if ((a = attr_find( e->e_attrs, ad_pwdGraceUseTime )) == NULL)
+			ngut = ppb->pp.pwdGraceAuthNLimit;
+		else {
+			for(ngut=0; a->a_nvals[ngut].bv_val; ngut++);
+			ngut = ppb->pp.pwdGraceAuthNLimit - ngut;
+		}
+
+		/*
+		 * ngut is the number of remaining grace logins
+		 */
+		Debug( LDAP_DEBUG_ANY,
+			"ppolicy_bind: Entry %s has an expired password: %d grace logins\n",
+			e->e_name.bv_val, ngut, 0);
+		
+		if (ngut < 1) {
+			ppb->pErr = PP_passwordExpired;
+			rs->sr_err = LDAP_INVALID_CREDENTIALS;
+			goto done;
+		}
+
+		/*
+		 * Add a grace user time to the entry
+		 */
+		m = ch_calloc( sizeof(Modifications), 1 );
+		m->sml_op = LDAP_MOD_ADD;
+		m->sml_flags = 0;
+		m->sml_type = ad_pwdGraceUseTime->ad_cname;
+		m->sml_desc = ad_pwdGraceUseTime;
+		m->sml_numvals = 1;
+		m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+		m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+		ber_dupbv( &m->sml_values[0], &timestamp );
+		ber_dupbv( &m->sml_nvalues[0], &timestamp );
+		m->sml_next = mod;
+		mod = m;
+
+check_expiring_password:
+		/*
+		 * Now we need to check to see
+		 * if it is about to expire, and if so, should the user
+		 * be warned about it in the password policy control.
+		 *
+		 * If the password has expired, and we're in the grace period, then
+		 * we don't need to do this bit. Similarly, if we don't have password
+		 * aging, then there's no need to do this bit either.
+		 */
+		if ((ppb->pp.pwdMaxAge < 1) || (pwExpired) || (ppb->pp.pwdExpireWarning < 1))
+			goto done;
+
+		age = (int)(now - pwtime);
+		
+		/*
+		 * We know that there is a password Change Time attribute - if
+		 * there wasn't, then the pwdExpired value would be true, unless
+		 * there is no password aging - and if there is no password aging,
+		 * then this section isn't called anyway - you can't have an
+		 * expiring password if there's no limit to expire.
+		 */
+		if (ppb->pp.pwdMaxAge - age < ppb->pp.pwdExpireWarning ) {
+			/*
+			 * Set the warning value.
+			 */
+			warn = ppb->pp.pwdMaxAge - age; /* seconds left until expiry */
+			if (warn < 0) warn = 0; /* something weird here - why is pwExpired not set? */
+			
+			Debug( LDAP_DEBUG_ANY,
+				"ppolicy_bind: Setting warning for password expiry for %s = %d seconds\n",
+				op->o_req_dn.bv_val, warn, 0 );
+		}
+	}
+
+done:
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	be_entry_release_r( op, e );
+
+locked:
+	if ( mod ) {
+		Operation op2 = *op;
+		SlapReply r2 = { REP_RESULT };
+		slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+
+		/* FIXME: Need to handle replication of some (but not all)
+		 * of the operational attributes...
+		 */
+		op2.o_tag = LDAP_REQ_MODIFY;
+		op2.o_callback = &cb;
+		op2.orm_modlist = mod;
+		op2.o_dn = op->o_bd->be_rootdn;
+		op2.o_ndn = op->o_bd->be_rootndn;
+		op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+		rc = op->o_bd->be_modify( &op2, &r2 );
+		slap_mods_free( mod, 1 );
+	}
+
+	if ( ppb->send_ctrl ) {
+		LDAPControl *ctrl = NULL;
+		pp_info *pi = on->on_bi.bi_private;
+
+		/* Do we really want to tell that the account is locked? */
+		if ( ppb->pErr == PP_accountLocked && !pi->use_lockout ) {
+			ppb->pErr = PP_noError;
+		}
+		ctrl = create_passcontrol( warn, ngut, ppb->pErr );
+		ppb->oldctrls = add_passcontrol( op, rs, ctrl );
+		op->o_callback->sc_cleanup = ppolicy_ctrls_cleanup;
+	}
+	op->o_bd->bd_info = bi;
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_bind( Operation *op, SlapReply *rs )
+{
+	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+	/* Reset lockout status on all Bind requests */
+	if ( !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+		ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+		BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+	}
+
+	/* Root bypasses policy */
+	if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn )) {
+		Entry *e;
+		int rc;
+		ppbind *ppb;
+		slap_callback *cb;
+
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+
+		if ( rc != LDAP_SUCCESS ) {
+			return SLAP_CB_CONTINUE;
+		}
+
+		cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback),
+			1, op->o_tmpmemctx );
+		ppb = (ppbind *)(cb+1);
+		ppb->on = on;
+		ppb->pErr = PP_noError;
+
+		/* Setup a callback so we can munge the result */
+
+		cb->sc_response = ppolicy_bind_response;
+		cb->sc_next = op->o_callback->sc_next;
+		cb->sc_private = ppb;
+		op->o_callback->sc_next = cb;
+
+		/* Did we receive a password policy request control? */
+		if ( op->o_ctrlflag[ppolicy_cid] ) {
+			ppb->send_ctrl = 1;
+		}
+
+		op->o_bd->bd_info = (BackendInfo *)on;
+		ppolicy_get( op, e, &ppb->pp );
+
+		rc = account_locked( op, e, &ppb->pp, &ppb->mod );
+
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		
+		/* RGFA - conditionally save password for use in ppolicy_bind_response */
+		ppb->pw.bv_len = 0; /* indicator for no password saved - make sure its zero */
+		if ((ppb->pp.pwdMaxTotalAttempts != 0) &&  (op->oq_bind.rb_method == LDAP_AUTH_SIMPLE) ) {
+			ber_dupbv(&ppb->pw,&op->oq_bind.rb_cred);
+		}
+		be_entry_release_r( op, e );
+
+		if ( rc ) {
+			/* This will be the Draft 8 response, Unwilling is bogus */
+			ppb->pErr = PP_accountLocked;
+			send_ldap_error( op, rs, LDAP_INVALID_CREDENTIALS, NULL );
+			return rs->sr_err;
+		}
+
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+/* Reset the restricted info for the next session on this connection */
+static int
+ppolicy_connection_destroy( BackendDB *bd, Connection *conn )
+{
+	if ( !BER_BVISEMPTY( &pwcons[conn->c_conn_idx].dn )) {
+		ch_free( pwcons[conn->c_conn_idx].dn.bv_val );
+		BER_BVZERO( &pwcons[conn->c_conn_idx].dn );
+	}
+	return SLAP_CB_CONTINUE;
+}
+
+/* Check if this connection is restricted */
+static int
+ppolicy_restrict(
+	Operation *op,
+	SlapReply *rs )
+{
+	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+	int send_ctrl = 0;
+
+	/* Did we receive a password policy request control? */
+	if ( op->o_ctrlflag[ppolicy_cid] ) {
+		send_ctrl = 1;
+	}
+
+	if ( op->o_conn && !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+		LDAPControl **oldctrls;
+		/* if the current authcDN doesn't match the one we recorded,
+		 * then an intervening Bind has succeeded and the restriction
+		 * no longer applies. (ITS#4516)
+		 */
+		if ( !dn_match( &op->o_conn->c_ndn,
+				&pwcons[op->o_conn->c_conn_idx].dn )) {
+			ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+			BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+			return SLAP_CB_CONTINUE;
+		}
+
+		Debug( LDAP_DEBUG_TRACE,
+			"connection restricted to password changing only\n", 0, 0, 0);
+		if ( send_ctrl ) {
+			LDAPControl *ctrl = NULL;
+			ctrl = create_passcontrol( -1, -1, PP_changeAfterReset );
+			oldctrls = add_passcontrol( op, rs, ctrl );
+		}
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, 
+			"Operations are restricted to bind/unbind/abandon/StartTLS/modify password" );
+		if ( send_ctrl ) {
+			ctrls_cleanup( op, rs, oldctrls );
+		}
+		return rs->sr_err;
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_add(
+	Operation *op,
+	SlapReply *rs )
+{
+	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+	pp_info *pi = on->on_bi.bi_private;
+	PassPolicy pp;
+	Attribute *pa;
+	const char *txt;
+
+	if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE )
+		return rs->sr_err;
+
+	/* If this is a replica, assume the master checked everything */
+	if ( be_shadow_update( op ))
+		return SLAP_CB_CONTINUE;
+
+	/* Check for password in entry */
+	if ((pa = attr_find( op->oq_add.rs_e->e_attrs,
+		slap_schema.si_ad_userPassword )))
+	{
+		assert( pa->a_vals != NULL );
+		assert( !BER_BVISNULL( &pa->a_vals[ 0 ] ) );
+
+		if ( !BER_BVISNULL( &pa->a_vals[ 1 ] ) ) {
+			send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, "Password policy only allows one password value" );
+			return rs->sr_err;
+		}
+
+		/*
+		 * new entry contains a password - if we're not the root user
+		 * then we need to check that the password fits in with the
+		 * security policy for the new entry.
+		 */
+		ppolicy_get( op, op->ora_e, &pp );
+		if (pp.pwdCheckQuality > 0 && !be_isroot( op )) {
+			struct berval *bv = &(pa->a_vals[0]);
+			int rc, send_ctrl = 0;
+			LDAPPasswordPolicyError pErr = PP_noError;
+
+			/* Did we receive a password policy request control? */
+			if ( op->o_ctrlflag[ppolicy_cid] ) {
+				send_ctrl = 1;
+			}
+			rc = check_password_quality( bv, &pp, &pErr, op->ora_e );
+			if (rc != LDAP_SUCCESS) {
+				LDAPControl **oldctrls = NULL;
+				op->o_bd->bd_info = (BackendInfo *)on->on_info;
+				if ( send_ctrl ) {
+					LDAPControl *ctrl = NULL;
+					ctrl = create_passcontrol( -1, -1, pErr );
+					oldctrls = add_passcontrol( op, rs, ctrl );
+				}
+				send_ldap_error( op, rs, rc, "Password fails quality checking policy" );
+				if ( send_ctrl ) {
+					ctrls_cleanup( op, rs, oldctrls );
+				}
+				return rs->sr_err;
+			}
+		}
+			/*
+			 * A controversial bit. We hash cleartext
+			 * passwords provided via add and modify operations
+			 * You're not really supposed to do this, since
+			 * the X.500 model says "store attributes" as they
+			 * get provided. By default, this is what we do
+			 *
+			 * But if the hash_passwords flag is set, we hash
+			 * any cleartext password attribute values via the
+			 * default password hashing scheme.
+			 */
+		if ((pi->hash_passwords) &&
+			(password_scheme( &(pa->a_vals[0]), NULL ) != LDAP_SUCCESS)) {
+			struct berval hpw;
+
+			slap_passwd_hash( &(pa->a_vals[0]), &hpw, &txt );
+			if (hpw.bv_val == NULL) {
+				/*
+				 * hashing didn't work. Emit an error.
+				 */
+				rs->sr_err = LDAP_OTHER;
+				rs->sr_text = txt;
+				send_ldap_error( op, rs, LDAP_OTHER, "Password hashing failed" );
+				return rs->sr_err;
+			}
+
+			memset( pa->a_vals[0].bv_val, 0, pa->a_vals[0].bv_len);
+			ber_memfree( pa->a_vals[0].bv_val );
+			pa->a_vals[0].bv_val = hpw.bv_val;
+			pa->a_vals[0].bv_len = hpw.bv_len;
+		}
+
+		/* If password aging is in effect, set the pwdChangedTime */
+		if ( pp.pwdMaxAge || pp.pwdMinAge ) {
+			struct berval timestamp;
+			char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+			time_t now = slap_get_time();
+
+			timestamp.bv_val = timebuf;
+			timestamp.bv_len = sizeof(timebuf);
+			slap_timestamp( &now, &timestamp );
+
+			attr_merge_one( op->ora_e, ad_pwdChangedTime, &timestamp, &timestamp );
+		}
+	}
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_mod_cb( Operation *op, SlapReply *rs )
+{
+	slap_callback *sc = op->o_callback;
+	op->o_callback = sc->sc_next;
+	if ( rs->sr_err == LDAP_SUCCESS ) {
+		ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+		BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+	}
+	op->o_tmpfree( sc, op->o_tmpmemctx );
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_modify( Operation *op, SlapReply *rs )
+{
+	slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
+	pp_info			*pi = on->on_bi.bi_private;
+	int			i, rc, mod_pw_only, pwmod, pwmop = -1, deladd,
+				hsize = 0;
+	PassPolicy		pp;
+	Modifications		*mods = NULL, *modtail = NULL,
+				*ml, *delmod, *addmod;
+	Attribute		*pa, *ha, at;
+	const char		*txt;
+	pw_hist			*tl = NULL, *p;
+	int			zapReset, send_ctrl = 0;
+	Entry			*e;
+	struct berval		newpw = BER_BVNULL, oldpw = BER_BVNULL,
+				*bv, cr[2];
+	LDAPPasswordPolicyError pErr = PP_noError;
+	LDAPControl 		**oldctrls = NULL;
+
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+	op->o_bd->bd_info = (BackendInfo *)on;
+
+	if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE;
+
+	/* If this is a replica, we may need to tweak some of the
+	 * master's modifications. Otherwise, just pass it through.
+	 */
+	if ( be_shadow_update( op )) {
+		Modifications **prev;
+		int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0;
+		Attribute *a_grace, *a_lock, *a_fail;
+
+		a_grace = attr_find( e->e_attrs, ad_pwdGraceUseTime );
+		a_lock = attr_find( e->e_attrs, ad_pwdAccountLockedTime );
+		a_fail = attr_find( e->e_attrs, ad_pwdFailureTime );
+
+		for( prev = &op->orm_modlist, ml = *prev; ml; ml = *prev ) {
+
+			if ( ml->sml_desc == slap_schema.si_ad_userPassword )
+				got_pw = 1;
+
+			/* If we're deleting an attr that didn't exist,
+			 * drop this delete op
+			 */
+			if ( ml->sml_op == LDAP_MOD_DELETE ) {
+				int drop = 0;
+
+				if ( ml->sml_desc == ad_pwdGraceUseTime ) {
+					got_del_grace = 1;
+					if ( !a_grace )
+						drop = 1;
+				} else
+				if ( ml->sml_desc == ad_pwdAccountLockedTime ) {
+					got_del_lock = 1;
+					if ( !a_lock )
+						drop = 1;
+				} else
+				if ( ml->sml_desc == ad_pwdFailureTime ) {
+					got_del_fail = 1;
+					if ( !a_fail )
+						drop = 1;
+				}
+				if ( drop ) {
+					*prev = ml->sml_next;
+					ml->sml_next = NULL;
+					slap_mods_free( ml, 1 );
+					continue;
+				}
+			}
+			prev = &ml->sml_next;
+		}
+
+		/* If we're resetting the password, make sure grace, accountlock,
+		 * and failure also get removed.
+		 */
+		if ( got_pw ) {
+			if ( a_grace && !got_del_grace ) {
+				ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+				ml->sml_op = LDAP_MOD_DELETE;
+				ml->sml_flags = SLAP_MOD_INTERNAL;
+				ml->sml_type.bv_val = NULL;
+				ml->sml_desc = ad_pwdGraceUseTime;
+				ml->sml_numvals = 0;
+				ml->sml_values = NULL;
+				ml->sml_nvalues = NULL;
+				ml->sml_next = NULL;
+				*prev = ml;
+				prev = &ml->sml_next;
+			}
+			if ( a_lock && !got_del_lock ) {
+				ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+				ml->sml_op = LDAP_MOD_DELETE;
+				ml->sml_flags = SLAP_MOD_INTERNAL;
+				ml->sml_type.bv_val = NULL;
+				ml->sml_desc = ad_pwdAccountLockedTime;
+				ml->sml_numvals = 0;
+				ml->sml_values = NULL;
+				ml->sml_nvalues = NULL;
+				ml->sml_next = NULL;
+				*prev = ml;
+			}
+			if ( a_fail && !got_del_fail ) {
+				ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+				ml->sml_op = LDAP_MOD_DELETE;
+				ml->sml_flags = SLAP_MOD_INTERNAL;
+				ml->sml_type.bv_val = NULL;
+				ml->sml_desc = ad_pwdFailureTime;
+				ml->sml_numvals = 0;
+				ml->sml_values = NULL;
+				ml->sml_nvalues = NULL;
+				ml->sml_next = NULL;
+				*prev = ml;
+                /* RGFA - no harm in deleting attribute even if it does not exist */
+                ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+				ml->sml_op = LDAP_MOD_DELETE;
+				ml->sml_flags = SLAP_MOD_INTERNAL;
+				ml->sml_type.bv_val = NULL;
+				ml->sml_desc = ad_pwdUniqueAttempts;
+				ml->sml_numvals = 0;
+				ml->sml_values = NULL;
+				ml->sml_nvalues = NULL;
+				ml->sml_next = NULL;
+				*prev = ml;
+			}
+		}
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		be_entry_release_r( op, e );
+		return SLAP_CB_CONTINUE;
+	}
+
+	/* Did we receive a password policy request control? */
+	if ( op->o_ctrlflag[ppolicy_cid] ) {
+		send_ctrl = 1;
+	}
+
+	/* See if this is a pwdModify exop. If so, we can
+	 * access the plaintext passwords from that request.
+	 */
+	{
+		slap_callback *sc;
+
+		for ( sc = op->o_callback; sc; sc=sc->sc_next ) {
+			if ( sc->sc_response == slap_null_cb &&
+				sc->sc_private ) {
+				req_pwdexop_s *qpw = sc->sc_private;
+				newpw = qpw->rs_new;
+				oldpw = qpw->rs_old;
+			   	break;
+			}
+		}
+	}
+
+	ppolicy_get( op, e, &pp );
+
+	for ( ml = op->orm_modlist,
+			pwmod = 0, mod_pw_only = 1,
+			deladd = 0, delmod = NULL,
+			addmod = NULL,
+			zapReset = 1;
+		ml != NULL; modtail = ml, ml = ml->sml_next )
+	{
+		if ( ml->sml_desc == pp.ad ) {
+			pwmod = 1;
+			pwmop = ml->sml_op;
+			if ((deladd == 0) && (ml->sml_op == LDAP_MOD_DELETE) &&
+				(ml->sml_values) && !BER_BVISNULL( &ml->sml_values[0] ))
+			{
+				deladd = 1;
+				delmod = ml;
+			}
+
+			if ((ml->sml_op == LDAP_MOD_ADD) ||
+				(ml->sml_op == LDAP_MOD_REPLACE))
+			{
+				if ( ml->sml_values && !BER_BVISNULL( &ml->sml_values[0] )) {
+					if ( deladd == 1 )
+						deladd = 2;
+
+					/* FIXME: there's no easy way to ensure
+					 * that add does not cause multiple
+					 * userPassword values; one way (that 
+					 * would be consistent with the single
+					 * password constraint) would be to turn
+					 * add into replace); another would be
+					 * to disallow add.
+					 *
+					 * Let's check at least that a single value
+					 * is being added
+					 */
+					if ( addmod || !BER_BVISNULL( &ml->sml_values[ 1 ] ) ) {
+						rs->sr_err = LDAP_CONSTRAINT_VIOLATION; 
+						rs->sr_text = "Password policy only allows one password value";
+						goto return_results;
+					}
+
+					addmod = ml;
+				} else {
+					/* replace can have no values, add cannot */
+					assert( ml->sml_op == LDAP_MOD_REPLACE );
+				}
+			}
+
+		} else if ( !(ml->sml_flags & SLAP_MOD_INTERNAL) && !is_at_operational( ml->sml_desc->ad_type ) ) {
+			mod_pw_only = 0;
+			/* modifying something other than password */
+		}
+
+		/*
+		 * If there is a request to explicitly add a pwdReset
+		 * attribute, then we suppress the normal behaviour on
+		 * password change, which is to remove the pwdReset
+		 * attribute.
+		 *
+		 * This enables an administrator to assign a new password
+		 * and place a "must reset" flag on the entry, which will
+		 * stay until the user explicitly changes his/her password.
+		 */
+		if (ml->sml_desc == ad_pwdReset ) {
+			if ((ml->sml_op == LDAP_MOD_ADD) ||
+				(ml->sml_op == LDAP_MOD_REPLACE))
+				zapReset = 0;
+		}
+	}
+	
+	if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn ) && !mod_pw_only ) {
+		if ( dn_match( &op->o_conn->c_ndn,
+				&pwcons[op->o_conn->c_conn_idx].dn )) {
+			Debug( LDAP_DEBUG_TRACE,
+				"connection restricted to password changing only\n", 0, 0, 0 );
+			rs->sr_err = LDAP_INSUFFICIENT_ACCESS; 
+			rs->sr_text = "Operations are restricted to bind/unbind/abandon/StartTLS/modify password";
+			pErr = PP_changeAfterReset;
+			goto return_results;
+		} else {
+			ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+			BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+		}
+	}
+
+	/*
+	 * if we have a "safe password modify policy", then we need to check if we're doing
+	 * a delete (with the old password), followed by an add (with the new password).
+	 *
+	 * If we got just a delete with nothing else, just let it go. We also skip all the checks if
+	 * the root user is bound. Root can do anything, including avoid the policies.
+	 */
+
+	if (!pwmod) goto do_modify;
+
+	/*
+	 * Build the password history list in ascending time order
+	 * We need this, even if the user is root, in order to maintain
+	 * the pwdHistory operational attributes properly.
+	 */
+	if (addmod && pp.pwdInHistory > 0 && (ha = attr_find( e->e_attrs, ad_pwdHistory ))) {
+		struct berval oldpw;
+		time_t oldtime;
+
+		for(i=0; ha->a_nvals[i].bv_val; i++) {
+			rc = parse_pwdhistory( &(ha->a_nvals[i]), NULL,
+				&oldtime, &oldpw );
+
+			if (rc != LDAP_SUCCESS) continue; /* invalid history entry */
+
+			if (oldpw.bv_val) {
+				add_to_pwd_history( &tl, oldtime, &oldpw,
+					&(ha->a_nvals[i]) );
+				oldpw.bv_val = NULL;
+				oldpw.bv_len = 0;
+			}
+		}
+		for(p=tl; p; p=p->next, hsize++); /* count history size */
+	}
+
+	if (be_isroot( op )) goto do_modify;
+
+	if (!pp.pwdAllowUserChange) {
+		rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+		rs->sr_text = "User alteration of password is not allowed";
+		pErr = PP_passwordModNotAllowed;
+		goto return_results;
+	}
+
+	/* Just deleting? */
+	if (!addmod) {
+		/* skip everything else */
+		pwmod = 0;
+		goto do_modify;
+	}
+
+	/* This is a pwdModify exop that provided the old pw.
+	 * We need to create a Delete mod for this old pw and 
+	 * let the matching value get found later
+	 */
+	if (pp.pwdSafeModify && oldpw.bv_val ) {
+		ml = (Modifications *)ch_calloc( sizeof( Modifications ), 1 );
+		ml->sml_op = LDAP_MOD_DELETE;
+		ml->sml_flags = SLAP_MOD_INTERNAL;
+		ml->sml_desc = pp.ad;
+		ml->sml_type = pp.ad->ad_cname;
+		ml->sml_numvals = 1;
+		ml->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+		ber_dupbv( &ml->sml_values[0], &oldpw );
+		BER_BVZERO( &ml->sml_values[1] );
+		ml->sml_next = op->orm_modlist;
+		op->orm_modlist = ml;
+		delmod = ml;
+		deladd = 2;
+	}
+
+	if (pp.pwdSafeModify && deladd != 2) {
+		Debug( LDAP_DEBUG_TRACE,
+			"change password must use DELETE followed by ADD/REPLACE\n",
+			0, 0, 0 );
+		rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+		rs->sr_text = "Must supply old password to be changed as well as new one";
+		pErr = PP_mustSupplyOldPassword;
+		goto return_results;
+	}
+
+	/* Check age, but only if pwdReset is not TRUE */
+	pa = attr_find( e->e_attrs, ad_pwdReset );
+	if ((!pa || !bvmatch( &pa->a_nvals[0], &slap_true_bv )) &&
+		pp.pwdMinAge > 0) {
+		time_t pwtime = (time_t)-1, now;
+		int age;
+
+		if ((pa = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL)
+			pwtime = parse_time( pa->a_nvals[0].bv_val );
+		now = slap_get_time();
+		age = (int)(now - pwtime);
+		if ((pwtime != (time_t)-1) && (age < pp.pwdMinAge)) {
+			rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+			rs->sr_text = "Password is too young to change";
+			pErr = PP_passwordTooYoung;
+			goto return_results;
+		}
+	}
+
+	/* pa is used in password history check below, be sure it's set */
+	if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL && delmod) {
+		/*
+		 * we have a password to check
+		 */
+		const char *txt;
+		
+		bv = oldpw.bv_val ? &oldpw : delmod->sml_values;
+		/* FIXME: no access checking? */
+		rc = slap_passwd_check( op, NULL, pa, bv, &txt );
+		if (rc != LDAP_SUCCESS) {
+			Debug( LDAP_DEBUG_TRACE,
+				"old password check failed: %s\n", txt, 0, 0 );
+			
+			rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+			rs->sr_text = "Must supply correct old password to change to new one";
+			pErr = PP_mustSupplyOldPassword;
+			goto return_results;
+
+		} else {
+			int i;
+			
+			/*
+			 * replace the delete value with the (possibly hashed)
+			 * value which is currently in the password.
+			 */
+			for ( i = 0; !BER_BVISNULL( &delmod->sml_values[i] ); i++ ) {
+				free( delmod->sml_values[i].bv_val );
+				BER_BVZERO( &delmod->sml_values[i] );
+			}
+			free( delmod->sml_values );
+			delmod->sml_values = ch_calloc( sizeof(struct berval), 2 );
+			BER_BVZERO( &delmod->sml_values[1] );
+			ber_dupbv( &(delmod->sml_values[0]),  &(pa->a_nvals[0]) );
+		}
+	}
+
+	bv = newpw.bv_val ? &newpw : &addmod->sml_values[0];
+	if (pp.pwdCheckQuality > 0) {
+
+		rc = check_password_quality( bv, &pp, &pErr, e );
+		if (rc != LDAP_SUCCESS) {
+			rs->sr_err = rc;
+			rs->sr_text = "Password fails quality checking policy";
+			goto return_results;
+		}
+	}
+
+	/* If pwdInHistory is zero, passwords may be reused */
+	if (pa && pp.pwdInHistory > 0) {
+		/*
+		 * Last check - the password history.
+		 */
+		/* FIXME: no access checking? */
+		if (slap_passwd_check( op, NULL, pa, bv, &txt ) == LDAP_SUCCESS) {
+			/*
+			 * This is bad - it means that the user is attempting
+			 * to set the password to the same as the old one.
+			 */
+			rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+			rs->sr_text = "Password is not being changed from existing value";
+			pErr = PP_passwordInHistory;
+			goto return_results;
+		}
+	
+		/*
+		 * Iterate through the password history, and fail on any
+		 * password matches.
+		 */
+		at = *pa;
+		at.a_vals = cr;
+		cr[1].bv_val = NULL;
+		for(p=tl; p; p=p->next) {
+			cr[0] = p->pw;
+			/* FIXME: no access checking? */
+			rc = slap_passwd_check( op, NULL, &at, bv, &txt );
+			
+			if (rc != LDAP_SUCCESS) continue;
+			
+			rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+			rs->sr_text = "Password is in history of old passwords";
+			pErr = PP_passwordInHistory;
+			goto return_results;
+		}
+	}
+
+do_modify:
+	if (pwmod) {
+		struct berval timestamp;
+		char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+		time_t now = slap_get_time();
+
+		/* If the conn is restricted, set a callback to clear it
+		 * if the pwmod succeeds
+		 */
+		if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+			slap_callback *sc = op->o_tmpcalloc( 1, sizeof( slap_callback ),
+				op->o_tmpmemctx );
+			sc->sc_next = op->o_callback;
+			/* Must use sc_response to insure we reset on success, before
+			 * the client sees the response. Must use sc_cleanup to insure
+			 * that it gets cleaned up if sc_response is not called.
+			 */
+			sc->sc_response = ppolicy_mod_cb;
+			sc->sc_cleanup = ppolicy_mod_cb;
+			op->o_callback = sc;
+		}
+
+		/*
+		 * keep the necessary pwd.. operational attributes
+		 * up to date.
+		 */
+
+		timestamp.bv_val = timebuf;
+		timestamp.bv_len = sizeof(timebuf);
+		slap_timestamp( &now, &timestamp );
+
+		mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+		mods->sml_desc = ad_pwdChangedTime;
+		if (pwmop != LDAP_MOD_DELETE) {
+			mods->sml_op = LDAP_MOD_REPLACE;
+			mods->sml_numvals = 1;
+			mods->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+			ber_dupbv( &mods->sml_values[0], &timestamp );
+			BER_BVZERO( &mods->sml_values[1] );
+			assert( !BER_BVISNULL( &mods->sml_values[0] ) );
+
+		} else {
+			mods->sml_op = LDAP_MOD_DELETE;
+		}
+		mods->sml_flags = SLAP_MOD_INTERNAL;
+		mods->sml_next = NULL;
+		modtail->sml_next = mods;
+		modtail = mods;
+
+		if (attr_find(e->e_attrs, ad_pwdGraceUseTime )) {
+			mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+			mods->sml_op = LDAP_MOD_DELETE;
+			mods->sml_desc = ad_pwdGraceUseTime;
+			mods->sml_flags = SLAP_MOD_INTERNAL;
+			mods->sml_next = NULL;
+			modtail->sml_next = mods;
+			modtail = mods;
+		}
+
+		if (attr_find(e->e_attrs, ad_pwdAccountLockedTime )) {
+			mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+			mods->sml_op = LDAP_MOD_DELETE;
+			mods->sml_desc = ad_pwdAccountLockedTime;
+			mods->sml_flags = SLAP_MOD_INTERNAL;
+			mods->sml_next = NULL;
+			modtail->sml_next = mods;
+			modtail = mods;
+		}
+
+		if (attr_find(e->e_attrs, ad_pwdFailureTime )) {
+			mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+			mods->sml_op = LDAP_MOD_DELETE;
+			mods->sml_desc = ad_pwdFailureTime;
+			mods->sml_flags = SLAP_MOD_INTERNAL;
+			mods->sml_next = NULL;
+			modtail->sml_next = mods;
+			modtail = mods;
+		}
+		/* RGFA - delete on reset*/
+		if (attr_find(e->e_attrs, ad_pwdUniqueAttempts )) {
+			mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+			mods->sml_op = LDAP_MOD_DELETE;
+			mods->sml_desc = ad_pwdUniqueAttempts;
+			mods->sml_flags = SLAP_MOD_INTERNAL;
+			mods->sml_next = NULL;
+			modtail->sml_next = mods;
+			modtail = mods;
+		}
+
+		/* Delete the pwdReset attribute, since it's being reset */
+		if ((zapReset) && (attr_find(e->e_attrs, ad_pwdReset ))) {
+			mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+			mods->sml_op = LDAP_MOD_DELETE;
+			mods->sml_desc = ad_pwdReset;
+			mods->sml_flags = SLAP_MOD_INTERNAL;
+			mods->sml_next = NULL;
+			modtail->sml_next = mods;
+			modtail = mods;
+		}
+
+		if (pp.pwdInHistory > 0) {
+			if (hsize >= pp.pwdInHistory) {
+				/*
+				 * We use the >= operator, since we are going to add
+				 * the existing password attribute value into the
+				 * history - thus the cardinality of history values is
+				 * about to rise by one.
+				 *
+				 * If this would push it over the limit of history
+				 * values (remembering - the password policy could have
+				 * changed since the password was last altered), we must
+				 * delete at least 1 value from the pwdHistory list.
+				 *
+				 * In fact, we delete '(#pwdHistory attrs - max pwd
+				 * history length) + 1' values, starting with the oldest.
+				 * This is easily evaluated, since the linked list is
+				 * created in ascending time order.
+				 */
+				mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+				mods->sml_op = LDAP_MOD_DELETE;
+				mods->sml_flags = SLAP_MOD_INTERNAL;
+				mods->sml_desc = ad_pwdHistory;
+				mods->sml_numvals = hsize - pp.pwdInHistory + 1;
+				mods->sml_values = ch_calloc( sizeof( struct berval ),
+					hsize - pp.pwdInHistory + 2 );
+				BER_BVZERO( &mods->sml_values[ hsize - pp.pwdInHistory + 1 ] );
+				for(i=0,p=tl; i < (hsize - pp.pwdInHistory + 1); i++, p=p->next) {
+					BER_BVZERO( &mods->sml_values[i] );
+					ber_dupbv( &(mods->sml_values[i]), &p->bv );
+				}
+				mods->sml_next = NULL;
+				modtail->sml_next = mods;
+				modtail = mods;
+			}
+			free_pwd_history_list( &tl );
+
+			/*
+			 * Now add the existing password into the history list.
+			 * This will be executed even if the operation is to delete
+			 * the password entirely.
+			 *
+			 * This isn't in the spec explicitly, but it seems to make
+			 * sense that the password history list is the list of all
+			 * previous passwords - even if they were deleted. Thus, if
+			 * someone tries to add a historical password at some future
+			 * point, it will fail.
+			 */
+			if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL) {
+				mods = (Modifications *) ch_malloc( sizeof( Modifications ) );
+				mods->sml_op = LDAP_MOD_ADD;
+				mods->sml_flags = SLAP_MOD_INTERNAL;
+				mods->sml_type.bv_val = NULL;
+				mods->sml_desc = ad_pwdHistory;
+				mods->sml_nvalues = NULL;
+				mods->sml_numvals = 1;
+				mods->sml_values = ch_calloc( sizeof( struct berval ), 2 );
+				mods->sml_values[ 1 ].bv_val = NULL;
+				mods->sml_values[ 1 ].bv_len = 0;
+				make_pwd_history_value( timebuf, &mods->sml_values[0], pa );
+				mods->sml_next = NULL;
+				modtail->sml_next = mods;
+				modtail = mods;
+
+			} else {
+				Debug( LDAP_DEBUG_TRACE,
+				"ppolicy_modify: password attr lookup failed\n", 0, 0, 0 );
+			}
+		}
+
+		/*
+		 * Controversial bit here. If the new password isn't hashed
+		 * (ie, is cleartext), we probably should hash it according
+		 * to the default hash. The reason for this is that we want
+		 * to use the policy if possible, but if we hash the password
+		 * before, then we're going to run into trouble when it
+		 * comes time to check the password.
+		 *
+		 * Now, the right thing to do is to use the extended password
+		 * modify operation, but not all software can do this,
+		 * therefore it makes sense to hash the new password, now
+		 * we know it passes the policy requirements.
+		 *
+		 * Of course, if the password is already hashed, then we
+		 * leave it alone.
+		 */
+
+		if ((pi->hash_passwords) && (addmod) && !newpw.bv_val && 
+			(password_scheme( &(addmod->sml_values[0]), NULL ) != LDAP_SUCCESS))
+		{
+			struct berval hpw, bv;
+			
+			slap_passwd_hash( &(addmod->sml_values[0]), &hpw, &txt );
+			if (hpw.bv_val == NULL) {
+					/*
+					 * hashing didn't work. Emit an error.
+					 */
+				rs->sr_err = LDAP_OTHER;
+				rs->sr_text = txt;
+				goto return_results;
+			}
+			bv = addmod->sml_values[0];
+				/* clear and discard the clear password */
+			memset(bv.bv_val, 0, bv.bv_len);
+			ber_memfree(bv.bv_val);
+			addmod->sml_values[0] = hpw;
+		}
+	}
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	be_entry_release_r( op, e );
+	return SLAP_CB_CONTINUE;
+
+return_results:
+	free_pwd_history_list( &tl );
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	be_entry_release_r( op, e );
+	if ( send_ctrl ) {
+		LDAPControl *ctrl = NULL;
+
+		ctrl = create_passcontrol( -1, -1, pErr );
+		oldctrls = add_passcontrol( op, rs, ctrl );
+	}
+	send_ldap_result( op, rs );
+	if ( send_ctrl ) {
+		ctrls_cleanup( op, rs, oldctrls );
+	}
+	return rs->sr_err;
+}
+
+static int
+ppolicy_parseCtrl(
+	Operation *op,
+	SlapReply *rs,
+	LDAPControl *ctrl )
+{
+	if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+		rs->sr_text = "passwordPolicyRequest control value not absent";
+		return LDAP_PROTOCOL_ERROR;
+	}
+	op->o_ctrlflag[ppolicy_cid] = ctrl->ldctl_iscritical
+		? SLAP_CONTROL_CRITICAL
+		: SLAP_CONTROL_NONCRITICAL;
+
+	return LDAP_SUCCESS;
+}
+
+static int
+attrPretty(
+	Syntax *syntax,
+	struct berval *val,
+	struct berval *out,
+	void *ctx )
+{
+	AttributeDescription *ad = NULL;
+	const char *err;
+	int code;
+
+	code = slap_bv2ad( val, &ad, &err );
+	if ( !code ) {
+		ber_dupbv_x( out, &ad->ad_type->sat_cname, ctx );
+	}
+	return code;
+}
+
+static int
+attrNormalize(
+	slap_mask_t use,
+	Syntax *syntax,
+	MatchingRule *mr,
+	struct berval *val,
+	struct berval *out,
+	void *ctx )
+{
+	AttributeDescription *ad = NULL;
+	const char *err;
+	int code;
+
+	code = slap_bv2ad( val, &ad, &err );
+	if ( !code ) {
+		ber_str2bv_x( ad->ad_type->sat_oid, 0, 1, out, ctx );
+	}
+	return code;
+}
+
+static int
+ppolicy_db_init(
+	BackendDB *be,
+	ConfigReply *cr
+)
+{
+	slap_overinst *on = (slap_overinst *) be->bd_info;
+
+	/* Has User Schema been initialized yet? */
+	if ( !pwd_UsSchema[0].ad[0] ) {
+		const char *err;
+		int i, code;
+
+		for (i=0; pwd_UsSchema[i].def; i++) {
+			code = slap_str2ad( pwd_UsSchema[i].def, pwd_UsSchema[i].ad, &err );
+			if ( code ) {
+				if ( cr ){
+					snprintf( cr->msg, sizeof(cr->msg), 
+						"User Schema load failed for attribute \"%s\". Error code %d: %s",
+						pwd_UsSchema[i].def, code, err );
+					fprintf( stderr, "%s\n", cr->msg );
+				}
+				return code;
+			}
+		}
+		{
+			Syntax *syn;
+			MatchingRule *mr;
+
+			syn = ch_malloc( sizeof( Syntax ));
+			*syn = *ad_pwdAttribute->ad_type->sat_syntax;
+			syn->ssyn_pretty = attrPretty;
+			ad_pwdAttribute->ad_type->sat_syntax = syn;
+
+			mr = ch_malloc( sizeof( MatchingRule ));
+			*mr = *ad_pwdAttribute->ad_type->sat_equality;
+			mr->smr_normalize = attrNormalize;
+			ad_pwdAttribute->ad_type->sat_equality = mr;
+		}
+	}
+
+	on->on_bi.bi_private = ch_calloc( sizeof(pp_info), 1 );
+
+	if ( dtblsize && !pwcons ) {
+		/* accommodate for c_conn_idx == -1 */
+		pwcons = ch_calloc( sizeof(pw_conn), dtblsize + 1 );
+		pwcons++;
+	}
+
+	return 0;
+}
+
+static int
+ppolicy_db_open(
+	BackendDB *be,
+	ConfigReply *cr
+)
+{
+	ov_count++;
+	return overlay_register_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST );
+}
+
+static int
+ppolicy_close(
+	BackendDB *be,
+	ConfigReply *cr
+)
+{
+	slap_overinst *on = (slap_overinst *) be->bd_info;
+	pp_info *pi = on->on_bi.bi_private;
+
+	/* Perhaps backover should provide bi_destroy hooks... */
+	ov_count--;
+	if ( ov_count <=0 && pwcons ) {
+		pwcons--;
+		free( pwcons );
+		pwcons = NULL;
+	}
+	free( pi->def_policy.bv_val );
+	free( pi );
+
+	return 0;
+}
+
+static char *extops[] = {
+	LDAP_EXOP_MODIFY_PASSWD,
+	NULL
+};
+
+static slap_overinst ppolicy;
+
+int ppolicy_initialize()
+{
+	int i, code;
+
+	for (i=0; pwd_OpSchema[i].def; i++) {
+		code = register_at( pwd_OpSchema[i].def, pwd_OpSchema[i].ad, 0 );
+		if ( code ) {
+			Debug( LDAP_DEBUG_ANY,
+				"ppolicy_initialize: register_at failed\n", 0, 0, 0 );
+			return code;
+		}
+		/* Allow Manager to set these as needed */
+		if ( is_at_no_user_mod( (*pwd_OpSchema[i].ad)->ad_type )) {
+			(*pwd_OpSchema[i].ad)->ad_type->sat_flags |=
+				SLAP_AT_MANAGEABLE;
+		}
+	}
+
+	code = register_supported_control( LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+		SLAP_CTRL_ADD|SLAP_CTRL_BIND|SLAP_CTRL_MODIFY|SLAP_CTRL_HIDE, extops,
+		ppolicy_parseCtrl, &ppolicy_cid );
+	if ( code != LDAP_SUCCESS ) {
+		fprintf( stderr, "Failed to register control %d\n", code );
+		return code;
+	}
+
+	ldap_pvt_thread_mutex_init( &chk_syntax_mutex );
+
+	ppolicy.on_bi.bi_type = "ppolicy";
+	ppolicy.on_bi.bi_db_init = ppolicy_db_init;
+	ppolicy.on_bi.bi_db_open = ppolicy_db_open;
+	ppolicy.on_bi.bi_db_close = ppolicy_close;
+
+	ppolicy.on_bi.bi_op_add = ppolicy_add;
+	ppolicy.on_bi.bi_op_bind = ppolicy_bind;
+	ppolicy.on_bi.bi_op_compare = ppolicy_restrict;
+	ppolicy.on_bi.bi_op_delete = ppolicy_restrict;
+	ppolicy.on_bi.bi_op_modify = ppolicy_modify;
+	ppolicy.on_bi.bi_op_search = ppolicy_restrict;
+	ppolicy.on_bi.bi_connection_destroy = ppolicy_connection_destroy;
+
+	ppolicy.on_bi.bi_cf_ocs = ppolicyocs;
+	code = config_register_schema( ppolicycfg, ppolicyocs );
+	if ( code ) return code;
+
+	return overlay_register( &ppolicy );
+}
+
+#if SLAPD_OVER_PPOLICY == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+	return ppolicy_initialize();
+}
+#endif
+
+#endif	/* defined(SLAPD_OVER_PPOLICY) */
new file mode 100644
--- /dev/null
+++ b/ppolicy.schema
@@ -0,0 +1,605 @@
+# $OpenLDAP: pkg/ldap/servers/slapd/schema/ppolicy.schema,v 1.7.2.3 2008/02/11 23:26:49 kurt Exp $
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2004-2008 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>.
+#
+## Portions Copyright (C) The Internet Society (2004).
+## Please see full copyright statement below.
+
+# Definitions from Draft behera-ldap-password-policy-07 (a work in progress)
+#	Password Policy for LDAP Directories
+# With extensions from Hewlett-Packard:
+#	pwdCheckModule etc.
+
+# Contents of this file are subject to change (including deletion)
+# without notice.
+#
+# Not recommended for production use!
+# Use with extreme caution!
+
+#Network Working Group                                     J. Sermersheim
+#Internet-Draft                                               Novell, Inc
+#Expires: April 24, 2005                                        L. Poitou
+#                                                        Sun Microsystems
+#                                                        October 24, 2004
+#
+#
+#                  Password Policy for LDAP Directories
+#                draft-behera-ldap-password-policy-08.txt
+#
+#Status of this Memo
+#
+#   This document is an Internet-Draft and is subject to all provisions
+#   of section 3 of RFC 3667.  By submitting this Internet-Draft, each
+#   author represents that any applicable patent or other IPR claims of
+#   which he or she is aware have been or will be disclosed, and any of
+#   which he or she become aware will be disclosed, in accordance with
+#   RFC 3668.
+#
+#   Internet-Drafts are working documents of the Internet Engineering
+#   Task Force (IETF), its areas, and its working groups.  Note that
+#   other groups may also distribute working documents as
+#   Internet-Drafts.
+#
+#   Internet-Drafts are draft documents valid for a maximum of six months
+#   and may be updated, replaced, or obsoleted by other documents at any
+#   time.  It is inappropriate to use Internet-Drafts as reference
+#   material or to cite them other than as "work in progress."
+#
+#   The list of current Internet-Drafts can be accessed at
+#   http://www.ietf.org/ietf/1id-abstracts.txt.
+#
+#   The list of Internet-Draft Shadow Directories can be accessed at
+#   http://www.ietf.org/shadow.html.
+#
+#   This Internet-Draft will expire on April 24, 2005.
+#
+#Copyright Notice
+#
+#   Copyright (C) The Internet Society (2004).
+#
+#Abstract
+#
+#   Password policy as described in this document is a set of rules that
+#   controls how passwords are used and administered in Lightweight
+#   Directory Access Protocol (LDAP) based directories.  In order to
+#   improve the security of LDAP directories and make it difficult for
+#   password cracking programs to break into directories, it is desirable
+#   to enforce a set of rules on password usage.  These rules are made to
+#
+#  [trimmed]
+#
+#5.  Schema used for Password Policy
+#
+#   The schema elements defined here fall into two general categories.  A
+#   password policy object class is defined which contains a set of
+#   administrative password policy attributes, and a set of operational
+#   attributes are defined that hold general password policy state
+#   information for each user.
+#
+#5.2  Attribute Types used in the pwdPolicy ObjectClass
+#
+#   Following are the attribute types used by the pwdPolicy object class.
+#
+#5.2.1  pwdAttribute
+#
+#   This holds the name of the attribute to which the password policy is
+#   applied.  For example, the password policy may be applied to the
+#   userPassword attribute.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.1
+      NAME 'pwdAttribute'
+      EQUALITY objectIdentifierMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
+
+#5.2.2  pwdMinAge
+#
+#   This attribute holds the number of seconds that must elapse between
+#   modifications to the password.  If this attribute is not present, 0
+#   seconds is assumed.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.2
+      NAME 'pwdMinAge'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.3  pwdMaxAge
+#
+#   This attribute holds the number of seconds after which a modified
+#   password will expire.
+#
+#   If this attribute is not present, or if the value is 0 the password
+#   does not expire.  If not 0, the value must be greater than or equal
+#   to the value of the pwdMinAge.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.3
+      NAME 'pwdMaxAge'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.4  pwdInHistory
+#
+#   This attribute specifies the maximum number of used passwords stored
+#   in the pwdHistory attribute.
+#
+#   If this attribute is not present, or if the value is 0, used
+#   passwords are not stored in the pwdHistory attribute and thus may be
+#   reused.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.4
+      NAME 'pwdInHistory'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.5  pwdCheckQuality
+#
+#   {TODO: Consider changing the syntax to OID.  Each OID will list a
+#   quality rule (like min len, # of special characters, etc).  These
+#   rules can be specified outsid ethis document.}
+#
+#   {TODO: Note that even though this is meant to be a check that happens
+#   during password modification, it may also be allowed to happen during
+#   authN.  This is useful for situations where the password is encrypted
+#   when modified, but decrypted when used to authN.}
+#
+#   This attribute indicates how the password quality will be verified
+#   while being modified or added.  If this attribute is not present, or
+#   if the value is '0', quality checking will not be enforced.  A value
+#   of '1' indicates that the server will check the quality, and if the
+#   server is unable to check it (due to a hashed password or other
+#   reasons) it will be accepted.  A value of '2' indicates that the
+#   server will check the quality, and if the server is unable to verify
+#   it, it will return an error refusing the password.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.5
+      NAME 'pwdCheckQuality'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.6  pwdMinLength
+#
+#   When quality checking is enabled, this attribute holds the minimum
+#   number of characters that must be used in a password.  If this
+#   attribute is not present, no minimum password length will be
+#   enforced.  If the server is unable to check the length (due to a
+#   hashed password or otherwise), the server will, depending on the
+#   value of the pwdCheckQuality attribute, either accept the password
+#   without checking it ('0' or '1') or refuse it ('2').
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.6
+      NAME 'pwdMinLength'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.7  pwdExpireWarning
+#
+#   This attribute specifies the maximum number of seconds before a
+#   password is due to expire that expiration warning messages will be
+#   returned to an authenticating user.
+#
+#   If this attribute is not present, or if the value is 0 no warnings
+#   will be returned.  If not 0, the value must be smaller than the value
+#   of the pwdMaxAge attribute.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.7
+      NAME 'pwdExpireWarning'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.8  pwdGraceAuthNLimit
+#
+#   This attribute specifies the number of times an expired password can
+#   be used to authenticate.  If this attribute is not present or if the
+#   value is 0, authentication will fail.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.8
+      NAME 'pwdGraceAuthNLimit'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.9  pwdLockout
+#
+#   This attribute indicates, when its value is "TRUE", that the password
+#   may not be used to authenticate after a specified number of
+#   consecutive failed bind attempts.  The maximum number of consecutive
+#   failed bind attempts is specified in pwdMaxFailure.
+#
+#   If this attribute is not present, or if the value is "FALSE", the
+#   password may be used to authenticate when the number of failed bind
+#   attempts has been reached.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.9
+      NAME 'pwdLockout'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+#5.2.10  pwdLockoutDuration
+#
+#   This attribute holds the number of seconds that the password cannot
+#   be used to authenticate due to too many failed bind attempts.  If
+#   this attribute is not present, or if the value is 0 the password
+#   cannot be used to authenticate until reset by a password
+#   administrator.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.10
+      NAME 'pwdLockoutDuration'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.11  pwdMaxFailure
+#
+#   This attribute specifies the number of consecutive failed bind
+#   attempts after which the password may not be used to authenticate.
+#   If this attribute is not present, or if the value is 0, this policy
+#   is not checked, and the value of pwdLockout will be ignored.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.11
+      NAME 'pwdMaxFailure'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.12  pwdFailureCountInterval
+#
+#   This attribute holds the number of seconds after which the password
+#   failures are purged from the failure counter, even though no
+#   successful authentication occurred.
+#
+#   If this attribute is not present, or if its value is 0, the failure
+#   counter is only reset by a successful authentication.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.12
+      NAME 'pwdFailureCountInterval'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+#5.2.13  pwdMustChange
+#
+#   This attribute specifies with a value of "TRUE" that users must
+#   change their passwords when they first bind to the directory after a
+#   password is set or reset by a password administrator.  If this
+#   attribute is not present, or if the value is "FALSE", users are not
+#   required to change their password upon binding after the password
+#   administrator sets or resets the password.  This attribute is not set
+#   due to any actions specified by this document, it is typically set by
+#   a password administrator after resetting a user's password.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.13
+      NAME 'pwdMustChange'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+#5.2.14  pwdAllowUserChange
+#
+#   This attribute indicates whether users can change their own
+#   passwords, although the change operation is still subject to access
+#   control.  If this attribute is not present, a value of "TRUE" is
+#   assumed.  This attribute is intended to be used in the absense of an
+#   access control mechanism.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.14
+      NAME 'pwdAllowUserChange'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+#5.2.15  pwdSafeModify
+#
+#   This attribute specifies whether or not the existing password must be
+#   sent along with the new password when being changed.  If this
+#   attribute is not present, a "FALSE" value is assumed.
+
+attributetype ( 1.3.6.1.4.1.42.2.27.8.1.15
+      NAME 'pwdSafeModify'
+      EQUALITY booleanMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+      SINGLE-VALUE )
+
+# Mozilla extension
+# 5.2.16  pwdMaxTotalAttempts
+
+# This attribute may take one of three values:
+# a. Zero (0) This indicates that repeat password checking is not invoked. The value 0 is defaulted 
+#    if the attribute is not present.
+# b. A positive number in the range 1 to 65535 defines the maximum number of failed bind attempts
+#    using a repeated password after which any password may not be used to authenticate. 
+# c. -1 (minus 1). This indicates that repeat password detection features are required but an 
+#    unlimited number of repeat password attempts are allowed.
+# When repeat password detection is invoked (pwdMaxTotalAttempts is either -1 or in the range 
+# 1 to 65535) each failed password attempt is evaluated against the list maintained in 
+# pwdUnigueAttempts and if not present will be added to pwdUniqueAttempts and also to 
+# pwdFailureTime. Thus the first attempt to use any password will count toward the value 
+# defined in pwdMaxFailure. If the failed password is present in pwdUniqueAttempts it will 
+# only increment the appropriate counter in pwdUniqueAttempts if pwdMaxTotalAttempts is in 
+# the range 1 to 65535. When the sum of all non-expired counts in pwdUniqueAttempts equals 
+# pwdMaxTotalAttempts then the action defined by pwdLockout is invoked. 
+# If this attribute is not present (defaults to 0), or if the value is 0, no repeat password 
+# detection is invoked and any failed password attempt (whether repeat or unique) will count 
+# toward the value defined by pwdMaxFailure. If the value is set to -1 then an unlimited number 
+# of repeat password attempts are allowed.
+
+attributetype  ( 1.3.6.1.4.1.13769.1.3.1
+      NAME 'pwdMaxTotalAttempts'
+      EQUALITY integerMatch
+      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+      SINGLE-VALUE )
+
+
+
+# HP extensions
+#
+# pwdCheckModule
+#
+#    This attribute names a user-defined loadable module that provides
+#    a check_password() function. If pwdCheckQuality is set to '1' or '2'
+#    this function will be called after all of the internal password
+#    quality checks have been passed. The function has this prototype:
+#
+#    int check_password( char *password, char **errormessage, void *arg )
+#
+#    The function should return LDAP_SUCCESS for a valid password.
+
+attributetype ( 1.3.6.1.4.1.4754.1.99.1
+     NAME 'pwdCheckModule'
+     EQUALITY caseExactIA5Match
+     SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+     DESC 'Loadable module that instantiates "check_password() function'
+     SINGLE-VALUE )
+
+objectclass ( 1.3.6.1.4.1.4754.2.99.1
+      NAME 'pwdPolicyChecker'
+      SUP top
+      AUXILIARY
+      MAY ( pwdCheckModule ) )
+
+#5.1  The pwdPolicy Object Class
+#
+#   This object class contains the attributes defining a password policy
+#   in effect for a set of users.  Section 10 describes the
+#   administration of this object, and the relationship between it and
+#   particular objects.
+#
+objectclass ( 1.3.6.1.4.1.42.2.27.8.2.1
+      NAME 'pwdPolicy'
+      SUP top
+      AUXILIARY
+      MUST ( pwdAttribute )
+      MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $
+      pwdMinLength $ pwdExpireWarning $ pwdGraceAuthNLimit $ pwdLockout
+      $ pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $
+      pwdMustChange $ pwdAllowUserChange $ pwdMaxTotalAttempts $ pwdSafeModify ) )
+
+#5.3  Attribute Types for Password Policy State Information
+#
+#   Password policy state information must be maintained for each user.
+#   The information is located in each user entry as a set of operational
+#   attributes.  These operational attributes are: pwdChangedTime,
+#   pwdAccountLockedTime, pwdFailureTime, pwdHistory, pwdGraceUseTime,
+#   pwdReset, pwdPolicySubEntry.
+#
+#5.3.1  Password Policy State Attribute Option
+#
+#   Since the password policy could apply to several attributes used to
+#   store passwords, each of the above operational attributes must have
+#   an option to specify which pwdAttribute it applies to.  The password
+#   policy option is defined as the following:
+#
+#   pwd-<passwordAttribute>
+#
+#   where passwordAttribute a string following the OID syntax
+#   (1.3.6.1.4.1.1466.115.121.1.38).  The attribute type descriptor
+#   (short name) MUST be used.
+#
+#   For example, if the pwdPolicy object has for pwdAttribute
+#   "userPassword" then the pwdChangedTime operational attribute, in a
+#   user entry, will be:
+#
+#   pwdChangedTime;pwd-userPassword: 20000103121520Z
+#
+#   This attribute option follows sub-typing semantics.  If a client
+#   requests a password policy state attribute to be returned in a search
+#   operation, and does not specify an option, all subtypes of that
+#   policy state attribute are returned.
+#
+#5.3.2  pwdChangedTime
+#
+#   This attribute specifies the last time the entry's password was
+#   changed.  This is used by the password expiration policy.  If this
+#   attribute does not exist, the password will never expire.
+#
+#      ( 1.3.6.1.4.1.42.2.27.8.1.16
+#      NAME 'pwdChangedTime'
+#      DESC 'The time the password was last changed'
+#      EQUALITY generalizedTimeMatch
+#      ORDERING generalizedTimeOrderingMatch
+#      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+#      SINGLE-VALUE
+#      USAGE directoryOperation )
+#
+#5.3.3  pwdAccountLockedTime
+#
+#   This attribute holds the time that the user's account was locked.  A
+#   locked account means that the password may no longer be used to
+#   authenticate.  A 000001010000Z value means that the account has been
+#   locked permanently, and that only a password administrator can unlock
+#   the account.
+#
+#      ( 1.3.6.1.4.1.42.2.27.8.1.17
+#      NAME 'pwdAccountLockedTime'
+#      DESC 'The time an user account was locked'
+#      EQUALITY generalizedTimeMatch
+#      ORDERING generalizedTimeOrderingMatch
+#      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+#      SINGLE-VALUE
+#      USAGE directoryOperation )
+#
+#5.3.4  pwdFailureTime
+#
+#   This attribute holds the timestamps of the consecutive authentication
+#   failures.
+#
+#      ( 1.3.6.1.4.1.42.2.27.8.1.19
+#      NAME 'pwdFailureTime'
+#      DESC 'The timestamps of the last consecutive authentication
+#      failures'
+#      EQUALITY generalizedTimeMatch
+#      ORDERING generalizedTimeOrderingMatch
+#      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+#      USAGE directoryOperation )
+#
+#5.3.5  pwdHistory
+#
+#   This attribute holds a history of previously used passwords.  Values
+#   of this attribute are transmitted in string format as given by the
+#   following ABNF:
+#
+#   pwdHistory = time "#" syntaxOID "#" length "#" data
+#
+#   time       = <generalizedTimeString as specified in 6.14
+#                 of [RFC2252]>
+#
+#   syntaxOID  = numericoid    ; the string representation of the
+#                              ; dotted-decimal OID that defines the
+#                              ; syntax used to store the password.
+#                              ; numericoid is described in 4.1
+#                              ; of [RFC2252].
+#
+#   length     = numericstring ; the number of octets in data.
+#                              ; numericstring is described in 4.1
+#                              ; of [RFC2252].
+#
+#   data       = <octets representing the password in the format
+#                 specified by syntaxOID>.
+#
+#   This format allows the server to store, and transmit a history of
+#   passwords that have been used.  In order for equality matching to
+#   function properly, the time field needs to adhere to a consistent
+#   format.  For this purpose, the time field MUST be in GMT format.
+#
+#      ( 1.3.6.1.4.1.42.2.27.8.1.20
+#      NAME 'pwdHistory'
+#      DESC 'The history of user s passwords'
+#      EQUALITY octetStringMatch
+#      SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+#      USAGE directoryOperation )
+#
+#5.3.6  pwdGraceUseTime
+#
+#   This attribute holds the timestamps of grace authentications after a
+#   password has expired.
+#
+#      ( 1.3.6.1.4.1.42.2.27.8.1.21
+#      NAME 'pwdGraceUseTime'
+#      DESC 'The timestamps of the grace authentication after the
+#      password has expired'
+#      EQUALITY generalizedTimeMatch
+#      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+#
+#5.3.7  pwdReset
+#
+#   This attribute holds a flag to indicate (when TRUE) that the password
+#   has been updated by the password administrator and must be changed by
+#   the user on first authentication.
+#
+#      ( 1.3.6.1.4.1.42.2.27.8.1.22
+#      NAME 'pwdReset'
+#      DESC 'The indication that the password has been reset'
+#      EQUALITY booleanMatch
+#      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+#      SINGLE-VALUE
+#      USAGE directoryOperation )
+#
+#5.3.8  pwdPolicySubentry
+#
+#   This attribute points to the pwdPolicy subentry in effect for this
+#   object.
+#
+#      ( 1.3.6.1.4.1.42.2.27.8.1.23
+#      NAME 'pwdPolicySubentry'
+#      DESC 'The pwdPolicy subentry in effect for this object'
+#      EQUALITY distinguishedNameMatch
+#      SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+#      SINGLE-VALUE
+#      USAGE directoryOperation )
+#
+#
+# 5.3.10  pwdUniqueAttempts
+
+# This attribute holds a history of previously failed passwords 
+# attempts up to the limit defined by pwdMaxFailure. Values of this
+# attribute are transmitted in string format as given by the following
+# ABNF:
+#
+#  pwdUniqueAttempts = time "#" count "#" length "#" data
+#
+#  time       = <generalizedTimeString as specified in 6.14
+#                of [RFC2252]>.
+#
+#  count  = Integer    ;       the count of uses of this password
+#                             ;Integer is described in 6.16 of [RFC2252]
+#
+#  length     = numericstring ; the number of octets in data.
+#                             ; numericstring is described in 4.1
+#                             ; of [RFC2252].
+#
+#  data       = <octets representing the password in the default hash format
+#             for the server
+#
+# In order for equality matching to function properly, the time field 
+# needs to adhere to a consistent format.  For this purpose, the time 
+# field MUST be in GMT (UCT) format. The time field is set only when 
+# the attribute is initially added.
+#
+#     (1.3.6.1.4.1.13769.1.3.2
+#     NAME 'pwdUniqueAttempts'
+#     DESC 'History of unique passwords attempts'
+#     EQUALITY octetStringMatch
+#     SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+#     NO-USER-MODIFICATION
+#     USAGE directoryOperation )
+# When a failed password attempt occurs and the value of pwdMaxTotalAttempts
+# is non 0 then pwdUniqueAttempts is searched for a match. If a match occurs
+# then the count field of this entry is incremented only if the value of 
+# pwdMaxTotalAttempts is positive. If no match occurs then an entry is made 
+# in pwdFailureTime and in pwdUniqueAttempts (with a count field value of 1). 
+# If pwdMaxTotalAttempts is positive then when the sum of the count field 
+# values of all items in pwdUniqueAttempts equals pwdMaxTotalAttempts the 
+# action taken is defined by pwdLockout.
+
+#Disclaimer of Validity
+#
+#   This document and the information contained herein are provided on an
+#   "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS
+#   OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET
+#   ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED,
+#   INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE
+#   INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+#   WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+#
+#
+#Copyright Statement
+#
+#   Copyright (C) The Internet Society (2004).  This document is subject
+#   to the rights, licenses and restrictions contained in BCP 78, and
+#   except as set forth therein, the authors retain all their rights.
+
new file mode 100644
--- /dev/null
+++ b/slapo-ppolicy.5
@@ -0,0 +1,877 @@
+.\" $OpenLDAP: pkg/ldap/doc/man/man5/slapo-ppolicy.5,v 1.12.2.7 2008/04/24 08:15:34 hyc Exp $
+.\" Copyright 2004-2008 The OpenLDAP Foundation All Rights Reserved.
+.\" Copying restrictions apply.  See COPYRIGHT/LICENSE.
+.TH SLAPO_PPOLICY 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.SH NAME
+slapo-ppolicy \- Password Policy overlay to slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+.LP
+The 
+.B ppolicy
+overlay
+is an implementation of the most recent IETF Password
+Policy proposal for LDAP.   When instantiated, it intercepts,
+decodes and applies specific password policy controls to overall
+use of a backend database, changes to user password fields, etc.
+.P
+The overlay provides a variety of password control mechanisms.  They
+include password aging--both minimum and maximum ages, password
+reuse and duplication control, account time-outs, mandatory password
+resets, acceptable password content, and even grace logins.
+Different groups of users may be associated with different password
+policies, and there is no limit to the number of password policies
+that may be created.
+.P
+Note that some of the policies do not take effect when the operation
+is performed with the
+.B rootdn
+identity; all the operations, when performed with any other identity,
+may be subjected to constraints, like access control.
+.P
+Note that the IETF Password Policy proposal for LDAP makes sense
+when considering a single-valued password attribute, while 
+the userPassword attribute allows multiple values.  This implementation
+enforces a single value for the userPassword attribute, despite
+its specification.
+
+.SH CONFIGURATION
+These 
+.B slapd.conf
+configuration options apply to the ppolicy overlay. They should appear
+after the
+.B overlay
+directive.
+.TP
+.B ppolicy_default <policyDN>
+Specify the DN of the pwdPolicy object to use when no specific policy is
+set on a given user's entry. If there is no specific policy for an entry
+and no default is given, then no policies will be enforced.
+.TP
+.B ppolicy_hash_cleartext
+Specify that cleartext passwords present in Add and Modify requests should
+be hashed before being stored in the database. This violates the X.500/LDAP
+information model, but may be needed to compensate for LDAP clients that
+don't use the Password Modify extended operation to manage passwords.  It
+is recommended that when this option is used that compare, search, and
+read access be denied to all directory users. 
+.TP
+.B ppolicy_use_lockout
+A client will always receive an LDAP
+.B InvalidCredentials
+response when
+Binding to a locked account. By default, when a Password Policy control
+was provided on the Bind request, a Password Policy response will be
+included with no special error code set. This option changes the
+Password Policy response to include the
+.B AccountLocked
+error code. Note
+that sending the
+.B AccountLocked
+error code provides useful information
+to an attacker; sites that are sensitive to security issues should not
+enable this option.
+
+.SH OBJECT CLASS
+The 
+.B ppolicy
+overlay depends on the
+.B pwdPolicy
+object class.  The definition of that class is as follows:
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.2.1
+    NAME 'pwdPolicy'
+    AUXILIARY
+    SUP top
+    MUST ( pwdAttribute )
+    MAY (
+        pwdMinAge $ pwdMaxAge $ pwdInHistory $
+        pwdCheckQuality $ pwdMinLength $
+        pwdExpireWarning $ pwdGraceAuthnLimit $
+        pwdLockout $ pwdLockoutDuration $
+        pwdMaxFailure $ pwdFailureCountInterval $
+        pwdMustChange $ pwdAllowUserChange $
+        pwdSafeModify $ pwdMaxTotalAttempts) )
+.RE
+
+This implementation also provides an additional
+.B pwdPolicyChecker
+objectclass, used for password quality checking (see below).
+.LP
+.RS 4
+(  1.3.6.1.4.1.4754.2.99.1
+    NAME 'pwdPolicyChecker'
+    AUXILIARY
+    SUP top
+    MAY ( pwdCheckModule ) )
+.RE
+.P
+Every account that should be subject to password policy control should
+have a
+.B
+pwdPolicySubentry
+attribute containing the DN of a valid
+.B pwdPolicy
+entry, or they can simply use the configured default.
+In this way different users may be managed according to
+different policies.
+
+.SH OBJECT CLASS ATTRIBUTES
+.P
+Each one of the sections below details the meaning and use of a particular
+attribute of this
+.B pwdPolicy
+object class.
+.P
+
+.B pwdAttribute
+.P
+This attribute contains the name of the attribute to which the password
+policy is applied. For example, the password policy may be applied
+to the
+.B userPassword
+attribute.
+.P
+Note: in this implementation, the only
+value accepted for
+.B pwdAttribute
+is
+.IR " userPassword ".
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.1
+   NAME 'pwdAttribute'
+   EQUALITY objectIdentifierMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )
+.RE
+
+.B pwdMinAge
+.P
+This attribute contains the number of seconds that must elapse
+between modifications allowed to the password. If this attribute
+is not present, zero seconds is assumed (i.e. the password may be
+modified whenever and however often is desired).
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.2
+   NAME 'pwdMinAge'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdMaxAge
+.P
+This attribute contains the number of seconds after which a modified
+password will expire.  If this attribute is not present, or if its
+value is zero (0), then passwords will not expire.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.3
+   NAME 'pwdMaxAge'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdInHistory
+.P
+This attribute is used to specify the maximum number of used
+passwords that will be stored in the
+.B pwdHistory
+attribute.  If the
+.B pwdInHistory
+attribute is not present, or if its value is
+zero (0), used passwords will not be stored in
+.B pwdHistory
+and thus any previously-used password may be reused.
+No history checking occurs if the password is being modified by the
+.BR rootdn ,
+although the password is saved in the history.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.4
+   NAME 'pwdInHistory'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdCheckQuality
+.P
+This attribute indicates if and how password syntax will be checked
+while a password is being modified or added. If this attribute is
+not present, or its value is zero (0), no syntax checking will be
+done. If its value is one (1), the server will check the syntax,
+and if the server is unable to check the syntax,
+whether due to a client-side hashed password or some other reason,
+it will be
+accepted. If its value is two (2), the server will check the syntax,
+and if the server is unable to check the syntax it will return an
+error refusing the password.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.5
+   NAME 'pwdCheckQuality'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdMinLength
+.P
+When syntax checking is enabled
+(see also the
+.B pwdCheckQuality
+attribute), this attribute contains the minimum
+number of characters that will be accepted in a password. If this
+attribute is not present, minimum password length is not
+enforced. If the server is unable to check the length of the password,
+whether due to a client-side hashed password or some other reason,
+the server will, depending on the
+value of
+.BR pwdCheckQuality ,
+either accept the password
+without checking it (if
+.B pwdCheckQuality
+is zero (0) or one (1)) or refuse it (if
+.B pwdCheckQuality
+is two (2)).
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.6
+   NAME 'pwdMinLength'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdExpireWarning
+.P
+This attribute contains the maximum number of seconds before a
+password is due to expire that expiration warning messages will be
+returned to a user who is authenticating to the directory.
+If this attribute is not
+present, or if the value is zero (0), no warnings will be sent.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.7
+   NAME 'pwdExpireWarning'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdGraceAuthnLimit
+.P
+This attribute contains the number of times that an expired password
+may be used to authenticate a user to the directory. If this
+attribute is not present or if its value is zero (0), users with
+expired passwords will not be allowed to authenticate to the
+directory.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.8
+   NAME 'pwdGraceAuthnLimit'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdLockout
+.P
+This attribute specifies the action that should be taken
+by the directory when a user has made a number of failed attempts
+to authenticate to the directory.  If
+.B pwdLockout
+is set (its value is "TRUE"), the user will not be allowed to
+attempt to authenticate to the directory after there have been a
+specified number of consecutive failed bind attempts.  The maximum
+number of consecutive failed bind attempts allowed is specified by
+the
+.B pwdMaxFailure
+attribute.  If
+.B pwdLockout
+is not present, or if its value is "FALSE", the password may be
+used to authenticate no matter how many consecutive failed bind
+attempts have been made.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.9
+   NAME 'pwdLockout'
+   EQUALITY booleanMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+   SINGLE-VALUE )
+.RE
+
+.B pwdLockoutDuration
+.P
+This attribute contains the number of seconds during
+which the password cannot be used to authenticate the
+user to the directory due to too many consecutive failed
+bind attempts.
+(See also
+.B pwdLockout
+and
+.BR pwdMaxFailure .)
+If
+.B pwdLockoutDuration
+is not present, or if its value is zero (0), the password
+cannot be used to authenticate the user to the directory
+again until it is reset by an administrator.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.10
+   NAME 'pwdLockoutDuration'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdMaxFailure
+.P
+This attribute contains the number of consecutive failed bind
+attempts after which the password may not be used to authenticate
+a user to the directory.
+If
+.B pwdMaxFailure
+is not present, or its value is zero (0), then a user will
+be allowed to continue to attempt to authenticate to
+the directory, no matter how many consecutive failed 
+bind attempts have occurred with that user's DN.
+(See also
+.B pwdLockout
+and
+.BR pwdLockoutDuration .)
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.11
+   NAME 'pwdMaxFailure'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdMaxTotalAttempts
+.P
+This optional attribute modifies the behavior of
+.B pwdMaxFailure
+by differentiating between failed bind attempts using a 
+repeated password (typically from client applications that
+cache passwords) and failed passwords using a unique password.
+If
+.B pwdMaxTotalAttempts
+is not present, or its value is zero (0), then no repeat password
+checking will be performed and account lockout will be controlled
+by the value of 
+.B pwdMaxFailure .
+If
+.B pwdMaxTotalAttempts
+is set to a value other than zero (0) then the password used in 
+each consecutive failed bind attempt is inspected. If the password
+is unique then it will be counted toward the threshold defined by
+.B pwdMaxFailure .
+If the password is a repeat (one previously used since the last 
+successful authentication) it will not count toward the threshold 
+defined by
+.B pwdMaxFailure .
+If
+.B pwdMaxTotalAttempts
+is -1 then an unlimited number of consecutive bind attempts using
+a repeat password are permitted. If
+.B pwdMaxTotalAttempts
+is a positive number (> 0) then this value defines the maximum number
+of failed bind attempts (repeat plus unique) that are allowed before 
+the account is locked.
+(See also
+.B pwdMaxFailure
+,
+.B pwdLockout
+and 
+.BR pwdLockoutDuration .)
+.LP
+.RS 4
+(  1.3.6.1.4.1.13769.1.3.1
+   NAME 'pwdMaxTotalAttempts'
+   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
+consecutive failed bind attempts are purged from the failure counter,
+even though no successful authentication has occurred.
+If
+.B pwdFailureCountInterval
+is not present, or its value is zero (0), the failure
+counter will only be reset by a successful authentication.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.12
+   NAME 'pwdFailureCountInterval'
+   EQUALITY integerMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+   SINGLE-VALUE )
+.RE
+
+.B pwdMustChange
+.P
+This attribute specifies whether users must change their passwords
+when they first bind to the directory after a password is set or
+reset by the administrator, or not.  If
+.B pwdMustChange
+has a value of "TRUE", users must change their passwords when they
+first bind to the directory after a password is set or reset by
+the administrator.  If
+.B pwdMustChange
+is not present, or its value is "FALSE",
+users are not required to change their password upon binding after
+the administrator sets or resets the password.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.13
+  NAME 'pwdMustChange'
+  EQUALITY booleanMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+  SINGLE-VALUE )
+.RE
+
+.B pwdAllowUserChange
+.P
+This attribute specifies whether users are allowed to change their own
+passwords or not.  If
+.B pwdAllowUserChange
+is set to "TRUE", or if the attribute is not present, users will be
+allowed to change their own passwords.  If its value is "FALSE",
+users will not be allowed to change their own passwords.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.14
+   NAME 'pwdAllowUserChange'
+   EQUALITY booleanMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+   SINGLE-VALUE )
+.RE
+
+.B pwdSafeModify
+.P
+This attribute denotes whether the user's existing password must be sent
+along with their new password when changing a password.  If
+.B pwdSafeModify
+is set to "TRUE", the existing password must be sent
+along with the new password.  If the attribute is not present, or
+its value is "FALSE", the existing password need not be sent
+along with the new password.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.15
+   NAME 'pwdSafeModify'
+   EQUALITY booleanMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+   SINGLE-VALUE )
+.RE
+
+.B pwdCheckModule
+.P
+This attribute names a user-defined loadable module that must
+instantiate the check_password() function.  This function
+will be called to further check a new password if
+.B pwdCheckQuality
+is set to one (1) or two (2),
+after all of the built-in password compliance checks have
+been passed.  This function will be called according to this
+function prototype:
+.RS 4
+int
+.I check_password
+(char *pPasswd, char **ppErrStr, Entry *pEntry);
+.RE
+The
+.B pPasswd
+parameter contains the clear-text user password, the
+.B ppErrStr
+parameter contains a double pointer that allows the function
+to return human-readable details about any error it encounters.
+The optional
+.B pEntry
+parameter, if non-NULL, carries a pointer to the
+entry whose password is being checked.
+If
+.B ppErrStr
+is NULL, then 
+.I funcName
+must NOT attempt to use it/them.
+A return value of LDAP_SUCCESS from the called
+function indicates that the password is ok, any other value
+indicates that the password is unacceptable.  If the password is
+unacceptable, the server will return an error to the client, and
+.B ppErrStr
+may be used to return a human-readable textual explanation of the
+error. The error string must be dynamically allocated as it will
+be free()'d by slapd.
+.LP
+.RS 4
+(  1.3.6.1.4.1.4754.1.99.1
+   NAME 'pwdCheckModule'
+   EQUALITY caseExactIA5Match
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+   SINGLE-VALUE )
+.RE
+.P
+Note: 
+The user-defined loadable module named by
+.B pwdCheckModule     
+must be in
+.B slapd's
+standard executable search PATH.
+.P
+Note:
+.B pwdCheckModule
+is a non-standard extension to the LDAP password
+policy proposal.
+
+.SH OPERATIONAL ATTRIBUTES
+.P
+The operational attributes used by the
+.B ppolicy
+module are stored in the user's entry.  Most of these attributes
+are not intended to be changed directly by users; they are there
+to track user activity.  They have been detailed here so that
+administrators and users can both understand the workings of
+the
+.B ppolicy
+module.
+
+.P
+Note that the current IETF Password Policy proposal does not define
+how these operational attributes are expected to behave in a
+replication environment. In general, authentication attempts on
+a slave server only affect the copy of the operational attributes
+on that slave and will not affect any attributes for
+a user's entry on the master server. Operational attribute changes
+resulting from authentication attempts on a master server
+will usually replicate to the slaves (and also overwrite
+any changes that originated on the slave). 
+These behaviors are not guaranteed and are subject to change
+when a formal specification emerges.
+
+.B userPassword
+.P
+The
+.B userPassword
+attribute is not strictly part of the
+.B ppolicy
+module.  It is, however, the attribute that is tracked and controlled
+by the module.  Please refer to the standard OpenLDAP schema for
+its definition.
+
+.B pwdPolicySubentry
+.P
+This attribute refers directly to the
+.B pwdPolicy
+subentry that is to be used for this particular directory user.
+If
+.B pwdPolicySubentry
+exists, it must contain the DN of a valid
+.B pwdPolicy
+object.  If it does not exist, the
+.B ppolicy
+module will enforce the default password policy rules on the
+user associated with this authenticating DN. If there is no
+default, or the referenced subentry does not exist, then no
+policy rules will be enforced.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.23
+   NAME 'pwdPolicySubentry'
+   DESC 'The pwdPolicy subentry in effect for
+       this object'
+   EQUALITY distinguishedNameMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+   SINGLE-VALUE
+   NO-USER-MODIFICATION
+   USAGE directoryOperation)
+.RE
+
+.B pwdChangedTime
+.P
+This attribute denotes the last time that the entry's password was
+changed.  This value is used by the password expiration policy to
+determine whether the password is too old to be allowed to be used
+for user authentication.  If
+.B pwdChangedTime
+does not exist, the user's password will not expire.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.16
+   NAME 'pwdChangedTime'
+   DESC 'The time the password was last changed'
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+   EQUALITY generalizedTimeMatch
+   ORDERING generalizedTimeOrderingMatch
+   SINGLE-VALUE
+   NO-USER-MODIFICATION
+   USAGE directoryOperation)
+.RE
+
+.B pwdAccountLockedTime
+.P
+This attribute contains the time that the user's account was locked.
+If the account has been locked, the password may no longer be used to
+authenticate the user to the directory.  If
+.B pwdAccountLockedTime   
+is set to 000001010000Z, the user's account has been permanently locked
+and may only be unlocked by an administrator.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.17
+   NAME 'pwdAccountLockedTime'
+   DESC 'The time an user account was locked'
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+   EQUALITY generalizedTimeMatch
+   ORDERING generalizedTimeOrderingMatch
+   SINGLE-VALUE
+   NO-USER-MODIFICATION
+   USAGE directoryOperation)
+.RE
+
+.B pwdFailureTime
+.P
+This attribute contains the timestamps of each of the consecutive
+authentication failures made upon attempted authentication to this
+DN (i.e. account).  If too many timestamps accumulate here (refer to
+the
+.B pwdMaxFailure
+password policy attribute for details),
+and the
+.B pwdLockout
+password policy attribute is set to "TRUE", the
+account may be locked.
+(Please also refer to the
+.B pwdLockout
+password policy attribute.)
+Excess timestamps beyond those allowed by
+.B pwdMaxFailure
+may also be purged.  If a successful authentication is made to this
+DN (i.e. to this user account), then
+.B pwdFailureTime   
+will be cleansed of entries.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.19
+   NAME 'pwdFailureTime'
+   DESC 'The timestamps of the last consecutive
+       authentication failures'
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+   EQUALITY generalizedTimeMatch
+   ORDERING generalizedTimeOrderingMatch
+   NO-USER-MODIFICATION
+   USAGE directoryOperation )
+.RE
+
+.B pwdUniqueAttempts
+.P
+This attribute contains the history of unique passwords and is only
+used when the user attribute
+.B pwdMaxTotalAttempts
+is present and non zero.
+The values of this attribute are stored in string format as follows:
+
+.RS 4
+
+pwdUniqueAttempts=
+.RS 4
+time "#" count "#" length "#" data
+.RE
+
+time=
+.RS 4
+GeneralizedTime as specified in section 3.3.13 of [RFC4517]
+.RE
+
+.P
+count = integer
+.RS 4
+The count of uses of this password. Integer is described 
+in 6.16 of [RFC2252]
+
+.RE
+
+length = NumericString
+.RS 4
+The number of octets in the data. NumericString is described in
+section 3.3.23 of [RFC4517].
+.RE
+
+data =
+.RS 4
+Octets representing the password in the default hash format for
+the server.
+.RE
+
+.RE
+
+This format allows the server to store and transmit a history of
+passwords that have been used.  In order for equality matching
+on the values in this attribute to function properly, the time
+field is in GMT format.
+When the account is locked, reset by an administrator or a 
+successful authentication occurs all attribute are deleted.
+.LP
+.RS 4
+(  1.3.6.1.4.1.13769.1.3.2
+   NAME 'pwdUniqueAttempts'
+   DESC 'History of unique password attempts'
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+   EQUALITY octetStringMatch
+   NO-USER-MODIFICATION
+   USAGE directoryOperation)
+.RE
+
+.B pwdHistory
+.P
+This attribute contains the history of previously used passwords
+for this DN (i.e. for this user account).
+The values of this attribute are stored in string format as follows:
+
+.RS 4
+
+pwdHistory=
+.RS 4
+time "#" syntaxOID "#" length "#" data
+.RE
+
+time=
+.RS 4
+GeneralizedTime as specified in section 3.3.13 of [RFC4517]
+.RE
+
+.P
+syntaxOID = numericoid
+.RS 4
+This is the string representation of the dotted-decimal OID that
+defines the syntax used to store the password.  numericoid is
+described in section 1.4 of [RFC4512].
+.RE
+
+length = NumericString
+.RS 4
+The number of octets in the data.  NumericString is described in
+section 3.3.23 of [RFC4517].
+.RE
+
+data =
+.RS 4
+Octets representing the password in the format specified by syntaxOID.
+.RE
+
+.RE
+
+This format allows the server to store and transmit a history of
+passwords that have been used.  In order for equality matching
+on the values in this attribute to function properly, the time
+field is in GMT format.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.20
+   NAME 'pwdHistory'
+   DESC 'The history of user passwords'
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
+   EQUALITY octetStringMatch
+   NO-USER-MODIFICATION
+   USAGE directoryOperation)
+.RE
+
+.B pwdGraceUseTime
+This attribute contains the list of timestamps of logins made after
+the user password in the DN has expired.  These post-expiration
+logins are known as "\fIgrace logins\fP".
+If too many
+.I grace logins
+have been used (please refer to the
+.B pwdGraceLoginLimit
+password policy attribute), then the DN will no longer be allowed
+to be used to authenticate the user to the directory until the
+administrator changes the DN's
+.B userPassword
+attribute.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.21
+   NAME 'pwdGraceUseTime'
+   DESC 'The timestamps of the grace login once the password has expired'
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
+   EQUALITY generalizedTimeMatch
+   NO-USER-MODIFICATION
+   USAGE directoryOperation)
+.RE
+
+.B pwdReset
+.P
+This attribute indicates whether the user's password has been reset
+by the administrator and thus must be changed upon first use of this
+DN for authentication to the directory.  If
+.B pwdReset   
+is set to "TRUE", then the password was reset and the user must change
+it upon first authentication.  If the attribute does not exist, or
+is set to "FALSE", the user need not change their password due to
+administrative reset.
+.LP
+.RS 4
+(  1.3.6.1.4.1.42.2.27.8.1.22
+   NAME 'pwdReset'
+   DESC 'The indication that the password has
+       been reset'
+   EQUALITY booleanMatch
+   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+   SINGLE-VALUE
+   USAGE directoryOperation)
+.RE
+
+.SH EXAMPLES
+.LP
+.RS
+.nf
+database bdb
+suffix dc=example,dc=com
+\...
+overlay ppolicy
+ppolicy_default "cn=Standard,ou=Policies,dc=example,dc=com"
+.fi
+.RE
+
+.SH SEE ALSO
+.BR ldap (3),
+.BR slapd.conf (5),
+.LP
+"OpenLDAP Administrator's Guide" (http://www.OpenLDAP.org/doc/admin/)
+.LP
+IETF LDAP password policy proposal by P. Behera, L.  Poitou and J.
+Sermersheim:  documented in IETF document
+"draft-behera-ldap-password-policy-09.txt".
+
+.SH BUGS
+The LDAP Password Policy specification is not yet an approved standard,
+and it is still evolving. This code will continue to be in flux until the
+specification is finalized.
+
+.SH ACKNOWLEDGEMENTS
+.P
+This module was written in 2004 by Howard Chu of Symas Corporation
+with significant input from Neil Dunbar and Kartik Subbarao of Hewlett-Packard.
+.P
+This manual page borrows heavily and shamelessly from the specification
+upon which the password policy module it describes is based.  This
+source is the
+IETF LDAP password policy proposal by P. Behera, L.
+Poitou and J. Sermersheim.
+The proposal is fully documented in
+the
+IETF document named draft-behera-ldap-password-policy-09.txt,
+written in July of 2005.
+.P
+.so ../Project