mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Ludovic Poitou
26.15.2015 b8a868a4f541df785e161f02e2bdb426fccdf1a3
Code cleanup.
1 files modified
1432 ■■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/core/PasswordPolicyState.java 1432 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/PasswordPolicyState.java
@@ -75,6 +75,7 @@
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.protocols.internal.InternalClientConnection.getRootConnection;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -106,18 +107,15 @@
  /** Indicates whether the user's password is expired. */
  private ConditionResult isPasswordExpired = ConditionResult.UNDEFINED;
  /**
   * Indicates whether the warning to send to the client would be the first
   * warning for the user.
   */
  /** Indicates whether the warning to send to the client would be the first warning for the user. */
  private ConditionResult isFirstWarning = ConditionResult.UNDEFINED;
  /** Indicates whether the user's account is locked by the idle lockout. */
  private ConditionResult isIdleLocked = ConditionResult.UNDEFINED;
  /**
   * Indicates whether the user may use a grace login if the password is expired
   * and there are one or more grace logins remaining.
   * Indicates whether the user may use a grace login if the password is expiredand there are one or
   * more grace logins remaining.
   */
  private ConditionResult mayUseGraceLogin = ConditionResult.UNDEFINED;
@@ -139,10 +137,7 @@
  /** The time that the user's account should expire (or did expire). */
  private long accountExpirationTime = Long.MIN_VALUE;
  /**
   * The time that the user's entry was locked due to too many authentication
   * failures.
   */
  /** The time that the user's entry was locked due to too many authentication failures. */
  private long failureLockedTime = Long.MIN_VALUE;
  /** The time that the user last authenticated to the Directory Server. */
@@ -164,25 +159,20 @@
  /**
   * Creates a new password policy state object with the provided information.
   * Note that this version of the constructor should only be used for testing
   * purposes when the tests should be evaluated with a fixed time rather than
   * the actual current time. For all other purposes, the other constructor
   * Note that this version of the constructor should only be used for testing purposes when the tests should be
   * evaluated with a fixed time rather than the actual current time. For all other purposes, the other constructor
   * should be used.
   *
   * @param policy
   *          The password policy associated with the state.
   * @param userEntry
   *          The entry with the user account.
   * @param currentTime
   *          The time to use as the current time for all time-related
   *          determinations.
   * @param policy      The password policy associated with the state.
   * @param userEntry   The entry with the user account.
   * @param currentTime The time to use as the current time for all time-related determinations.
   */
  PasswordPolicyState(PasswordPolicy policy, Entry userEntry, long currentTime)
  {
    super(userEntry);
    this.currentTime = currentTime;
    this.userDNString     = userEntry.getName().toString();
    this.passwordPolicy   = policy;
    this.userDNString = userEntry.getName().toString();
    this.passwordPolicy = policy;
  }
@@ -192,8 +182,7 @@
    *
    * @param  attributeType  The attribute type whose value should be retrieved.
    *
    * @return  The value of the specified attribute as a string, or
    *          <CODE>null</CODE> if there is no such value.
    * @return  The value of the specified attribute as a string, or <CODE>null</CODE> if there is no such value.
    */
  private String getValue(AttributeType attributeType)
  {
@@ -215,8 +204,7 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning null because attribute %s does not " +
            "exist in user entry %s",
        logger.trace("Returning null because attribute %s does not exist in user entry %s",
            attributeType.getNameOrOID(), userDNString);
      }
    }
@@ -224,8 +212,7 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning value %s for user %s",
            stringValue, userDNString);
        logger.trace("Returning value %s for user %s", stringValue, userDNString);
      }
    }
@@ -235,17 +222,13 @@
  /**
   * Retrieves the set of values of the specified attribute from the user's
   * entry in generalized time format.
   * Retrieves the set of values of the specified attribute from the user's entry in generalized time format.
   *
   * @param  attributeType  The attribute type whose values should be parsed as
   *                        generalized time values.
   * @param  attributeType  The attribute type whose values should be parsed as generalized time values.
   *
   * @return  The set of generalized time values, or an empty list if there are
   *          none.
   * @return  The set of generalized time values, or an empty list if there are none.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              decode a value as a generalized time.
   * @throws  DirectoryException  If a problem occurs while attempting to decode a value as a generalized time.
   */
  private List<Long> getGeneralizedTimes(AttributeType attributeType)
          throws DirectoryException
@@ -268,9 +251,9 @@
            logger.traceException(e, "Unable to decode value %s for attribute %s in user entry %s",
                v, attributeType.getNameOrOID(), userDNString);
            LocalizableMessage message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME.
                get(v, attributeType.getNameOrOID(), userDNString, e);
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, e);
            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME.get(v, attributeType.getNameOrOID(), userDNString, e),
                e);
          }
        }
      }
@@ -278,8 +261,7 @@
    if (timeValues.isEmpty())
    {
      logger.trace("Returning an empty list because attribute %s " +
          "does not exist in user entry %s",
      logger.trace("Returning an empty list because attribute %s does not exist in user entry %s",
          attributeType.getNameOrOID(), userDNString);
    }
    return timeValues;
@@ -330,8 +312,7 @@
    if (passwordChangedTime < 0)
    {
      // Get the password changed time for the user.
      AttributeType type = DirectoryServer.getAttributeType(
          OP_ATTR_PWPOLICY_CHANGED_TIME_LC, true);
      AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC, true);
      try
      {
@@ -340,10 +321,9 @@
      catch (DirectoryException e)
      {
        /*
         * The password change time could not be parsed (but has been logged in
         * the debug log). The best effort we can do from here is to a) use the
         * current time, b) use the start of the epoch (1/1/1970), or c) use the
         * create time stamp. Lets treat this problem as if the change time
         * The password change time could not be parsed (but has been logged in the debug log).
         * The best effort we can do from here is to a) use the current time, b) use the start
         * of the epoch (1/1/1970), or c) use the create time stamp. Lets treat this problem as if the change time
         * attribute did not exist and resort to the create time stamp.
         */
      }
@@ -351,8 +331,7 @@
      if (passwordChangedTime < 0)
      {
        // Get the time that the user's account was created.
        AttributeType createTimeType = DirectoryServer.getAttributeType(
            OP_ATTR_CREATE_TIMESTAMP_LC, true);
        AttributeType createTimeType = DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true);
        try
        {
          passwordChangedTime = getGeneralizedTime(userEntry, createTimeType);
@@ -360,12 +339,10 @@
        catch (DirectoryException e)
        {
          /*
           * The create time stamp could not be parsed (but has been logged in
           * the debug log). The best effort we can do from here is to a) use
           * the current time, or b) use the start of the epoch (1/1/1970). Lets
           * treat this problem as if the change time attribute did not exist
           * and use the start of the epoch. Doing so stands a greater chance of
           * forcing a password change.
           * The create time stamp could not be parsed (but has been logged in the debug log).
           * The best effort we can do from here is to a) use the current time, or b) use the start of
            * the epoch (1/1/1970). Lets treat this problem as if the change time attribute did not exist
           * and use the start of the epoch. Doing so stands a greater chance of forcing a password change.
           */
        }
@@ -376,8 +353,7 @@
          if (logger.isTraceEnabled())
          {
            logger.trace(
                "Could not determine password changed time for " + "user %s.",
                userDNString);
                "Could not determine password changed time for " + "user %s.", userDNString);
          }
        }
      }
@@ -401,16 +377,13 @@
  /**
   * Retrieves the unmodifiable set of values for the password
   * attribute from the user entry.
   * Retrieves the unmodifiable set of values for the password attribute from the user entry.
   *
   * @return The unmodifiable set of values for the password attribute
   *         from the user entry.
   * @return The unmodifiable set of values for the password attribute from the user entry.
   */
  public Set<ByteString> getPasswordValues()
  {
    List<Attribute> attrList = userEntry.getAttribute(passwordPolicy
        .getPasswordAttribute());
    List<Attribute> attrList = userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    if (attrList != null)
    {
      for (Attribute a : attrList)
@@ -432,8 +405,7 @@
  /**
   * Sets a new value for the password changed time equal to the
   * current time.
   * Sets a new value for the password changed time equal to the current time.
   */
  public void setPasswordChangedTime()
  {
@@ -441,11 +413,10 @@
  }
  /**
   * Sets a new value for the password changed time equal to the specified time.
   * This method should generally only be used for testing purposes, since the
   * variant that uses the current time is preferred almost everywhere else.
   * This method should generally only be used for testing purposes, since the variant that uses
   * the current time is preferred almost everywhere else.
   *
   * @param  passwordChangedTime  The time to use
   */
@@ -453,19 +424,16 @@
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Setting password changed time for user %s to " +
          "current time of %d", userDNString, currentTime);
      logger.trace("Setting password changed time for user %s to current time of %d", userDNString, currentTime);
    }
    // passwordChangedTime is computed in the constructor from values in the
    // entry.
    // passwordChangedTime is computed in the constructor from values in the entry.
    if (getPasswordChangedTime() != passwordChangedTime)
    {
      this.passwordChangedTime = passwordChangedTime;
      String timeValue = GeneralizedTimeSyntax.format(passwordChangedTime);
      Attribute a = Attributes.create(OP_ATTR_PWPOLICY_CHANGED_TIME,
          timeValue);
      Attribute a = Attributes.create(OP_ATTR_PWPOLICY_CHANGED_TIME, timeValue);
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
@@ -474,29 +442,24 @@
  /**
   * Removes the password changed time value from the user's entry.  This should
   * only be used for testing purposes, as it can really mess things up if you
   * don't know what you're doing.
   * Removes the password changed time value from the user's entry.  This should only be used for testing
   * purposes, as it can really mess things up if you don't know what you're doing.
   */
  public void clearPasswordChangedTime()
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Clearing password changed time for user %s",
          userDNString);
      logger.trace("Clearing password changed time for user %s", userDNString);
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC,
                                       true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC, true);
    Attribute a = Attributes.empty(type);
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    // Fall back to using the entry creation time as the password changed time,
    // if it's defined.  Otherwise, use a value of zero.
    AttributeType createTimeType =
         DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true);
    // Fall back to using the entry creation time as the password changed time, if it's defined.
    // Otherwise, use a value of zero.
    AttributeType createTimeType = DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true);
    try
    {
      passwordChangedTime = getGeneralizedTime(userEntry, createTimeType);
@@ -514,19 +477,16 @@
  /**
   * Updates the user entry to indicate whether user account has been
   * administratively disabled.
   * Updates the user entry to indicate whether user account has been administratively disabled.
   *
   * @param isDisabled
   *          Indicates whether the user account has been administratively
   *          disabled.
   *          Indicates whether the user account has been administratively disabled.
   */
  public void setDisabled(boolean isDisabled)
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Updating user %s to set the disabled flag to %b",
          userDNString, isDisabled);
      logger.trace("Updating user %s to set the disabled flag to %b", userDNString, isDisabled);
    }
@@ -537,8 +497,7 @@
    this.isDisabled = ConditionResult.not(this.isDisabled);
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
    if (isDisabled)
    {
@@ -548,18 +507,15 @@
    else
    {
      // erase
      modifications.add(new Modification(ModificationType.REPLACE,
                                           Attributes.empty(type), true));
      modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
    }
  }
  /**
   * Indicates whether the user's account is currently expired.
   *
   * @return  <CODE>true</CODE> if the user's account is expired, or
   *          <CODE>false</CODE> if not.
   * @return  <CODE>true</CODE> if the user's account is expired, or <CODE>false</CODE> if not.
   */
  public boolean isAccountExpired()
  {
@@ -568,25 +524,21 @@
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning stored result of %b for user %s",
            (isAccountExpired == ConditionResult.TRUE), userDNString);
            isAccountExpired == ConditionResult.TRUE, userDNString);
      }
      return isAccountExpired == ConditionResult.TRUE;
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                          true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME, true);
    try
    {
    try {
      accountExpirationTime = getGeneralizedTime(userEntry, type);
     }
    }
    catch (Exception e)
    {
      logger.traceException(e, "User %s is considered to have an expired " +
          "account because an error occurred while attempting to make " +
          "the determination.", userDNString);
      logger.traceException(e, "User %s is considered to have an expired account because an error occurred " +
          "while attempting to make the determination.", userDNString);
      isAccountExpired = ConditionResult.TRUE;
      return true;
@@ -596,23 +548,21 @@
    {
      // The user does have an expiration time, but it hasn't arrived yet.
      isAccountExpired = ConditionResult.FALSE;
      logger.trace("The account for user %s is not expired because " +
          "the expiration time has not yet arrived.", userDNString);
      logger.trace("The account for user %s is not expired because the expiration time has not yet arrived.",
          userDNString);
    }
    else if (accountExpirationTime >= 0)
    {
      // The user does have an expiration time, and it is in the past.
      isAccountExpired = ConditionResult.TRUE;
      logger.trace("The account for user %s is expired because the " +
          "expiration time in that account has passed.", userDNString);
      logger.trace("The account for user %s is expired because the expiration time in that account has passed.",
          userDNString);
    }
    else
    {
      // The user doesn't have an expiration time in their entry, so it
      // can't be expired.
      // The user doesn't have an expiration time in their entry, so it can't be expired.
      isAccountExpired = ConditionResult.FALSE;
      logger.trace("The account for user %s is not expired because " +
          "there is no expiration time in the user's entry.",
      logger.trace("The account for user %s is not expired because there is no expiration time in the user's entry.",
          userDNString);
    }
@@ -624,8 +574,7 @@
  /**
   * Retrieves the time at which the user's account will expire.
   *
   * @return  The time at which the user's account will expire, or -1 if it is
   *          not configured with an expiration time.
   * @return  The time at which the user's account will expire, or -1 if it is not configured with an expiration time.
   */
  public long getAccountExpirationTime()
  {
@@ -642,8 +591,7 @@
  /**
   * Sets the user's account expiration time to the specified value.
   *
   * @param  accountExpirationTime  The time that the user's account should
   *                                expire.
   * @param  accountExpirationTime  The time that the user's account should expire.
   */
  public void setAccountExpirationTime(long accountExpirationTime)
  {
@@ -657,14 +605,11 @@
      if (logger.isTraceEnabled())
      {
        logger.trace("Setting account expiration time for user %s to %s",
            userDNString, timeStr);
        logger.trace("Setting account expiration time for user %s to %s", userDNString, timeStr);
      }
      this.accountExpirationTime = accountExpirationTime;
      AttributeType type =
           DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                            true);
      AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME, true);
      Attribute a = Attributes.create(type, timeStr);
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
@@ -680,32 +625,24 @@
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Clearing account expiration time for user %s",
          userDNString);
      logger.trace("Clearing account expiration time for user %s", userDNString);
    }
    accountExpirationTime = -1;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                          true);
    modifications.add(new Modification(ModificationType.REPLACE,
          Attributes.empty(type), true));
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME, true);
    modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
  }
  /**
   * Retrieves the set of times of failed authentication attempts for
   * the user. If authentication failure time expiration is enabled,
   * and there are expired times in the entry, these times are removed
   * from the instance field and an update is provided to delete those
   * values from the entry.
   * Retrieves the set of times of failed authentication attempts for the user. If authentication failure
   * time expiration is enabled, and there are expired times in the entry, these times are removed
   * from the instance field and an update is provided to delete those values from the entry.
   *
   * @return The set of times of failed authentication attempts for
   *         the user, which will be an empty list in the case of no
   *         valid (unexpired) times in the entry.
   * @return The set of times of failed authentication attempts for the user, which will be an empty list
   *         in the case of no valid (unexpired) times in the entry.
   */
  public List<Long> getAuthFailureTimes()
  {
@@ -713,20 +650,17 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning stored auth failure time list of %d " +
            "elements for user %s",
        logger.trace("Returning stored auth failure time list of %d elements for user %s",
            authFailureTimes.size(), userDNString);
      }
      return authFailureTimes;
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
           OP_ATTR_PWPOLICY_FAILURE_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME);
    }
    try
@@ -735,14 +669,10 @@
    }
    catch (Exception e)
    {
      logger.traceException(e, "Error while processing auth failure times for user %s",
          userDNString);
      logger.traceException(e, "Error while processing auth failure times for user %s", userDNString);
      authFailureTimes = new ArrayList<Long>();
      modifications.add(new Modification(ModificationType.REPLACE,
            Attributes.empty(type), true));
      modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
      return authFailureTimes;
    }
@@ -750,9 +680,8 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning an empty auth failure time list for " +
            "user %s because the attribute is absent from the entry.",
            userDNString);
        logger.trace("Returning an empty auth failure time list for user %s because the attribute" +
                " is absent from the entry.", userDNString);
      }
      return authFailureTimes;
@@ -763,8 +692,7 @@
    {
      LinkedHashSet<ByteString> valuesToRemove = null;
      long expirationTime = currentTime -
           (passwordPolicy.getLockoutFailureExpirationInterval() * 1000L);
      long expirationTime = currentTime - passwordPolicy.getLockoutFailureExpirationInterval() * 1000L;
      Iterator<Long> iterator = authFailureTimes.iterator();
      while (iterator.hasNext())
      {
@@ -773,8 +701,7 @@
        {
          if (logger.isTraceEnabled())
          {
            logger.trace("Removing expired auth failure time %d for " +
                "user %s", l, userDNString);
            logger.trace("Removing expired auth failure time %d for user %s", l, userDNString);
          }
          iterator.remove();
@@ -793,26 +720,23 @@
        AttributeBuilder builder = new AttributeBuilder(type);
        builder.addAll(valuesToRemove);
        Attribute a = builder.toAttribute();
        modifications.add(new Modification(ModificationType.DELETE, a,
            true));
        modifications.add(new Modification(ModificationType.DELETE, a, true));
      }
    }
    if (logger.isTraceEnabled())
    {
      logger.trace("Returning auth failure time list of %d elements " +
          "for user %s", authFailureTimes.size(), userDNString);
      logger.trace("Returning auth failure time list of %d elements for user %s",
          authFailureTimes.size(), userDNString);
    }
    return authFailureTimes;
  }
  /**
   * Updates the set of authentication failure times to include the current
   * time. If the number of failures reaches the policy configuration limit,
   * lock the account.
   * Updates the set of authentication failure times to include the current time.
   * If the number of failures reaches the policy configuration limit, lock the account.
   */
  public void updateAuthFailureTimes()
  {
@@ -823,8 +747,7 @@
    if (logger.isTraceEnabled())
    {
      logger.trace("Updating authentication failure times for user %s",
          userDNString);
      logger.trace("Updating authentication failure times for user %s", userDNString);
    }
@@ -848,27 +771,23 @@
    failureTimes.add(highestFailureTime);
    // And the attribute in the user entry
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_FAILURE_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME);
    }
    Attribute addAttr = Attributes.create(type, GeneralizedTimeSyntax.format(highestFailureTime));
    modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    // Now check to see if there have been sufficient failures to lock the
    // account.
    // Now check to see if there have been sufficient failures to lock the account.
    int lockoutCount = passwordPolicy.getLockoutFailureCount();
    if ((lockoutCount > 0) && (lockoutCount <= authFailureTimes.size()))
    if (lockoutCount > 0 && lockoutCount <= authFailureTimes.size())
    {
      setFailureLockedTime(highestFailureTime);
      if (logger.isTraceEnabled())
      {
        logger.trace("Locking user account %s due to too many failures.",
            userDNString);
        logger.trace("Locking user account %s due to too many failures.", userDNString);
      }
    }
  }
@@ -876,26 +795,22 @@
  /**
   * Explicitly specifies the auth failure times for the associated user.  This
   * should generally only be used for testing purposes.  Note that it will also
   * set or clear the locked time as appropriate.
   * Explicitly specifies the auth failure times for the associated user.  This should generally only be used
   * for testing purposes.  Note that it will also set or clear the locked time as appropriate.
   *
   * @param  authFailureTimes  The set of auth failure times to use for the
   *                           account.  An empty list or {@code null} will
   *                           clear the account of any existing failures.
   * @param  authFailureTimes  The set of auth failure times to use for the account.  An empty list or
   *                           {@code null} will clear the account of any existing failures.
   */
  public void setAuthFailureTimes(List<Long> authFailureTimes)
  {
    if ((authFailureTimes == null) || authFailureTimes.isEmpty())
    if (authFailureTimes == null || authFailureTimes.isEmpty())
    {
      clearAuthFailureTimes();
      clearFailureLockedTime();
      return;
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC,
                                          true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC, true);
    this.authFailureTimes = authFailureTimes;
    AttributeBuilder builder = new AttributeBuilder(type);
@@ -910,16 +825,14 @@
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    // Now check to see if there have been sufficient failures to lock the
    // account.
    // Now check to see if there have been sufficient failures to lock the account.
    int lockoutCount = passwordPolicy.getLockoutFailureCount();
    if ((lockoutCount > 0) && (lockoutCount <= authFailureTimes.size()))
    if (lockoutCount > 0 && lockoutCount <= authFailureTimes.size())
    {
      setFailureLockedTime(highestFailureTime);
      if (logger.isTraceEnabled())
      {
        logger.trace("Locking user account %s due to too many failures.",
            userDNString);
        logger.trace("Locking user account %s due to too many failures.", userDNString);
      }
    }
  }
@@ -927,15 +840,13 @@
  /**
   * Updates the user entry to remove any record of previous authentication
   * failure times.
   * Updates the user entry to remove any record of previous authentication failure times.
   */
  private void clearAuthFailureTimes()
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Clearing authentication failure times for user %s",
          userDNString);
      logger.trace("Clearing authentication failure times for user %s", userDNString);
    }
    List<Long> failureTimes = getAuthFailureTimes();
@@ -944,26 +855,22 @@
      return;
    }
    failureTimes.clear(); // Note: failureTimes == this.authFailureTimes
    failureTimes.clear(); // Note: failureTimes != this.authFailureTimes
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_FAILURE_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME);
    }
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
  }
  /**
   * Retrieves the time of an authentication failure lockout for the user.
   *
   * @return  The time of an authentication failure lockout for the user, or -1
   *          if no such time is present in the entry.
   * @return  The time of an authentication failure lockout for the user, or -1 if no such time is present in the entry.
   */
  private long getFailureLockedTime()
  {
@@ -972,12 +879,10 @@
      return failureLockedTime;
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
           OP_ATTR_PWPOLICY_LOCKED_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    try
@@ -986,8 +891,7 @@
    }
    catch (Exception e)
    {
      logger.traceException(e,
          "Returning current time for user %s because an error occurred", userDNString);
      logger.traceException(e, "Returning current time for user %s because an error occurred", userDNString);
      failureLockedTime = currentTime;
      return failureLockedTime;
@@ -1013,12 +917,10 @@
    failureLockedTime = time;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    Attribute a = Attributes.create(type, GeneralizedTimeSyntax.format(failureLockedTime));
@@ -1028,15 +930,13 @@
  /**
   * Updates the user entry to remove any record of previous authentication
   * failure lockout.
   * Updates the user entry to remove any record of previous authentication failure lockout.
   */
  private void clearFailureLockedTime()
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Clearing failure lockout time for user %s.",
          userDNString);
      logger.trace("Clearing failure lockout time for user %s.", userDNString);
    }
    if (-1L == getFailureLockedTime())
@@ -1046,38 +946,31 @@
    failureLockedTime = -1L;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
  }
  /**
   * Indicates whether the associated user should be considered locked out as a
   * result of too many authentication failures. In the case of an expired
   * lock-out, this routine produces the update to clear the lock-out attribute
   * and the authentication failure timestamps.
   * In case the failure lockout time is absent from the entry, but sufficient
   * authentication failure timestamps are present in the entry, this routine
   * produces the update to set the lock-out attribute.
   * Indicates whether the associated user should be considered locked out as a result of too many
   * authentication failures. In the case of an expired lock-out, this routine produces the update
   * to clear the lock-out attribute and the authentication failure timestamps.
   * In case the failure lockout time is absent from the entry, but sufficient authentication failure
   * timestamps are present in the entry, this routine produces the update to set the lock-out attribute.
   *
   * @return  <CODE>true</CODE> if the user is currently locked out due to too
   *          many authentication failures, or <CODE>false</CODE> if not.
   * @return  <CODE>true</CODE> if the user is currently locked out due to too many authentication failures,
   *          or <CODE>false</CODE> if not.
   */
  public boolean lockedDueToFailures()
  {
    // FIXME: Introduce a state field to cache the computed value of this
    // method. Note that only a cached "locked" status can be returned due to
    // the possibility of intervening updates to this.failureLockedTime by
    // updateAuthFailureTimes.
    // FIXME: Introduce a state field to cache the computed value of this method.
    // Note that only a cached "locked" status can be returned due to the possibility of intervening updates to
    // this.failureLockedTime by updateAuthFailureTimes.
    // Check if the feature is enabled in the policy.
    final int maxFailures = passwordPolicy.getLockoutFailureCount();
@@ -1085,36 +978,30 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false for user %s because lockout due " +
            "to failures is not enabled.", userDNString);
        logger.trace("Returning false for user %s because lockout due to failures is not enabled.", userDNString);
      }
      return false;
    }
    // Get the locked time from the user's entry. If it is present and not
    // expired, the account is locked. If it is absent, the failure timestamps
    // must be checked, since failure timestamps sufficient to lock the
    // account could be produced across the synchronization topology within the
    // synchronization latency. Also, note that IETF
    // draft-behera-ldap-password-policy-09 specifies "19700101000000Z" as
    // the value to be set under a "locked until reset" regime; however, this
    // implementation accepts the value as a locked entry, but observes the
    // lockout expiration policy for all values including this one.
    // FIXME: This "getter" is unusual in that it might produce an update to the
    // entry in two cases. Does it make sense to factor the methods so that,
    // e.g., an expired lockout is reported, and clearing the lockout is left to
    // the caller?
    // Get the locked time from the user's entry. If it is present and not expired, the account is locked.
    // If it is absent, the failure timestamps must be checked, since failure timestamps sufficient to lock the
    // account could be produced across the synchronization topology within the synchronization latency.
    // Also, note that IETF draft-behera-ldap-password-policy-09 specifies "19700101000000Z" as the value to be set
    // under a "locked until reset" regime; however, this implementation accepts the value as a locked entry,
    // but observes the lockout expiration policy for all values including this one.
    // FIXME: This "getter" is unusual in that it might produce an update to the entry in two cases.
    // Does it make sense to factor the methods so that, e.g., an expired lockout is reported, and clearing
    // the lockout is left to the caller?
    if (getFailureLockedTime() < 0L)
    {
      // There was no locked time present in the entry; however, sufficient
      // failure times might have accumulated to trigger a lockout.
      // There was no locked time present in the entry; however, sufficient failure times might have accumulated
      // to trigger a lockout.
      if (getAuthFailureTimes().size() < maxFailures)
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("Returning false for user %s because there is " +
              "no locked time.", userDNString);
          logger.trace("Returning false for user %s because there is no locked time.", userDNString);
        }
        return false;
@@ -1125,9 +1012,8 @@
      if (logger.isTraceEnabled())
      {
        logger.trace("Locking user %s because there were enough " +
            "existing failures even though there was no account locked time.",
            userDNString);
        logger.trace("Locking user %s because there were enough existing failures even though there was" +
                " no account locked time.", userDNString);
      }
      // Fall through...
    }
@@ -1135,17 +1021,15 @@
    // There is a failure locked time, but it may be expired.
    if (passwordPolicy.getLockoutDuration() > 0)
    {
      final long unlockTime = getFailureLockedTime() +
           (1000L * passwordPolicy.getLockoutDuration());
      final long unlockTime = getFailureLockedTime() + 1000L * passwordPolicy.getLockoutDuration();
      if (unlockTime > currentTime)
      {
        secondsUntilUnlock = (int) ((unlockTime - currentTime) / 1000);
        if (logger.isTraceEnabled())
        {
          logger.trace("Returning true for user %s because there is a " +
              "locked time and the lockout duration has not been reached.",
              userDNString);
          logger.trace("Returning true for user %s because there is a locked time and the lockout duration has" +
                  " not been reached.", userDNString);
        }
        return true;
@@ -1156,8 +1040,7 @@
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false for user %s " +
            "because the existing lockout has expired.", userDNString);
        logger.trace("Returning false for user %s because the existing lockout has expired.", userDNString);
      }
      assert -1L == getFailureLockedTime();
@@ -1166,9 +1049,7 @@
    if (logger.isTraceEnabled())
    {
      logger.trace("Returning true for user %s " +
          "because there is a locked time and no lockout duration.",
          userDNString);
      logger.trace("Returning true for user %s because there is a locked time and no lockout duration.", userDNString);
    }
    assert -1L <= getFailureLockedTime();
@@ -1178,29 +1059,25 @@
  /**
   * Retrieves the length of time in seconds until the user's account is
   * automatically unlocked.  This should only be called after calling
   * <CODE>lockedDueToFailures</CODE>.
   * Retrieves the length of time in seconds until the user's account is automatically unlocked.
   * This should only be called after calling <CODE>lockedDueToFailures</CODE>.
   *
   * @return  The length of time in seconds until the user's account is
   *          automatically unlocked, or -1 if the account is not locked or the
   *          lockout requires administrative action to clear.
   * @return  The length of time in seconds until the user's account is automatically unlocked, or -1 if the account
   * is not locked or the lockout requires administrative action to clear.
   */
  public int getSecondsUntilUnlock()
  {
    // secondsUntilUnlock is only set when failureLockedTime is present and
    // PasswordPolicy.getLockoutDuration is enabled; hence it is not
    // unreasonable to find secondsUntilUnlock uninitialized.
    // secondsUntilUnlock is only set when failureLockedTime is present and PasswordPolicy.getLockoutDuration
    // is enabled; hence it is not unreasonable to find secondsUntilUnlock uninitialized.
    assert failureLockedTime != Long.MIN_VALUE;
    return (secondsUntilUnlock < 0) ? -1 : secondsUntilUnlock;
    return secondsUntilUnlock < 0 ? -1 : secondsUntilUnlock;
  }
  /**
   * Updates the user account to remove any record of a previous lockout due to
   * failed authentications.
   * Updates the user account to remove any record of a previous lockout due to failed authentications.
   */
  public void clearFailureLockout()
  {
@@ -1211,11 +1088,9 @@
  /**
   * Retrieves the time that the user last authenticated to the Directory
   * Server.
   * Retrieves the time that the user last authenticated to the Directory Server.
   *
   * @return  The time that the user last authenticated to the Directory Server,
   *          or -1 if it cannot be determined.
   * @return  The time that the user last authenticated to the Directory Server, or -1 if it cannot be determined.
   */
  public long getLastLoginTime()
  {
@@ -1223,32 +1098,29 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning stored last login time of %d for " +
            "user %s.", lastLoginTime, userDNString);
        logger.trace("Returning stored last login time of %d for user %s.", lastLoginTime, userDNString);
      }
      return lastLoginTime;
    }
    // The policy configuration must be checked since the entry cannot be
    // evaluated without both an attribute name and timestamp format.
    // The policy configuration must be checked since the entry cannot be evaluated without both an attribute
    // name and timestamp format.
    AttributeType type   = passwordPolicy.getLastLoginTimeAttribute();
    String        format = passwordPolicy.getLastLoginTimeFormat();
    if ((type == null) || (format == null))
    if (type == null || format == null)
    {
      lastLoginTime = -1;
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning -1 for user %s because no last login " +
            "time will be maintained.", userDNString);
        logger.trace("Returning -1 for user %s because no last login time will be maintained.", userDNString);
      }
      return lastLoginTime;
    }
    boolean isGeneralizedTime =
        type.getSyntax().getName().equals(SYNTAX_GENERALIZED_TIME_NAME);
    boolean isGeneralizedTime = type.getSyntax().getName().equals(SYNTAX_GENERALIZED_TIME_NAME);
    lastLoginTime = -1;
    List<Attribute> attrList = userEntry.getAttribute(type);
@@ -1270,8 +1142,7 @@
          if (logger.isTraceEnabled())
          {
            logger.trace("Returning last login time of %d for user %s" +
                "decoded using current last login time format.",
            logger.trace("Returning last login time of %d for user %s, decoded using current last login time format.",
                lastLoginTime, userDNString);
          }
@@ -1281,8 +1152,7 @@
        {
          logger.traceException(e);
          // This could mean that the last login time was encoded using a
          // previous format.
          // This could mean that the last login time was encoded using a previous format.
          for (String f : passwordPolicy.getPreviousLastLoginTimeFormats())
          {
            try
@@ -1296,9 +1166,8 @@
              if (logger.isTraceEnabled())
              {
                logger.trace("Returning last login time of %d for " +
                    "user %s decoded using previous last login time format " +
                    "of %s.", lastLoginTime, userDNString, f);
                logger.trace("Returning last login time of %d for user %s decoded using previous last login time " +
                    "format of %s.", lastLoginTime, userDNString, f);
              }
              return lastLoginTime;
@@ -1312,9 +1181,8 @@
          assert lastLoginTime == -1;
          if (logger.isTraceEnabled())
          {
              logger.trace("Returning -1 for user %s because the " +
                  "last login time value %s could not be parsed using any " +
                  "known format.", userDNString, valueString);
              logger.trace("Returning -1 for user %s because the last login time value %s could not be parsed " +
                  "using any known format.", userDNString, valueString);
          }
          return lastLoginTime;
@@ -1325,8 +1193,7 @@
    assert lastLoginTime == -1;
    if (logger.isTraceEnabled())
    {
      logger.trace("Returning %d for user %s because no last " +
          "login time value exists.", lastLoginTime, userDNString);
      logger.trace("Returning %d for user %s because no last login time value exists.", lastLoginTime, userDNString);
    }
    return lastLoginTime;
@@ -1345,9 +1212,8 @@
  /**
   * Updates the user entry to use the specified last login time.  This should
   * be used primarily for testing purposes, as the variant that uses the
   * current time should be used most of the time.
   * Updates the user entry to use the specified last login time.  This should be used primarily for testing purposes,
   * as the variant that uses the current time should be used most of the time.
   *
   * @param  lastLoginTime  The last login time to set in the user entry.
   */
@@ -1356,7 +1222,7 @@
    AttributeType type = passwordPolicy.getLastLoginTimeAttribute();
    String format = passwordPolicy.getLastLoginTimeFormat();
    if ((type == null) || (format == null))
    if (type == null || format == null)
    {
      return;
    }
@@ -1366,8 +1232,7 @@
    {
      SimpleDateFormat dateFormat = new SimpleDateFormat(format);
      // If the attribute has a Generalized Time syntax, make it UTC time.
      if (type.getSyntax().getName()
          .equals(SYNTAX_GENERALIZED_TIME_NAME))
      if (type.getSyntax().getName().equals(SYNTAX_GENERALIZED_TIME_NAME))
      {
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
      }
@@ -1376,17 +1241,16 @@
    }
    catch (Exception e)
    {
      logger.traceException(e, "Unable to set last login time for user %s " +
          "because an error occurred", userDNString);
      logger.traceException(e, "Unable to set last login time for user %s because an error occurred", userDNString);
      return;
    }
    String existingTimestamp = getValue(type);
    if ((existingTimestamp != null) && timestamp.equals(existingTimestamp))
    if (existingTimestamp != null && timestamp.equals(existingTimestamp))
    {
      logger.trace("Not updating last login time for user %s " +
          "because the new value matches the existing value.", userDNString);
      logger.trace("Not updating last login time for user %s because the new value matches the existing value.",
          userDNString);
      return;
    }
@@ -1394,15 +1258,13 @@
    Attribute a = Attributes.create(type, timestamp);
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    logger.trace("Updated the last login time for user %s to %s",
        userDNString, timestamp);
    logger.trace("Updated the last login time for user %s to %s", userDNString, timestamp);
  }
  /**
   * Clears the last login time from the user's entry.  This should generally be
   * used only for testing purposes.
   * Clears the last login time from the user's entry.  This should generally be used only for testing purposes.
   */
  public void clearLastLoginTime()
  {
@@ -1413,21 +1275,17 @@
    lastLoginTime = -1;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_LAST_LOGIN_TIME, true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_LAST_LOGIN_TIME, true);
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
  }
  /**
   * Indicates whether the user's account is currently locked because it has
   * been idle for too long.
   * Indicates whether the user's account is currently locked because it has been idle for too long.
   *
   * @return  <CODE>true</CODE> if the user's account is locked because it has
   *          been idle for too long, or <CODE>false</CODE> if not.
   * @return  <CODE>true</CODE> if the user's account is locked because it has been idle for too long,
   *          or <CODE>false</CODE> if not.
   */
  public boolean lockedDueToIdleInterval()
  {
@@ -1435,29 +1293,26 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning stored result of %b for user %s",
            (isIdleLocked == ConditionResult.TRUE), userDNString);
        logger.trace("Returning stored result of %b for user %s", isIdleLocked == ConditionResult.TRUE, userDNString);
      }
      return isIdleLocked == ConditionResult.TRUE;
    }
    // Return immediately if this feature is disabled, since the feature is not
    // responsible for any state attribute in the entry.
    // Return immediately if this feature is disabled, since the feature is not responsible for any state attribute
    // in the entry.
    if (passwordPolicy.getIdleLockoutInterval() <= 0)
    {
      isIdleLocked = ConditionResult.FALSE;
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false for user %s because no idle " +
            "lockout interval is defined.", userDNString);
        logger.trace("Returning false for user %s because no idle lockout interval is defined.", userDNString);
      }
      return false;
    }
    long lockTime = currentTime -
                         (1000L * passwordPolicy.getIdleLockoutInterval());
    long lockTime = currentTime - 1000L * passwordPolicy.getIdleLockoutInterval();
    if(lockTime < 0) lockTime = 0;
    long theLastLoginTime = getLastLoginTime();
@@ -1487,13 +1342,10 @@
      isIdleLocked = ConditionResult.TRUE;
      if (logger.isTraceEnabled())
      {
        String reason = (theLastLoginTime < 0)
            ? "there is no last login time and the password " +
            "changed time is not in an acceptable window"
            : "neither last login time nor password " +
            "changed time are in an acceptable window";
        logger.trace("Returning true for user %s because %s.",
            userDNString, reason);
        String reason = theLastLoginTime < 0
            ? "there is no last login time and the password changed time is not in an acceptable window"
            : "neither last login time nor password changed time are in an acceptable window";
        logger.trace("Returning true for user %s because %s.", userDNString, reason);
      }
    }
@@ -1503,11 +1355,9 @@
/**
* Indicates whether the user's password must be changed before any other
* operation can be performed.
* Indicates whether the user's password must be changed before any other operation can be performed.
*
* @return  <CODE>true</CODE> if the user's password must be changed before
*          any other operation can be performed.
* @return  <CODE>true</CODE> if the user's password must be changed before any other operation can be performed.
*/
  public boolean mustChangePassword()
  {
@@ -1516,41 +1366,33 @@
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning stored result of %b for user %s.",
            (mustChangePassword == ConditionResult.TRUE), userDNString);
            mustChangePassword == ConditionResult.TRUE, userDNString);
      }
      return mustChangePassword == ConditionResult.TRUE;
    }
    // If the password policy doesn't use force change on add or force change on
    // reset, or if it forbids the user from changing his password, then return
    // false.
    // FIXME: the only getter responsible for a state attribute (pwdReset) that
    // considers the policy before checking the entry for the presence of the
    // attribute.
    // If the password policy doesn't use force change on add or force change on reset, or if it forbids the user
    // from changing his password, then return false.
    // FIXME: the only getter responsible for a state attribute (pwdReset) that considers the policy before
    // checking the entry for the presence of the attribute.
    if (! (passwordPolicy.isAllowUserPasswordChanges()
           && (passwordPolicy.isForceChangeOnAdd()
               || passwordPolicy.isForceChangeOnReset())))
           && (passwordPolicy.isForceChangeOnAdd() || passwordPolicy.isForceChangeOnReset())))
    {
      mustChangePassword = ConditionResult.FALSE;
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false for user %s because neither " +
            "force change on add nor force change on reset is enabled, " +
            "or users are not allowed to self-modify passwords.",
            userDNString);
        logger.trace("Returning false for user %s because neither force change on add nor force change on reset" +
                " is enabled, or users are not allowed to self-modify passwords.", userDNString);
      }
      return false;
    }
    AttributeType type =
           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
           OP_ATTR_PWPOLICY_RESET_REQUIRED);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED);
    }
    try
@@ -1559,8 +1401,7 @@
    }
    catch (Exception e)
    {
      logger.traceException(e, "Returning true for user %s because an error occurred",
          userDNString);
      logger.traceException(e, "Returning true for user %s because an error occurred", userDNString);
      mustChangePassword = ConditionResult.TRUE;
@@ -1570,8 +1411,7 @@
    if(mustChangePassword == ConditionResult.UNDEFINED)
    {
      mustChangePassword = ConditionResult.FALSE;
      logger.trace("Returning %b for user since the attribute \"%s\"" +
          " is not present in the entry.",
      logger.trace("Returning %b for user since the attribute \"%s\" is not present in the entry.",
          false, userDNString, OP_ATTR_PWPOLICY_RESET_REQUIRED);
      return false;
@@ -1585,18 +1425,15 @@
/**
* Updates the user entry to indicate whether the user's password must be
* changed.
* Updates the user entry to indicate whether the user's password must be changed.
*
* @param  mustChangePassword  Indicates whether the user's password must be
*                             changed.
* @param  mustChangePassword  Indicates whether the user's password must be changed.
*/
  public void setMustChangePassword(boolean mustChangePassword)
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Updating user %s to set the reset flag to %b",
          userDNString, mustChangePassword);
      logger.trace("Updating user %s to set the reset flag to %b", userDNString, mustChangePassword);
    }
    if (mustChangePassword == mustChangePassword())
@@ -1606,12 +1443,10 @@
    this.mustChangePassword = ConditionResult.not(this.mustChangePassword);
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_RESET_REQUIRED);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED);
    }
    if (mustChangePassword)
@@ -1621,31 +1456,26 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                           Attributes.empty(type), true));
      modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
    }
  }
  /**
   * Indicates whether the user's account is locked because the password has
   * been reset by an administrator but the user did not change the password in
   * a timely manner.
   * Indicates whether the user's account is locked because the password has been reset by an administrator
   * but the user did not change the password in a timely manner.
   *
   * @return  <CODE>true</CODE> if the user's account is locked because of the
   *          maximum reset age, or <CODE>false</CODE> if not.
   * @return  <CODE>true</CODE> if the user's account is locked because of the maximum reset age,
   *          or <CODE>false</CODE> if not.
   */
  public boolean lockedDueToMaximumResetAge()
  {
    // This feature is reponsible for neither a state field nor an entry state
    // attribute.
    // This feature is responsible for neither a state field nor an entry state attribute.
    if (passwordPolicy.getMaxPasswordResetAge() <= 0L)
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false for user %s because there is no " +
            "maximum reset age.", userDNString);
        logger.trace("Returning false for user %s because there is no maximum reset age.", userDNString);
      }
      return false;
@@ -1655,21 +1485,18 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false for user %s because the user's " +
            "password has not been reset.", userDNString);
        logger.trace("Returning false for user %s because the user's password has not been reset.", userDNString);
      }
      return false;
    }
    long maxResetTime = getPasswordChangedTime() +
        (1000L * passwordPolicy.getMaxPasswordResetAge());
    boolean locked = (maxResetTime < currentTime);
    long maxResetTime = getPasswordChangedTime() + 1000L * passwordPolicy.getMaxPasswordResetAge();
    boolean locked = maxResetTime < currentTime;
    if (logger.isTraceEnabled())
    {
      logger.trace("Returning %b for user %s after comparing the " +
          "current and max reset times.", locked, userDNString);
      logger.trace("Returning %b for user %s after comparing the current and max reset times.", locked, userDNString);
    }
    return locked;
@@ -1678,16 +1505,13 @@
  /**
   * Retrieves the time that the user's password should expire (if the
   * expiration is in the future) or did expire (if the expiration was in the
   * past).  Note that this method should be called after the
   * <CODE>lockedDueToMaximumResetAge</CODE> method because grace logins will
   * not be allowed in the case that the maximum reset age has passed whereas
   * they may be used for expiration due to maximum password age or forced
   * change time.
   * Retrieves the time that the user's password should expire (if the expiration is in the future) or
   * did expire (if the expiration was in the past).  Note that this method should be called after the
   * <CODE>lockedDueToMaximumResetAge</CODE> method because grace logins will not be allowed in the case
   * that the maximum reset age has passed whereas they may be used for expiration due to maximum password
   * age or forced change time.
   *
   * @return  The time that the user's password should/did expire, or -1 if it
   *          should not expire.
   * @return  The time that the user's password should/did expire, or -1 if it should not expire.
   */
  public long getPasswordExpirationTime()
  {
@@ -1700,7 +1524,7 @@
      long maxAge = passwordPolicy.getMaxPasswordAge();
      if (maxAge > 0L)
      {
        long expTime = getPasswordChangedTime() + (1000L*maxAge);
        long expTime = getPasswordChangedTime() + 1000L * maxAge;
        if (expTime < passwordExpirationTime)
        {
          passwordExpirationTime = expTime;
@@ -1709,9 +1533,9 @@
      }
      long maxResetAge = passwordPolicy.getMaxPasswordResetAge();
      if (mustChangePassword() && (maxResetAge > 0L))
      if (mustChangePassword() && maxResetAge > 0L)
      {
        long expTime = getPasswordChangedTime() + (1000L*maxResetAge);
        long expTime = getPasswordChangedTime() + 1000L * maxResetAge;
        if (expTime < passwordExpirationTime)
        {
          passwordExpirationTime = expTime;
@@ -1723,8 +1547,7 @@
      if (mustChangeTime > 0)
      {
        long reqChangeTime = getRequiredChangeTime();
        if ((reqChangeTime != mustChangeTime) &&
            (mustChangeTime < passwordExpirationTime))
        if (reqChangeTime != mustChangeTime && mustChangeTime < passwordExpirationTime)
        {
          passwordExpirationTime = mustChangeTime;
          checkWarning   = true;
@@ -1743,24 +1566,20 @@
      {
        mayUseGraceLogin = ConditionResult.TRUE;
        long warningInterval = passwordPolicy
            .getPasswordExpirationWarningInterval();
        long warningInterval = passwordPolicy.getPasswordExpirationWarningInterval();
        if (warningInterval > 0L)
        {
          long shouldWarnTime =
                    passwordExpirationTime - (warningInterval*1000L);
          long shouldWarnTime = passwordExpirationTime - warningInterval * 1000L;
          if (shouldWarnTime > currentTime)
          {
            // The warning time is in the future, so we know the password isn't
            // expired.
            // The warning time is in the future, so we know the password isn't expired.
            shouldWarn        = ConditionResult.FALSE;
            isFirstWarning    = ConditionResult.FALSE;
            isPasswordExpired = ConditionResult.FALSE;
          }
          else
          {
            // We're at least in the warning period, but the password may be
            // expired.
            // We're at least in the warning period, but the password may be expired.
            long theWarnedTime = getWarnedTime();
            if (passwordExpirationTime > currentTime)
@@ -1776,8 +1595,7 @@
                if (! passwordPolicy.isExpirePasswordsWithoutWarning())
                {
                  passwordExpirationTime =
                       currentTime + (warningInterval*1000L);
                  passwordExpirationTime = currentTime + warningInterval * 1000L;
                }
              }
              else
@@ -1786,15 +1604,14 @@
                if (! passwordPolicy.isExpirePasswordsWithoutWarning())
                {
                  passwordExpirationTime =
                      theWarnedTime + (warningInterval*1000L);
                  passwordExpirationTime = theWarnedTime + warningInterval * 1000L;
                }
              }
            }
            else
            {
              // The expiration time has passed, but we may not actually be
              // expired if the user has not yet seen a warning.
              // The expiration time has passed, but we may not actually be expired if the user has not
              // yet seen a warning.
              if (passwordPolicy.isExpirePasswordsWithoutWarning())
              {
                shouldWarn        = ConditionResult.FALSE;
@@ -1803,8 +1620,7 @@
              }
              else if (theWarnedTime > 0)
              {
                passwordExpirationTime =
                    theWarnedTime + (warningInterval*1000L);
                passwordExpirationTime = theWarnedTime + warningInterval*1000L;
                if (passwordExpirationTime > currentTime)
                {
                  shouldWarn        = ConditionResult.TRUE;
@@ -1823,15 +1639,14 @@
                shouldWarn             = ConditionResult.TRUE;
                isFirstWarning         = ConditionResult.TRUE;
                isPasswordExpired      = ConditionResult.FALSE;
                passwordExpirationTime = currentTime + (warningInterval*1000L);
                passwordExpirationTime = currentTime + warningInterval*1000L;
              }
            }
          }
        }
        else
        {
          // There will never be a warning, and the user's password may be
          // expired.
          // There will never be a warning, and the user's password may be expired.
          shouldWarn     = ConditionResult.FALSE;
          isFirstWarning = ConditionResult.FALSE;
@@ -1864,8 +1679,7 @@
    if (logger.isTraceEnabled())
    {
      logger.trace("Returning password expiration time of %d for user " +
          "%s.", passwordExpirationTime, userDNString);
      logger.trace("Returning password expiration time of %d for user %s.", passwordExpirationTime, userDNString);
    }
    return passwordExpirationTime;
@@ -1876,8 +1690,7 @@
  /**
   * Indicates whether the user's password is currently expired.
   *
   * @return  <CODE>true</CODE> if the user's password is currently expired, or
   *          <CODE>false</CODE> if not.
   * @return  <CODE>true</CODE> if the user's password is currently expired, or <CODE>false</CODE> if not.
   */
  public boolean isPasswordExpired()
  {
@@ -1894,17 +1707,14 @@
  }
  /**
   * Indicates whether the user's last password change was within the minimum
   * password age.
   * Indicates whether the user's last password change was within the minimum password age.
   *
   * @return  <CODE>true</CODE> if the password minimum age is nonzero, the
   *          account is not in force-change mode, and the last password change
   *          was within the minimum age, or <CODE>false</CODE> otherwise.
   * @return  <CODE>true</CODE> if the password minimum age is nonzero, the account is not in force-change mode,
   *          and the last password change was within the minimum age, or <CODE>false</CODE> otherwise.
   */
  public boolean isWithinMinimumAge()
  {
    // This feature is reponsible for neither a state field nor entry state
    // attribute.
    // This feature is responsible for neither a state field nor entry state attribute.
    long minAge = passwordPolicy.getMinPasswordAge();
    if (minAge <= 0L)
    {
@@ -1916,13 +1726,12 @@
      return false;
    }
    else if ((getPasswordChangedTime() + (minAge*1000L)) < currentTime)
    else if (getPasswordChangedTime() + minAge * 1000L < currentTime)
    {
      // It's been long enough since the user changed their password.
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false because the minimum age has " +
            "expired.");
        logger.trace("Returning false because the minimum age has expired.");
      }
      return false;
@@ -1932,8 +1741,7 @@
      // The user is in a must-change mode, so the minimum age doesn't apply.
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false because the account is in a " +
            "must-change state.");
        logger.trace("Returning false because the account is in a must-change state.");
      }
      return false;
@@ -1953,15 +1761,12 @@
  /**
   * Indicates whether the user may use a grace login if the password is expired
   * and there is at least one grace login remaining.  Note that this does not
   * check to see if the user's password is expired, does not verify that there
   * are any remaining grace logins, and does not update the set of grace login
   * times.
   * Indicates whether the user may use a grace login if the password is expired and there is at least one
   * grace login remaining.  Note that this does not check to see if the user's password is expired, does not
   * verify that there are any remaining grace logins, and does not update the set of grace login times.
   *
   * @return  <CODE>true</CODE> if the user may use a grace login if the
   *          password is expired and there is at least one grace login
   *          remaining, or <CODE>false</CODE> if the user may not use a grace
   * @return  <CODE>true</CODE> if the user may use a grace login if the password is expired and there is
   *          at least one grace login remaining, or <CODE>false</CODE> if the user may not use a grace
   *          login for some reason.
   */
  public boolean mayUseGraceLogin()
@@ -1973,12 +1778,10 @@
  /**
   * Indicates whether the user should receive a warning notification that the
   * password is about to expire.
   * Indicates whether the user should receive a warning notification that the password is about to expire.
   *
   * @return  <CODE>true</CODE> if the user should receive a warning
   *          notification that the password is about to expire, or
   *          <CODE>false</CODE> if not.
   * @return  <CODE>true</CODE> if the user should receive a warning notification that the password is about to expire,
   *          or <CODE>false</CODE> if not.
   */
  public boolean shouldWarn()
  {
@@ -1989,11 +1792,10 @@
  /**
   * Indicates whether the warning that the user should receive would be the
   * first warning for the user.
   * Indicates whether the warning that the user should receive would be the first warning for the user.
   *
   * @return  <CODE>true</CODE> if the warning that should be sent to the user
   *          would be the first warning, or <CODE>false</CODE> if not.
   * @return  <CODE>true</CODE> if the warning that should be sent to the user would be the first warning,
   *          or <CODE>false</CODE> if not.
   */
  public boolean isFirstWarning()
  {
@@ -2007,8 +1809,7 @@
   * Retrieves the length of time in seconds until the user's password expires.
   *
   * @return  The length of time in seconds until the user's password expires,
   *          0 if the password is currently expired, or -1 if the password
   *          should not expire.
   *          0 if the password is currently expired, or -1 if the password should not expire.
   */
  public int getSecondsUntilExpiration()
  {
@@ -2030,12 +1831,10 @@
  /**
   * Retrieves the timestamp for the last required change time that the user
   * complied with.
   * Retrieves the timestamp for the last required change time that the user complied with.
   *
   * @return  The timestamp for the last required change time that the user
   *          complied with, or -1 if the user's password has not been changed
   *          in compliance with this configuration.
   * @return  The timestamp for the last required change time that the user complied with,
   *          or -1 if the user's password has not been changed in compliance with this configuration.
   */
  public long getRequiredChangeTime()
  {
@@ -2043,15 +1842,13 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning stored required change time of %d for " +
            "user %s", requiredChangeTime, userDNString);
        logger.trace("Returning stored required change time of %d for user %s", requiredChangeTime, userDNString);
      }
      return requiredChangeTime;
    }
    AttributeType type = DirectoryServer.getAttributeType(
                              OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
    try
    {
@@ -2059,15 +1856,13 @@
    }
    catch (Exception e)
    {
      logger.traceException(e, "Returning %d for user %s because an error occurred",
          requiredChangeTime, userDNString);
      logger.traceException(e, "Returning %d for user %s because an error occurred", requiredChangeTime, userDNString);
      requiredChangeTime = -1;
      return requiredChangeTime;
    }
    logger.trace("Returning required change time of %d for user %s",
        requiredChangeTime, userDNString);
    logger.trace("Returning required change time of %d for user %s", requiredChangeTime, userDNString);
    return requiredChangeTime;
  }
@@ -2075,8 +1870,8 @@
  /**
   * Updates the user entry with a timestamp indicating that the password has
   * been changed in accordance with the require change time.
   * Updates the user entry with a timestamp indicating that the password has been changed in accordance
   * with the require change time.
   */
  public void setRequiredChangeTime()
  {
@@ -2090,26 +1885,23 @@
  /**
   * Updates the user entry with a timestamp indicating that the password has
   * been changed in accordance with the require change time.
   * Updates the user entry with a timestamp indicating that the password has been changed in accordance
   * with the require change time.
   *
   * @param  requiredChangeTime  The timestamp to use for the required change
   *                             time value.
   * @param  requiredChangeTime  The timestamp to use for the required change time value.
   */
  public void setRequiredChangeTime(long requiredChangeTime)
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Updating required change time for user %s",
          userDNString);
      logger.trace("Updating required change time for user %s", userDNString);
    }
    if (getRequiredChangeTime() != requiredChangeTime)
    {
      this.requiredChangeTime = requiredChangeTime;
      AttributeType type = DirectoryServer.getAttributeType(
                               OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
      AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
      String timeValue = GeneralizedTimeSyntax.format(requiredChangeTime);
      Attribute a = Attributes.create(type, timeValue);
@@ -2121,54 +1913,46 @@
  /**
   * Updates the user entry to remove any timestamp indicating that the password
   * has been changed in accordance with the required change time.
   * Updates the user entry to remove any timestamp indicating that the password has been changed in accordance
   * with the required change time.
   */
  public void clearRequiredChangeTime()
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Clearing required change time for user %s",
          userDNString);
      logger.trace("Clearing required change time for user %s", userDNString);
    }
    this.requiredChangeTime = Long.MIN_VALUE;
    AttributeType type = DirectoryServer.getAttributeType(
                             OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
    modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
  }
  /**
   * Retrieves the time that the user was first warned about an upcoming
   * expiration.
   * Retrieves the time that the user was first warned about an upcoming expiration.
   *
   * @return  The time that the user was first warned about an upcoming
   *          expiration, or -1 if the user has not been warned.
   * @return  The time that the user was first warned about an upcoming expiration, or -1 if the user has
   *          not been warned.
   */
  public long getWarnedTime()
  {
    if (warnedTime == Long.MIN_VALUE)
    {
      AttributeType type =
           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
      AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
      try
      {
        warnedTime = getGeneralizedTime(userEntry, type);
      }
      catch (Exception e)
      {
        logger.traceException(e, "Unable to decode the warned time for user %s",
            userDNString);
        logger.traceException(e, "Unable to decode the warned time for user %s", userDNString);
        warnedTime = -1;
      }
    }
    logger.trace("Returning a warned time of %d for user %s",
        warnedTime, userDNString);
    logger.trace("Returning a warned time of %d for user %s", warnedTime, userDNString);
    return warnedTime;
  }
@@ -2185,9 +1969,9 @@
  /**
   * Updates the user entry to set the warned time to the specified time.  This
   * method should generally only be used for testing purposes, since the
   * variant that uses the current time is preferred almost everywhere else.
   * Updates the user entry to set the warned time to the specified time.  This method should generally
   * only be used for testing purposes, since the variant that uses the current time is preferred almost
   * everywhere else.
   *
   * @param  warnedTime  The value to use for the warned time.
   */
@@ -2198,8 +1982,7 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Not updating warned time for user %s because " +
            "the warned time is the same as the specified time.",
        logger.trace("Not updating warned time for user %s because the warned time is the same as the specified time.",
            userDNString);
      }
@@ -2208,10 +1991,8 @@
    this.warnedTime = warnedTime;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
    Attribute a = Attributes.create(type, GeneralizedTimeSyntax
        .createGeneralizedTimeValue(currentTime));
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
    Attribute a = Attributes.create(type, GeneralizedTimeSyntax.createGeneralizedTimeValue(currentTime));
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
@@ -2239,8 +2020,7 @@
    }
    warnedTime = -1;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
    Attribute a = Attributes.empty(type);
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
@@ -2253,22 +2033,18 @@
  /**
   * Retrieves the times that the user has authenticated to the server using a
   * grace login.
   * Retrieves the times that the user has authenticated to the server using a grace login.
   *
   * @return  The times that the user has authenticated to the server using a
   *          grace login.
   * @return  The times that the user has authenticated to the server using a grace login.
   */
  public List<Long> getGraceLoginTimes()
  {
    if (graceLoginTimes == null)
    {
      AttributeType type = DirectoryServer.getAttributeType(
                                OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
      AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
      if (type == null)
      {
        type = DirectoryServer.getDefaultAttributeType(
                                    OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
        type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
      }
      try
@@ -2277,13 +2053,11 @@
      }
      catch (Exception e)
      {
        logger.traceException(e, "Error while processing grace login times for user %s",
            userDNString);
        logger.traceException(e, "Error while processing grace login times for user %s", userDNString);
        graceLoginTimes = new ArrayList<Long>();
        modifications.add(new Modification(ModificationType.REPLACE,
              Attributes.empty(type), true));
        modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
      }
    }
@@ -2296,8 +2070,7 @@
  /**
   * Retrieves the number of grace logins that the user has left.
   *
   * @return  The number of grace logins that the user has left, or -1 if grace
   *          logins are not allowed.
   * @return  The number of grace logins that the user has left, or -1 if grace logins are not allowed.
   */
  public int getGraceLoginsRemaining()
  {
@@ -2314,15 +2087,13 @@
  /**
   * Updates the set of grace login times for the user to include the current
   * time.
   * Updates the set of grace login times for the user to include the current time.
   */
  public void updateGraceLoginTimes()
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Updating grace login times for user %s",
          userDNString);
      logger.trace("Updating grace login times for user %s", userDNString);
    }
    List<Long> graceTimes = getGraceLoginTimes();
@@ -2342,12 +2113,10 @@
    }
    graceTimes.add(highestGraceTime); // graceTimes == this.graceLoginTimes
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
    }
    Attribute addAttr = Attributes.create(type, GeneralizedTimeSyntax.format(highestGraceTime));
@@ -2357,14 +2126,14 @@
  /**
   * Specifies the set of grace login use times for the associated user.  If
   * the provided list is empty or {@code null}, then the set will be cleared.
   * Specifies the set of grace login use times for the associated user.  If the provided list is empty
   * or {@code null}, then the set will be cleared.
   *
   * @param  graceLoginTimes  The grace login use times for the associated user.
   */
  public void setGraceLoginTimes(List<Long> graceLoginTimes)
  {
    if ((graceLoginTimes == null) || graceLoginTimes.isEmpty())
    if (graceLoginTimes == null || graceLoginTimes.isEmpty())
    {
      clearGraceLoginTimes();
      return;
@@ -2372,15 +2141,12 @@
    if (logger.isTraceEnabled())
    {
      logger.trace("Updating grace login times for user %s",
          userDNString);
      logger.trace("Updating grace login times for user %s", userDNString);
    }
    this.graceLoginTimes = graceLoginTimes;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC,
                                          true);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC, true);
    AttributeBuilder builder = new AttributeBuilder(type);
    for (Long l : graceLoginTimes)
    {
@@ -2400,8 +2166,7 @@
  {
    if (logger.isTraceEnabled())
    {
      logger.trace("Clearing grace login times for user %s",
          userDNString);
      logger.trace("Clearing grace login times for user %s", userDNString);
    }
    List<Long> graceTimes = getGraceLoginTimes();
@@ -2411,23 +2176,19 @@
    }
    graceTimes.clear(); // graceTimes == this.graceLoginTimes
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
    }
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
  }
  /**
   * Retrieves a list of the clear-text passwords for the user.  If the user
   * does not have any passwords in the clear, then the list will be empty.
   * Retrieves a list of the clear-text passwords for the user.  If the user does not have any passwords
   * in the clear, then the list will be empty.
   *
   * @return  A list of the clear-text passwords for the user.
   */
@@ -2435,8 +2196,7 @@
  {
    LinkedList<ByteString> clearPasswords = new LinkedList<ByteString>();
    List<Attribute> attrList =
         userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    List<Attribute> attrList = userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    if (attrList == null)
    {
@@ -2451,20 +2211,18 @@
      {
        try
        {
          StringBuilder[] pwComponents =
              getPwComponents(usesAuthPasswordSyntax, v);
          StringBuilder[] pwComponents = getPwComponents(usesAuthPasswordSyntax, v);
          String schemeName = pwComponents[0].toString();
          PasswordStorageScheme<?> scheme = (usesAuthPasswordSyntax)
                    ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
                    : DirectoryServer.getPasswordStorageScheme(schemeName);
          PasswordStorageScheme<?> scheme = usesAuthPasswordSyntax
              ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
              : DirectoryServer.getPasswordStorageScheme(schemeName);
          if (scheme == null)
          {
            if (logger.isTraceEnabled())
            {
              logger.trace("User entry %s contains a password with " +
                  "scheme %s that is not defined in the server.",
                                  userDNString, schemeName);
              logger.trace("User entry %s contains a password with scheme %s that is not defined in the server.",
                  userDNString, schemeName);
            }
            continue;
@@ -2472,12 +2230,9 @@
          if (scheme.isReversible())
          {
            ByteString clearValue = (usesAuthPasswordSyntax)
                         ? scheme.getAuthPasswordPlaintextValue(
                               pwComponents[1].toString(),
                               pwComponents[2].toString())
                         : scheme.getPlaintextValue(
                ByteString.valueOf(pwComponents[1].toString()));
            ByteString clearValue = usesAuthPasswordSyntax
                         ? scheme.getAuthPasswordPlaintextValue(pwComponents[1].toString(), pwComponents[2].toString())
                         : scheme.getPlaintextValue(ByteString.valueOf(pwComponents[1].toString()));
            clearPasswords.add(clearValue);
          }
        }
@@ -2487,8 +2242,7 @@
          if (logger.isTraceEnabled())
          {
            logger.trace("Cannot get clear password value foruser %s: " +
                "%s", userDNString, e);
            logger.trace("Cannot get clear password value for user %s: %s", userDNString, e);
          }
        }
      }
@@ -2503,15 +2257,13 @@
  @Override
  public boolean passwordMatches(ByteString password)
  {
    List<Attribute> attrList =
         userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    if ((attrList == null) || attrList.isEmpty())
    List<Attribute> attrList = userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    if (attrList == null || attrList.isEmpty())
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false because user %s does not have " +
            "any values for password attribute %s", userDNString,
            passwordPolicy.getPasswordAttribute().getNameOrOID());
        logger.trace("Returning false because user %s does not have any values for password attribute %s",
            userDNString, passwordPolicy.getPasswordAttribute().getNameOrOID());
      }
      return false;
@@ -2525,38 +2277,31 @@
      {
        try
        {
          StringBuilder[] pwComponents =
              getPwComponents(usesAuthPasswordSyntax, v);
          StringBuilder[] pwComponents = getPwComponents(usesAuthPasswordSyntax, v);
          String schemeName = pwComponents[0].toString();
          PasswordStorageScheme<?> scheme = (usesAuthPasswordSyntax)
          PasswordStorageScheme<?> scheme = usesAuthPasswordSyntax
                     ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
                     : DirectoryServer.getPasswordStorageScheme(schemeName);
          if (scheme == null)
          {
            if (logger.isTraceEnabled())
            {
              logger.trace("User entry %s contains a password with " +
                  "scheme %s that is not defined in the server.",
              logger.trace("User entry %s contains a password with scheme %s that is not defined in the server.",
                                  userDNString, schemeName);
            }
            continue;
          }
          boolean passwordMatches = (usesAuthPasswordSyntax)
                     ? scheme.authPasswordMatches(password,
                                                  pwComponents[1].toString(),
                                                  pwComponents[2].toString())
                     : scheme.passwordMatches(password,
              ByteString.valueOf(pwComponents[1].toString()));
          boolean passwordMatches = usesAuthPasswordSyntax
              ? scheme.authPasswordMatches(password, pwComponents[1].toString(), pwComponents[2].toString())
              : scheme.passwordMatches(password, ByteString.valueOf(pwComponents[1].toString()));
          if (passwordMatches)
          {
            if (logger.isTraceEnabled())
            {
              logger.trace("Returning true for user %s because the " +
                  "provided password matches a value encoded with scheme %s",
                  userDNString, schemeName);
              logger.trace("Returning true for user %s because the provided password matches a value " +
                      "encoded with scheme %s", userDNString, schemeName);
            }
            return true;
@@ -2564,16 +2309,15 @@
        }
        catch (Exception e)
        {
          logger.traceException(e, "An error occurred while attempting to " +
              "process a password value for user %s", userDNString);
          logger.traceException(e, "An error occurred while attempting to process a password value for user %s",
              userDNString);
        }
      }
    }
    // If we've gotten here, then we couldn't find a match.
    logger.trace("Returning false because the provided password does " +
        "not match any of the stored password values for user %s",
        userDNString);
    logger.trace("Returning false because the provided password does not match any of the stored password " +
            "values for user %s", userDNString);
    return false;
  }
@@ -2587,8 +2331,7 @@
   *
   * @return An array of components.
   */
  private StringBuilder[] getPwComponents(boolean usesAuthPasswordSyntax,
      ByteString v) throws DirectoryException
  private StringBuilder[] getPwComponents(boolean usesAuthPasswordSyntax, ByteString v) throws DirectoryException
  {
    if (usesAuthPasswordSyntax)
    {
@@ -2611,8 +2354,7 @@
   *
   * @param  passwordValue  The value for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the provided password value is pre-encoded,
   *          or <CODE>false</CODE> if it is not.
   * @return  <CODE>true</CODE> if the provided password value is pre-encoded, or <CODE>false</CODE> if it is not.
   */
  public boolean passwordIsPreEncoded(ByteString passwordValue)
  {
@@ -2629,23 +2371,20 @@
  /**
   * Encodes the provided password using the default storage schemes (using the
   * appropriate syntax for the password attribute).
   * Encodes the provided password using the default storage schemes (using the appropriate syntax for the
   * password attribute).
   *
   * @param  password  The password to be encoded.
   *
   * @return  The password encoded using the default schemes.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the password.
   * @throws  DirectoryException  If a problem occurs while attempting to encode the password.
   */
  public List<ByteString> encodePassword(ByteString password)
         throws DirectoryException
  {
    List<PasswordStorageScheme<?>> schemes =
         passwordPolicy.getDefaultPasswordStorageSchemes();
    List<ByteString> encodedPasswords =
         new ArrayList<ByteString>(schemes.size());
    List<PasswordStorageScheme<?>> schemes = passwordPolicy.getDefaultPasswordStorageSchemes();
    List<ByteString> encodedPasswords = new ArrayList<ByteString>(schemes.size());
    if (passwordPolicy.isAuthPasswordSyntax())
    {
@@ -2668,36 +2407,28 @@
  /**
   * Indicates whether the provided password appears to be acceptable according
   * to the password validators.
   * Indicates whether the provided password appears to be acceptable according to the password validators.
   *
   * @param  operation         The operation that provided the password.
   * @param  userEntry         The user entry in which the password is used.
   * @param  newPassword       The password to be validated.
   * @param  currentPasswords  The set of clear-text current passwords for the
   *                           user (this may be a subset if not all of them are
   *                           available in the clear, or empty if none of them
   * @param  currentPasswords  The set of clear-text current passwords for the user (this may be a subset
   *                           if not all of them are available in the clear, or empty if none of them
   *                           are available in the clear).
   * @param  invalidReason     A buffer that may be used to hold the invalid
   *                           reason if the password is rejected.
   * @param  invalidReason     A buffer that may be used to hold the invalid reason if the password is rejected.
   *
   * @return  <CODE>true</CODE> if the password is acceptable for use, or
   *          <CODE>false</CODE> if it is not.
   * @return  <CODE>true</CODE> if the password is acceptable for use, or <CODE>false</CODE> if it is not.
   */
  public boolean passwordIsAcceptable(Operation operation, Entry userEntry,
      ByteString newPassword, Set<ByteString> currentPasswords,
      LocalizableMessageBuilder invalidReason)
  public boolean passwordIsAcceptable(Operation operation, Entry userEntry, ByteString newPassword,
                                      Set<ByteString> currentPasswords, LocalizableMessageBuilder invalidReason)
  {
    for (PasswordValidator<?> validator : passwordPolicy
        .getPasswordValidators())
    for (PasswordValidator<?> validator : passwordPolicy.getPasswordValidators())
    {
      if (!validator.passwordIsAcceptable(newPassword, currentPasswords,
          operation, userEntry, invalidReason))
      if (!validator.passwordIsAcceptable(newPassword, currentPasswords, operation, userEntry, invalidReason))
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("The password provided for user %s failed validation: %s",
              userDNString, invalidReason);
          logger.trace("The password provided for user %s failed validation: %s", userDNString, invalidReason);
        }
        return false;
      }
@@ -2708,9 +2439,8 @@
  /**
   * Performs any processing that may be necessary to remove deprecated storage
   * schemes from the user's entry that match the provided password and
   * re-encodes them using the default schemes.
   * Performs any processing that may be necessary to remove deprecated storage schemes from the user's entry
   * that match the provided password and re-encodes them using the default schemes.
   *
   * @param  password  The clear-text password provided by the user.
   */
@@ -2720,8 +2450,8 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Doing nothing for user %s because no " +
            "deprecated storage schemes have been defined.", userDNString);
        logger.trace("Doing nothing for user %s because no deprecated storage schemes have been defined.",
            userDNString);
      }
      return;
@@ -2730,12 +2460,11 @@
    AttributeType type = passwordPolicy.getPasswordAttribute();
    List<Attribute> attrList = userEntry.getAttribute(type);
    if ((attrList == null) || attrList.isEmpty())
    if (attrList == null || attrList.isEmpty())
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Doing nothing for entry %s because no password " +
            "values were found.", userDNString);
        logger.trace("Doing nothing for entry %s because no password values were found.", userDNString);
      }
      return;
@@ -2750,90 +2479,62 @@
    for (Attribute a : attrList)
    {
      Iterator<ByteString> iterator = a.iterator();
      while (iterator.hasNext())
      {
        ByteString v = iterator.next();
        try
        {
          StringBuilder[] pwComponents =
              getPwComponents(usesAuthPasswordSyntax, v);
      for (ByteString v : a) {
        try {
          StringBuilder[] pwComponents = getPwComponents(usesAuthPasswordSyntax, v);
          String schemeName = pwComponents[0].toString();
          PasswordStorageScheme<?> scheme = (usesAuthPasswordSyntax)
                    ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
                    : DirectoryServer.getPasswordStorageScheme(schemeName);
          if (scheme == null)
          {
            if (logger.isTraceEnabled())
            {
              logger.trace("Skipping password value for user %s " +
                  "because the associated storage scheme %s is not " +
                  "configured for use.", userDNString, schemeName);
          PasswordStorageScheme<?> scheme = usesAuthPasswordSyntax
              ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
              : DirectoryServer.getPasswordStorageScheme(schemeName);
          if (scheme == null) {
            if (logger.isTraceEnabled()) {
              logger.trace("Skipping password value for user %s because the associated storage scheme %s " +
                  "is not configured for use.", userDNString, schemeName);
            }
            continue;
          }
          boolean passwordMatches = (usesAuthPasswordSyntax)
                     ? scheme.authPasswordMatches(password,
                                                  pwComponents[1].toString(),
                                                  pwComponents[2].toString())
                     : scheme.passwordMatches(password,
              ByteString.valueOf(pwComponents[1].toString()));
          if (passwordMatches)
          {
            if (passwordPolicy.isDefaultPasswordStorageScheme(schemeName))
            {
          boolean passwordMatches = usesAuthPasswordSyntax
              ? scheme.authPasswordMatches(password, pwComponents[1].toString(), pwComponents[2].toString())
              : scheme.passwordMatches(password, ByteString.valueOf(pwComponents[1].toString()));
          if (passwordMatches) {
            if (passwordPolicy.isDefaultPasswordStorageScheme(schemeName)) {
              existingDefaultSchemes.add(schemeName);
              updatedValues.add(v);
            }
            else if (passwordPolicy
                .isDeprecatedPasswordStorageScheme(schemeName))
            {
              if (logger.isTraceEnabled())
              {
                logger.trace("Marking password with scheme %s for " +
                    "removal from user entry %s.", schemeName, userDNString);
            } else if (passwordPolicy.isDeprecatedPasswordStorageScheme(schemeName)) {
              if (logger.isTraceEnabled()) {
                logger.trace("Marking password with scheme %s for removal from user entry %s.",
                    schemeName, userDNString);
              }
              removedValues.add(v);
            }
            else
            {
            } else {
              updatedValues.add(v);
            }
          }
        }
        catch (Exception e)
        {
          logger.traceException(e, "Skipping password value for user %s because " +
              "an error occurred while attempting to decode it based on " +
              "the user password syntax", userDNString);
        } catch (Exception e) {
          logger.traceException(e, "Skipping password value for user %s because an error occurred while attempting " +
              "to decode it based on the user password syntax", userDNString);
        }
      }
    }
    if (removedValues.isEmpty())
    {
      logger.trace("User entry %s does not have any password values " +
          "encoded using deprecated schemes.", userDNString);
      logger.trace("User entry %s does not have any password values encoded using deprecated schemes.", userDNString);
      return;
    }
    LinkedHashSet<ByteString> addedValues = new LinkedHashSet<ByteString>();
    for (PasswordStorageScheme<?> s :
         passwordPolicy.getDefaultPasswordStorageSchemes())
    for (PasswordStorageScheme<?> s : passwordPolicy.getDefaultPasswordStorageSchemes())
    {
      if (! existingDefaultSchemes.contains(
           toLowerCase(s.getStorageSchemeName())))
      if (! existingDefaultSchemes.contains(toLowerCase(s.getStorageSchemeName())))
      {
        try
        {
          ByteString encodedPassword = (usesAuthPasswordSyntax)
                                       ? s.encodeAuthPassword(password)
                                       : s.encodePasswordWithScheme(password);
          ByteString encodedPassword =
              usesAuthPasswordSyntax ? s.encodeAuthPassword(password) : s.encodePasswordWithScheme(password);
          addedValues.add(encodedPassword);
          updatedValues.add(encodedPassword);
        }
@@ -2843,8 +2544,8 @@
          if (logger.isTraceEnabled())
          {
            logger.traceException(e, "Unable to encode password for user %s using " +
                 "default scheme %s", userDNString, s.getStorageSchemeName());
            logger.traceException(e, "Unable to encode password for user %s using default scheme %s",
                userDNString, s.getStorageSchemeName());
          }
        }
      }
@@ -2852,8 +2553,8 @@
    if (updatedValues.isEmpty())
    {
      logger.trace("Not updating user entry %s because removing " +
          "deprecated schemes would leave the user without a password.",
      logger.trace(
          "Not updating user entry %s because removing deprecated schemes would leave the user without a password.",
          userDNString);
      return;
    }
@@ -2873,38 +2574,34 @@
    if (logger.isTraceEnabled())
    {
      logger.trace("Updating user entry %s to replace password values " +
          "encoded with deprecated schemes with values encoded " +
          "with the default schemes.", userDNString);
      logger.trace("Updating user entry %s to replace password values encoded with deprecated schemes " +
          "with values encoded with the default schemes.", userDNString);
    }
  }
  /**
   * Indicates whether password history information should be matained for this
   * user.
   * Indicates whether password history information should be maintained for this user.
   *
   * @return  {@code true} if password history information should be maintained
   *          for this user, or {@code false} if not.
   * @return  {@code true} if password history information should be maintained for this user, or {@code false} if not.
   */
  public boolean maintainHistory()
  {
    return ((passwordPolicy.getPasswordHistoryCount() > 0) ||
            (passwordPolicy.getPasswordHistoryDuration() > 0));
    return passwordPolicy.getPasswordHistoryCount() > 0
        || passwordPolicy.getPasswordHistoryDuration() > 0;
  }
  /**
   * Indicates whether the provided password is equal to any of the current
   * passwords, or any of the passwords in the history.
   * Indicates whether the provided password is equal to any of the current passwords,
   * or any of the passwords in the history.
   *
   * @param  password  The password for which to make the determination.
   *
   * @return  {@code true} if the provided password is equal to any of the
   *          current passwords or any of the passwords in the history, or
   *          {@code false} if not.
   * @return  {@code true} if the provided password is equal to any of the current passwords or any of the passwords
   *          in the history, or {@code false} if not.
   */
  public boolean isPasswordInHistory(ByteString password)
  {
@@ -2912,40 +2609,32 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning false because password history " +
            "checking is disabled.");
        logger.trace("Returning false because password history checking is disabled.");
      }
      // Password history checking is disabled, so we don't care if it is in the
      // list or not.
      return false;
    }
    // Check to see if the provided password is equal to any of the current
    // passwords.  If so, then we'll consider it to be in the history.
    // Check to see if the provided password is equal to any of the current passwords.
    // If so, then we'll consider it to be in the history.
    if (passwordMatches(password))
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Returning true because the provided password " +
            "is currently in use.");
        logger.trace("Returning true because the provided password is currently in use.");
      }
      return true;
    }
    // Get the attribute containing the history and check to see if any of the
    // values is equal to the provided password.  However, first prune the list
    // by size and duration if necessary.
    // Get the attribute containing the history and check to see if any of the values is equal to the provided password.
    // However, first prune the list by size and duration if necessary.
    TreeMap<Long, ByteString> historyMap = getSortedHistoryValues(null);
    int historyCount = passwordPolicy.getPasswordHistoryCount();
    if ((historyCount > 0) && (historyMap.size() > historyCount))
    if (historyCount > 0 && historyMap.size() > historyCount)
    {
      int numToDelete = historyMap.size() - historyCount;
      Iterator<Long> iterator = historyMap.keySet().iterator();
      while ((iterator.hasNext()) && (numToDelete > 0))
      while (iterator.hasNext() && numToDelete > 0)
      {
        iterator.next();
        iterator.remove();
@@ -2956,7 +2645,7 @@
    long historyDuration = passwordPolicy.getPasswordHistoryDuration();
    if (historyDuration > 0L)
    {
      long retainDate = currentTime - (1000 * historyDuration);
      long retainDate = currentTime - 1000 * historyDuration;
      Iterator<Long> iterator = historyMap.keySet().iterator();
      while (iterator.hasNext())
      {
@@ -2978,41 +2667,34 @@
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("Returning true because the password is in " +
              "the history.");
          logger.trace("Returning true because the password is in the history.");
        }
        return true;
      }
    }
    // If we've gotten here, then the password isn't in the history.
    if (logger.isTraceEnabled())
    {
      logger.trace("Returning false because the password isn't in the " +
          "history.");
      logger.trace("Returning false because the password isn't in the history.");
    }
    return false;
  }
  /**
   * Gets a sorted list of the password history values contained in the user's
   * entry.  The values will be sorted by timestamp.
   * Gets a sorted list of the password history values contained in the user's entry.
   * The values will be sorted by timestamp.
   *
   * @param  removeAttrs  A list into which any values will be placed that could
   *                      not be properly decoded.  It may be {@code null} if
   *                      this is not needed.
   * @param  removeAttrs  A list into which any values will be placed that could not be properly decoded.
   *                      It may be {@code null} if this is not needed.
   */
  private TreeMap<Long,ByteString> getSortedHistoryValues(List<Attribute>
                                                                   removeAttrs)
  private TreeMap<Long,ByteString> getSortedHistoryValues(List<Attribute> removeAttrs)
  {
    TreeMap<Long, ByteString> historyMap = new TreeMap<Long, ByteString>();
    AttributeType historyType =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true);
    AttributeType historyType = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true);
    List<Attribute> attrList = userEntry.getAttribute(historyType);
    if (attrList != null)
    {
@@ -3026,9 +2708,7 @@
          {
            if (logger.isTraceEnabled())
            {
              logger.trace("Found value " + histStr + " in the " +
                  "history with no timestamp.  Marking it " +
                  "for removal.");
              logger.trace("Found value " + histStr + " in the history with no timestamp.  Marking it for removal.");
            }
            if (removeAttrs != null)
@@ -3041,8 +2721,7 @@
            try
            {
              long timestamp =
                   GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
                       ByteString.valueOf(histStr.substring(0, hashPos)));
                   GeneralizedTimeSyntax.decodeGeneralizedTimeValue(ByteString.valueOf(histStr.substring(0, hashPos)));
              historyMap.put(timestamp, v);
            }
            catch (Exception e)
@@ -3051,15 +2730,13 @@
              {
                logger.traceException(e);
                logger.trace("Could not decode the timestamp in " +
                    "history value " + histStr + " -- " + e +
                logger.trace("Could not decode the timestamp in history value " + histStr + " -- " + e +
                    ".  Marking it for removal.");
              }
              if (removeAttrs != null)
              {
                removeAttrs.add(Attributes
                    .create(a.getAttributeType(), v));
                removeAttrs.add(Attributes.create(a.getAttributeType(), v));
              }
            }
          }
@@ -3075,20 +2752,14 @@
  /**
   * Indicates whether the provided password matches the given history value.
   *
   * @param  password      The clear-text password for which to make the
   *                       determination.
   * @param  historyValue  The encoded history value to compare against the
   *                       clear-text password.
   * @param  password      The clear-text password for which to make the determination.
   * @param  historyValue  The encoded history value to compare against the clear-text password.
   *
   * @return  {@code true} if the provided password matches the history value,
   *          or {@code false} if not.
   * @return  {@code true} if the provided password matches the history value, or {@code false} if not.
   */
  private boolean historyValueMatches(ByteString password,
                                      ByteString historyValue)
  {
    // According to draft-behera-ldap-password-policy, password history values
    // should be in the format time#syntaxoid#encodedvalue.  In this method,
    // we only care about the syntax OID and encoded password.
  private boolean historyValueMatches(ByteString password, ByteString historyValue) {
    // According to draft-behera-ldap-password-policy, password history values should be in the format
    // time#syntaxoid#encodedvalue.  In this method, we only care about the syntax OID and encoded password.
    try
    {
      String histStr  = historyValue.toString();
@@ -3097,8 +2768,7 @@
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("Returning false because the password history " +
              "value didn't include any hash characters.");
          logger.trace("Returning false because the password history value didn't include any hash characters.");
        }
        return false;
@@ -3109,8 +2779,7 @@
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("Returning false because the password history " +
              "value only had one hash character.");
          logger.trace("Returning false because the password history value only had one hash character.");
        }
        return false;
@@ -3119,19 +2788,13 @@
      String syntaxOID = toLowerCase(histStr.substring(hashPos1+1, hashPos2));
      if (syntaxOID.equals(SYNTAX_AUTH_PASSWORD_OID))
      {
        StringBuilder[] authPWComponents =
             AuthPasswordSyntax.decodeAuthPassword(
                  histStr.substring(hashPos2+1));
        PasswordStorageScheme<?> scheme =
             DirectoryServer.getAuthPasswordStorageScheme(
                  authPWComponents[0].toString());
        if (scheme.authPasswordMatches(password, authPWComponents[1].toString(),
                                       authPWComponents[2].toString()))
        StringBuilder[] authPWComponents = AuthPasswordSyntax.decodeAuthPassword(histStr.substring(hashPos2+1));
        PasswordStorageScheme<?> scheme = DirectoryServer.getAuthPasswordStorageScheme(authPWComponents[0].toString());
        if (scheme.authPasswordMatches(password, authPWComponents[1].toString(), authPWComponents[2].toString()))
        {
          if (logger.isTraceEnabled())
          {
            logger.trace("Returning true because the auth password " +
                "history value matched.");
            logger.trace("Returning true because the auth password history value matched.");
          }
          return true;
@@ -3140,8 +2803,7 @@
        {
          if (logger.isTraceEnabled())
          {
            logger.trace("Returning false because the auth password " +
                "history value did not match.");
            logger.trace("Returning false because the auth password history value did not match.");
          }
          return false;
@@ -3149,18 +2811,13 @@
      }
      else if (syntaxOID.equals(SYNTAX_USER_PASSWORD_OID))
      {
        String[] userPWComponents =
             UserPasswordSyntax.decodeUserPassword(
                  histStr.substring(hashPos2+1));
        PasswordStorageScheme<?> scheme =
             DirectoryServer.getPasswordStorageScheme(userPWComponents[0]);
        if (scheme.passwordMatches(password,
            ByteString.valueOf(userPWComponents[1])))
        String[] userPWComponents = UserPasswordSyntax.decodeUserPassword(histStr.substring(hashPos2+1));
        PasswordStorageScheme<?> scheme = DirectoryServer.getPasswordStorageScheme(userPWComponents[0]);
        if (scheme.passwordMatches(password, ByteString.valueOf(userPWComponents[1])))
        {
          if (logger.isTraceEnabled())
          {
            logger.trace("Returning true because the user password " +
                "history value matched.");
            logger.trace("Returning true because the user password history value matched.");
          }
          return true;
@@ -3169,8 +2826,7 @@
        {
          if (logger.isTraceEnabled())
          {
            logger.trace("Returning false because the user password " +
                "history value did not match.");
            logger.trace("Returning false because the user password history value did not match.");
          }
          return false;
@@ -3180,9 +2836,8 @@
      {
        if (logger.isTraceEnabled())
        {
          logger.trace("Returning false because the syntax OID " +
              syntaxOID + " didn't match for either the auth " +
              "or user password syntax.");
          logger.trace("Returning false because the syntax OID " + syntaxOID +
              " didn't match for either the auth or user password syntax.");
        }
        return false;
@@ -3196,8 +2851,7 @@
        if (logger.isTraceEnabled())
        {
          logger.trace("Returning false because of an exception:  " +
                           stackTraceToSingleLineString(e));
          logger.trace("Returning false because of an exception:  " + stackTraceToSingleLineString(e));
        }
      }
@@ -3208,15 +2862,13 @@
  /**
   * Updates the password history information for this user by adding one of
   * the passwords to it. It will choose the first password encoded using a
   * secure storage scheme, and will fall back to a password encoded using an
   * insecure storage scheme if necessary.
   * Updates the password history information for this user by adding one of the passwords to it.
   * It will choose the first password encoded using a secure storage scheme, and will fall back to
   * a password encoded using an insecure storage scheme if necessary.
   */
  public void updatePasswordHistory()
  {
    List<Attribute> attrList =
         userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    List<Attribute> attrList = userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    if (attrList != null)
    {
      for (Attribute a : attrList)
@@ -3244,13 +2896,11 @@
          {
            if (logger.isTraceEnabled())
            {
              logger.trace("Encoded password " + v +
                      " cannot be decoded and cannot be added to history.");
              logger.trace("Encoded password " + v + " cannot be decoded and cannot be added to history.");
            }
          }
        }
        // If we get here we haven't found a password encoded securely, so we
        // have to use one of the other values.
        // If we get here we haven't found a password encoded securely, so we have to use one of the other values.
        if (insecurePassword != null)
        {
          addPasswordToHistory(insecurePassword.toString());
@@ -3262,13 +2912,12 @@
  /**
   * Adds the provided password to the password history.  If appropriate, one or
   * more old passwords may be evicted from the list if the total size would
   * exceed the configured count, or if passwords are older than the configured
   * duration.
   * Adds the provided password to the password history.  If appropriate, one or more old passwords may be
   * evicted from the list if the total size would exceed the configured count, or if passwords are older
   * than the configured duration.
   *
   * @param  encodedPassword  The encoded password (in either user password or
   *                          auth password format) to be added to the history.
   * @param  encodedPassword  The encoded password (in either user password or auth password format)
   *                          to be added to the history.
   */
  private void addPasswordToHistory(String encodedPassword)
  {
@@ -3276,33 +2925,28 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Not doing anything because password history " +
            "maintenance is disabled.");
        logger.trace("Not doing anything because password history maintenance is disabled.");
      }
      return;
    }
    // Get a sorted list of the existing values to see if there are any that
    // should be removed.
    // Get a sorted list of the existing values to see if there are any that should be removed.
    LinkedList<Attribute> removeAttrs = new LinkedList<Attribute>();
    TreeMap<Long, ByteString> historyMap = getSortedHistoryValues(removeAttrs);
    // If there is a maximum number of values to retain and we would be over the
    // limit with the new value, then get rid of enough values (oldest first)
    // to satisfy the count.
    AttributeType historyType =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true);
    // If there is a maximum number of values to retain and we would be over the limit with the new value,
    // then get rid of enough values (oldest first) to satisfy the count.
    AttributeType historyType = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true);
    int historyCount = passwordPolicy.getPasswordHistoryCount();
    if  ((historyCount > 0) && (historyMap.size() >= historyCount))
    if  (historyCount > 0 && historyMap.size() >= historyCount)
    {
      int numToDelete = (historyMap.size() - historyCount) + 1;
      LinkedHashSet<ByteString> removeValues =
           new LinkedHashSet<ByteString>(numToDelete);
      int numToDelete = historyMap.size() - historyCount + 1;
      LinkedHashSet<ByteString> removeValues = new LinkedHashSet<ByteString>(numToDelete);
      Iterator<ByteString> iterator = historyMap.values().iterator();
      while (iterator.hasNext() && (numToDelete > 0))
      while (iterator.hasNext() && numToDelete > 0)
      {
        ByteString v = iterator.next();
        removeValues.add(v);
@@ -3324,12 +2968,11 @@
    }
    // If there is a maximum duration, then get rid of any values that would be
    // over the duration.
    // If there is a maximum duration, then get rid of any values that would be over the duration.
    long historyDuration = passwordPolicy.getPasswordHistoryDuration();
    if (historyDuration > 0L)
    {
      long minAgeToKeep = currentTime - (1000L * historyDuration);
      long minAgeToKeep = currentTime - 1000L * historyDuration;
      Iterator<Long> iterator = historyMap.keySet().iterator();
      LinkedHashSet<ByteString> removeValues = new LinkedHashSet<ByteString>();
      while (iterator.hasNext())
@@ -3361,18 +3004,16 @@
    }
    // At this point, we can add the new value.  However, we want to make sure
    // that its timestamp (which is the current time) doesn't conflict with any
    // value already in the list.  If there is a conflict, then simply add one
    // to it until we don't have any more conflicts.
    // At this point, we can add the new value.  However, we want to make sure that its timestamp
    // (which is the current time) doesn't conflict with any value already in the list.  If there is a conflict,
    // then simply add one to it until we don't have any more conflicts.
    long newTimestamp = currentTime;
    while (historyMap.containsKey(newTimestamp))
    {
      newTimestamp++;
    }
    String newHistStr = GeneralizedTimeSyntax.format(newTimestamp) + "#" +
                        passwordPolicy.getPasswordAttribute().getSyntax().getOID() +
                        "#" + encodedPassword;
        passwordPolicy.getPasswordAttribute().getSyntax().getOID() + "#" + encodedPassword;
    Attribute newHistAttr = Attributes.create(historyType, newHistStr);
    if (logger.isTraceEnabled())
@@ -3381,30 +3022,26 @@
    }
    // Apply the changes, either by adding modifications or by directly updating
    // the entry.
    // Apply the changes, either by adding modifications or by directly updating the entry.
    for (Attribute a : removeAttrs)
    {
      modifications.add(new Modification(ModificationType.DELETE, a, true));
    }
    modifications.add(new Modification(ModificationType.ADD, newHistAttr,
        true));
    modifications.add(new Modification(ModificationType.ADD, newHistAttr, true));
  }
  /**
   * Retrieves the password history state values for the user.  This is only
   * intended for testing purposes.
   * Retrieves the password history state values for the user.  This is only intended for testing purposes.
   *
   * @return  The password history state values for the user.
   */
  public String[] getPasswordHistoryValues()
  {
    ArrayList<String> historyValues = new ArrayList<String>();
    AttributeType historyType =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true);
    AttributeType historyType = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true);
    List<Attribute> attrList = userEntry.getAttribute(historyType);
    if (attrList != null)
    {
@@ -3424,8 +3061,7 @@
  /**
   * Clears the password history state information for the user.  This is only
   * intended for testing purposes.
   * Clears the password history state information for the user.  This is only intended for testing purposes.
   */
  public void clearPasswordHistory()
  {
@@ -3434,22 +3070,17 @@
      logger.trace("Clearing password history for user %s", userDNString);
    }
    AttributeType type = DirectoryServer.getAttributeType(
                             OP_ATTR_PWPOLICY_HISTORY_LC, true);
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    AttributeType type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true);
    modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true));
  }
  /**
   * Generates a new password for the user.
   *
   * @return  The new password that has been generated, or <CODE>null</CODE> if
   *          no password generator has been defined.
   * @return  The new password that has been generated, or <CODE>null</CODE> if no password generator has been defined.
   *
   * @throws  DirectoryException  If an error occurs while attempting to
   *                              generate the new password.
   * @throws  DirectoryException  If an error occurs while attempting to generate the new password.
   */
  public ByteString generatePassword()
      throws DirectoryException
@@ -3459,9 +3090,8 @@
    {
      if (logger.isTraceEnabled())
      {
        logger.trace("Unable to generate a new password for user " +
            "%s because no password generator has been defined in the " +
            "associated password policy.", userDNString);
        logger.trace("Unable to generate a new password for user %s because no password generator has been defined" +
            "in the associated password policy.", userDNString);
      }
      return null;
@@ -3475,22 +3105,18 @@
  /**
   * Generates an account status notification for this user.
   *
   * @param  notificationType        The type for the account status
   *                                 notification.
   * @param  userEntry               The entry for the user to which this
   *                                 notification applies.
   * @param  message                 The human-readable message for the
   *                                 notification.
   * @param  notificationType        The type for the account status notification.
   * @param  userEntry               The entry for the user to which this notification applies.
   * @param  message                 The human-readable message for the notification.
   * @param  notificationProperties  The set of properties for the notification.
   */
  public void generateAccountStatusNotification(
          AccountStatusNotificationType notificationType,
          Entry userEntry, LocalizableMessage message,
          Map<AccountStatusNotificationProperty,List<String>>
               notificationProperties)
      AccountStatusNotificationType notificationType,
      Entry userEntry, LocalizableMessage message,
      Map<AccountStatusNotificationProperty,List<String>> notificationProperties)
  {
    generateAccountStatusNotification(new AccountStatusNotification(
         notificationType, userEntry, message, notificationProperties));
    generateAccountStatusNotification(
        new AccountStatusNotification(notificationType, userEntry, message, notificationProperties));
  }
@@ -3498,14 +3124,11 @@
  /**
   * Generates an account status notification for this user.
   *
   * @param  notification  The account status notification that should be
   *                       generated.
   * @param  notification  The account status notification that should be generated.
   */
  public void generateAccountStatusNotification(
                   AccountStatusNotification notification)
  public void generateAccountStatusNotification(AccountStatusNotification notification)
  {
    Collection<AccountStatusNotificationHandler<?>> handlers =
         passwordPolicy.getAccountStatusNotificationHandlers();
    Collection<AccountStatusNotificationHandler<?>> handlers = passwordPolicy.getAccountStatusNotificationHandlers();
    for (AccountStatusNotificationHandler<?> handler : handlers)
    {
      handler.handleStatusNotification(notification);
@@ -3515,12 +3138,11 @@
  /**
   * Retrieves the set of modifications that correspond to changes made in
   * password policy processing that may need to be applied to the user entry.
   * Retrieves the set of modifications that correspond to changes made in password policy processing
   * that may need to be applied to the user entry.
   *
   * @return  The set of modifications that correspond to changes made in
   *          password policy processing that may need to be applied to the user
   *          entry.
   * @return  The set of modifications that correspond to changes made in password policy processing
   *          that may need to be applied to the user entry.
   */
  public List<Modification> getModifications()
  {
@@ -3540,32 +3162,26 @@
      return;
    }
    // Convert the set of modifications to a set of LDAP modifications.
    ArrayList<RawModification> modList = new ArrayList<RawModification>();
    for (Modification m : modifications)
    {
      modList.add(RawModification.create(m.getModificationType(),
                       new LDAPAttribute(m.getAttribute())));
      modList.add(RawModification.create(m.getModificationType(), new LDAPAttribute(m.getAttribute())));
    }
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation internalModify =
         conn.processModify(ByteString.valueOf(userDNString), modList);
    InternalClientConnection conn = getRootConnection();
    ModifyOperation internalModify = conn.processModify(ByteString.valueOf(userDNString), modList);
    ResultCode resultCode = internalModify.getResultCode();
    if (resultCode != ResultCode.SUCCESS)
    {
      LocalizableMessage message = ERR_PWPSTATE_CANNOT_UPDATE_USER_ENTRY.get(userDNString,
                            internalModify.getErrorMessage());
      LocalizableMessage message = ERR_PWPSTATE_CANNOT_UPDATE_USER_ENTRY.get(
          userDNString, internalModify.getErrorMessage());
      // If this is a root user, or if the password policy says that we should
      // ignore these problems, then log a warning message.  Otherwise, cause
      // the bind to fail.
      if ((DirectoryServer.isRootDN(userEntry.getName()) ||
          (passwordPolicy.getStateUpdateFailurePolicy() ==
           PasswordPolicyCfgDefn.StateUpdateFailurePolicy.IGNORE)))
      // If this is a root user, or if the password policy says that we should ignore these problems,
      // then log a warning message.  Otherwise, cause the bind to fail.
      if (DirectoryServer.isRootDN(userEntry.getName())
          || passwordPolicy.getStateUpdateFailurePolicy() == PasswordPolicyCfgDefn.StateUpdateFailurePolicy.IGNORE)
      {
        logger.error(message);
      }