From 93a5c2c6d0c5e2acc2fd59618af6537af33f99f7 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Wed, 27 Jun 2007 22:15:31 +0000
Subject: [PATCH] Add a new extended operation that can be used to interact with password policy state information for a user, including getting and setting various state variables (even those marked NO-USER-MODIFICATION, although these are typically intended for testing purposes only).  Users will be required to have the password-reset privilege in order to be able to use this extended operation, and access control will also come into play.

---
 opendj-sdk/opends/resource/bin/manage-account                                                           |   37 
 opendj-sdk/opends/resource/bin/manage-account.bat                                                       |   33 
 opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java                            |  552 ++++
 opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java     | 1703 +++++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/tools/ManageAccount.java                                 | 1688 +++++++++++++++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/PasswordPolicyTestCase.java |   80 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/ManageAccountTestCase.java |  871 +++++++
 opendj-sdk/opends/resource/config/config.ldif                                                           |    7 
 opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java                         |  383 +++
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java                                |    5 
 opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java                                |    9 
 opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java                               | 1184 ++++++++++
 12 files changed, 6,501 insertions(+), 51 deletions(-)

diff --git a/opendj-sdk/opends/resource/bin/manage-account b/opendj-sdk/opends/resource/bin/manage-account
new file mode 100755
index 0000000..f022410
--- /dev/null
+++ b/opendj-sdk/opends/resource/bin/manage-account
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License").  You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at
+# trunk/opends/resource/legal-notices/OpenDS.LICENSE
+# or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at
+# trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+# add the following below this CDDL HEADER, with the fields enclosed
+# by brackets "[]" replaced with your own identifying information:
+#      Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+
+
+# This script may be used to perform LDAP search operations.
+OPENDS_INVOKE_CLASS="org.opends.server.tools.PasswordPolicyState"
+export OPENDS_INVOKE_CLASS
+
+SCRIPT_NAME_ARG="-Dorg.opends.server.scriptName=password-policy-state"
+export SCRIPT_NAME_ARG
+
+SCRIPT_DIR=`dirname "${0}"`
+"${SCRIPT_DIR}/../lib/_client-script.sh" "${@}"
diff --git a/opendj-sdk/opends/resource/bin/manage-account.bat b/opendj-sdk/opends/resource/bin/manage-account.bat
new file mode 100644
index 0000000..c2bf1b0
--- /dev/null
+++ b/opendj-sdk/opends/resource/bin/manage-account.bat
@@ -0,0 +1,33 @@
+
+@echo off
+rem CDDL HEADER START
+rem
+rem The contents of this file are subject to the terms of the
+rem Common Development and Distribution License, Version 1.0 only
+rem (the "License").  You may not use this file except in compliance
+rem with the License.
+rem
+rem You can obtain a copy of the license at
+rem trunk/opends/resource/legal-notices/OpenDS.LICENSE
+rem or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+rem See the License for the specific language governing permissions
+rem and limitations under the License.
+rem
+rem When distributing Covered Code, include this CDDL HEADER in each
+rem file and include the License file at
+rem trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+rem add the following below this CDDL HEADER, with the fields enclosed
+rem by brackets "[]" replaced with your own identifying information:
+rem      Portions Copyright [yyyy] [name of copyright owner]
+rem
+rem CDDL HEADER END
+rem
+rem
+rem      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+
+setlocal
+
+set OPENDS_INVOKE_CLASS="org.opends.server.tools.PasswordPolicyState"
+set SCRIPT_NAME_ARG="-Dorg.opends.server.scriptName=password-policy-state"
+call "%~dP0\..\lib\_client-script.bat" %*
+
diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index 59a1c70..927836c 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/opends/resource/config/config.ldif
@@ -406,6 +406,13 @@
 ds-cfg-extended-operation-handler-enabled: true
 ds-cfg-identity-mapper-dn: cn=Exact Match,cn=Identity Mappers,cn=config
 
+dn: cn=Password Policy State,cn=Extended Operations,cn=config
+objectClass: top
+objectCLass: ds-cfg-extended-operation-handler
+cn: Password Policy State
+ds-cfg-extended-operation-handler-class: org.opends.server.extensions.PasswordPolicyStateExtendedOperation
+ds-cfg-extended-operation-handler-enabled: true
+
 dn: cn=StartTLS,cn=Extended Operations,cn=config
 objectClass: top
 objectClass: ds-cfg-extended-operation-handler
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
index 78ba9fe..e9661d4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -810,7 +810,7 @@
       // If the user must change their password before doing anything else, and
       // if the target of the modify operation isn't the user's own entry, then
       // reject the request.
-      if (clientConnection.mustChangePassword())
+      if ((! isInternalOperation()) && clientConnection.mustChangePassword())
       {
         DN authzDN = getAuthorizationDN();
         if ((authzDN != null) && (! authzDN.equals(entryDN)))
@@ -2450,7 +2450,8 @@
             break modifyProcessing;
           }
         }
-        else if(pwPolicyState.mustChangePassword())
+        else if ((! isInternalOperation()) &&
+                 pwPolicyState.mustChangePassword())
         {
           // The user will not be allowed to do anything else before
           // the password gets changed.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index e37f904..d3a43fe 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -31,6 +31,7 @@
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
@@ -147,8 +148,8 @@
   // The set of grace login times for this user.
   private List<Long> graceLoginTimes = null;
 
-  // The time that the user's password should expire (or did expire).
-  private long expirationTime = Long.MIN_VALUE;
+  // 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.
@@ -157,6 +158,9 @@
   // The time that the user last authenticated to the Directory Server.
   private long lastLoginTime = Long.MIN_VALUE;
 
+  // The time that the user's password should expire (or did expire).
+  private long passwordExpirationTime = Long.MIN_VALUE;
+
   // The last required change time with which the user complied.
   private long requiredChangeTime = Long.MIN_VALUE;
 
@@ -187,13 +191,42 @@
                              boolean debug)
        throws DirectoryException
   {
+    this(userEntry, updateEntry, TimeThread.getTime(), debug);
+  }
+
+
+
+  /**
+   * 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
+   * should be used.
+   *
+   * @param  userEntry    The entry with the user account.
+   * @param  updateEntry  Indicates whether changes should update the provided
+   *                      user entry directly or whether they should be
+   *                      collected as a set of modifications.
+   * @param  currentTime  The time to use as the current time for all
+   *                      time-related determinations.
+   * @param  debug        Indicates whether to enable debugging for the
+   *                      operations performed.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to
+   *                              determine the password policy for the user or
+   *                              perform any other state initialization.
+   */
+  public PasswordPolicyState(Entry userEntry, boolean updateEntry,
+                             long currentTime, boolean debug)
+       throws DirectoryException
+  {
     this.userEntry   = userEntry;
     this.updateEntry = updateEntry;
     this.debug       = debug;
+    this.currentTime = currentTime;
 
     userDNString     = userEntry.getDN().toString();
     passwordPolicy   = getPasswordPolicyInternal(this.userEntry, this.debug);
-    currentTime      = TimeThread.getTime();
 
     // Get the password changed time for the user.
     AttributeType type
@@ -636,6 +669,30 @@
 
 
   /**
+   * Retrieves the time that the password was last changed.
+   *
+   * @return  The time that the password was last changed.
+   */
+  public long getPasswordChangedTime()
+  {
+    return passwordChangedTime;
+  }
+
+
+
+  /**
+   * Retrieves the time that this password policy state object was created.
+   *
+   * @return  The time that this password policy state object was created.
+   */
+  public long getCurrentTime()
+  {
+    return currentTime;
+  }
+
+
+
+  /**
    * Retrieves the set of values for the password attribute from the user entry.
    *
    * @return  The set of values for the password attribute from the user entry.
@@ -664,6 +721,20 @@
    */
   public void setPasswordChangedTime()
   {
+    setPasswordChangedTime(currentTime);
+  }
+
+
+
+  /**
+   * 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.
+   *
+   * @param  passwordChangedTime  The time to use
+   */
+  public void setPasswordChangedTime(long passwordChangedTime)
+  {
     if (debug)
     {
       if (debugEnabled())
@@ -675,9 +746,9 @@
 
     // passwordChangedTime is computed in the constructor from values in the
     // entry.
-    if (passwordChangedTime != currentTime)
+    if (this.passwordChangedTime != passwordChangedTime)
     {
-      passwordChangedTime = currentTime;
+      this.passwordChangedTime = passwordChangedTime;
 
       AttributeType type =
            DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
@@ -693,11 +764,11 @@
       values.add(new AttributeValue(type, timeValue));
 
       Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_CHANGED_TIME, values);
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
 
       if (updateEntry)
       {
+        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+        attrList.add(a);
         userEntry.putAttribute(type, attrList);
       }
       else
@@ -710,6 +781,57 @@
 
 
   /**
+   * 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 (debug)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("Clearing password changed time for user %s",
+                         userDNString);
+      }
+    }
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC,
+                                       true);
+    if (updateEntry)
+    {
+      userEntry.removeAttribute(type);
+    }
+    else
+    {
+      Attribute a = new Attribute(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);
+    try
+    {
+      passwordChangedTime = getGeneralizedTime(createTimeType);
+      if (passwordChangedTime <= 0)
+      {
+        passwordChangedTime = 0;
+      }
+    }
+    catch (Exception e)
+    {
+      passwordChangedTime = 0;
+    }
+  }
+
+
+
+
+  /**
    * Indicates whether the user account has been administratively disabled.
    *
    * @return  <CODE>true</CODE> if the user account has been administratively
@@ -876,10 +998,9 @@
          DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                           true);
 
-    long expirationTime;
     try
     {
-      expirationTime = getGeneralizedTime(type);
+      accountExpirationTime = getGeneralizedTime(type);
      }
     catch (Exception e)
     {
@@ -900,7 +1021,7 @@
       return true;
     }
 
-    if (expirationTime > currentTime)
+    if (accountExpirationTime > currentTime)
     {
       // The user does have an expiration time, but it hasn't arrived yet.
       isAccountExpired = ConditionResult.FALSE;
@@ -913,7 +1034,7 @@
         }
       }
     }
-    else if (expirationTime >= 0)
+    else if (accountExpirationTime >= 0)
     {
       // The user does have an expiration time, and it is in the past.
       isAccountExpired = ConditionResult.TRUE;
@@ -948,6 +1069,109 @@
 
 
   /**
+   * 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.
+   */
+  public long getAccountExpirationTime()
+  {
+    if (accountExpirationTime == Long.MIN_VALUE)
+    {
+      isAccountExpired();
+    }
+
+    return accountExpirationTime;
+  }
+
+
+
+  /**
+   * Sets the user's account expiration time to the specified value.
+   *
+   * @param  accountExpirationTime  The time that the user's account should
+   *                                expire.
+   */
+  public void setAccountExpirationTime(long accountExpirationTime)
+  {
+    if (accountExpirationTime < 0)
+    {
+      clearAccountExpirationTime();
+    }
+    else
+    {
+      String timeStr = GeneralizedTimeSyntax.format(accountExpirationTime);
+
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugInfo("Setting account expiration time for user %s to %s",
+                    userDNString, timeStr);
+        }
+      }
+
+      this.accountExpirationTime = accountExpirationTime;
+      AttributeType type =
+           DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
+                                            true);
+
+      LinkedHashSet<AttributeValue> values =
+           new LinkedHashSet<AttributeValue>(1);
+      values.add(new AttributeValue(type, timeStr));
+
+      Attribute a = new Attribute(type, OP_ATTR_ACCOUNT_EXPIRATION_TIME,
+                                  values);
+
+      if (updateEntry)
+      {
+        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+        attrList.add(a);
+        userEntry.putAttribute(type, attrList);
+      }
+      else
+      {
+        modifications.add(new Modification(ModificationType.REPLACE, a, true));
+      }
+    }
+  }
+
+
+
+  /**
+   * Clears the user's account expiration time.
+   */
+  public void clearAccountExpirationTime()
+  {
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("Clearing account expiration time for user %s",
+                  userDNString);
+      }
+    }
+
+    accountExpirationTime = -1;
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
+                                          true);
+
+    if (updateEntry)
+    {
+      userEntry.removeAttribute(type);
+    }
+    else
+    {
+      modifications.add(new Modification(ModificationType.REPLACE,
+                                         new Attribute(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
@@ -957,7 +1181,7 @@
    *          which will be an empty list in the case of no valid (unexpired)
    *          times in the entry.
    */
-  private List<Long> getAuthFailureTimes()
+  public List<Long> getAuthFailureTimes()
   {
     if (authFailureTimes != null)
     {
@@ -1185,7 +1409,75 @@
 
     // Now check to see if there have been sufficient failures to lock the
     // account.
-    if (passwordPolicy.getLockoutFailureCount() <= failureTimes.size())
+    int lockoutCount = passwordPolicy.getLockoutFailureCount();
+    if ((lockoutCount > 0) && (lockoutCount <= authFailureTimes.size()))
+    {
+      setFailureLockedTime(highestFailureTime);
+      if (debug)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugInfo("Locking user account %s due to too many failures.",
+                    userDNString);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * 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.
+   */
+  public void setAuthFailureTimes(List<Long> authFailureTimes)
+  {
+    if ((authFailureTimes == null) || authFailureTimes.isEmpty())
+    {
+      clearAuthFailureTimes();
+      clearFailureLockedTime();
+      return;
+    }
+
+    long highestFailureTime = -1;
+    for (Long l : authFailureTimes)
+    {
+      highestFailureTime = Math.max(l, highestFailureTime);
+    }
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC,
+                                          true);
+
+    LinkedHashSet<AttributeValue> values =
+           new LinkedHashSet<AttributeValue>(authFailureTimes.size());
+    for (Long l : authFailureTimes)
+    {
+      values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l)));
+    }
+
+    Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME, values);
+
+    if (updateEntry)
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
+      userEntry.putAttribute(type, attrList);
+    }
+    else
+    {
+      modifications.add(new Modification(ModificationType.REPLACE, a, true));
+    }
+
+    // Now check to see if there have been sufficient failures to lock the
+    // account.
+    int lockoutCount = passwordPolicy.getLockoutFailureCount();
+    if ((lockoutCount > 0) && (lockoutCount <= authFailureTimes.size()))
     {
       setFailureLockedTime(highestFailureTime);
       if (debug)
@@ -1686,6 +1978,20 @@
    */
   public void setLastLoginTime()
   {
+    setLastLoginTime(currentTime);
+  }
+
+
+
+  /**
+   * 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.
+   */
+  public void setLastLoginTime(long lastLoginTime)
+  {
     AttributeType type = passwordPolicy.getLastLoginTimeAttribute();
     String format = passwordPolicy.getLastLoginTimeFormat();
 
@@ -1698,7 +2004,8 @@
     try
     {
       SimpleDateFormat dateFormat = new SimpleDateFormat(format);
-      timestamp = dateFormat.format(TimeThread.getDate());
+      timestamp = dateFormat.format(new Date(lastLoginTime));
+      this.lastLoginTime = dateFormat.parse(timestamp).getTime();
     }
     catch (Exception e)
     {
@@ -1764,6 +2071,38 @@
 
 
   /**
+   * Clears the last login time from the user's entry.  This should generally be
+   * used only for testing purposes.
+   */
+  public void clearLastLoginTime()
+  {
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("Clearing last login time for user %s", userDNString);
+      }
+    }
+
+    lastLoginTime = -1;
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_LAST_LOGIN_TIME, true);
+
+    if (updateEntry)
+    {
+      userEntry.removeAttribute(type);
+    }
+    else
+    {
+      modifications.add(new Modification(ModificationType.REPLACE,
+                                         new Attribute(type), true));
+    }
+  }
+
+
+
+  /**
    * Indicates whether the user's account is currently locked because it has
    * been idle for too long.
    *
@@ -2108,9 +2447,9 @@
    */
   public long getPasswordExpirationTime()
   {
-    if (expirationTime == Long.MIN_VALUE)
+    if (passwordExpirationTime == Long.MIN_VALUE)
     {
-      expirationTime = Long.MAX_VALUE;
+      passwordExpirationTime = Long.MAX_VALUE;
 
       boolean checkWarning = false;
 
@@ -2118,9 +2457,9 @@
       if (maxAge > 0)
       {
         long expTime = passwordChangedTime + (1000L*maxAge);
-        if (expTime < expirationTime)
+        if (expTime < passwordExpirationTime)
         {
-          expirationTime = expTime;
+          passwordExpirationTime = expTime;
           checkWarning   = true;
         }
       }
@@ -2129,9 +2468,9 @@
       if (mustChangePassword() && (maxResetAge > 0))
       {
         long expTime = passwordChangedTime + (1000L*maxResetAge);
-        if (expTime < expirationTime)
+        if (expTime < passwordExpirationTime)
         {
-          expirationTime = expTime;
+          passwordExpirationTime = expTime;
           checkWarning   = false;
         }
       }
@@ -2141,20 +2480,20 @@
       {
         long reqChangeTime = getRequiredChangeTime();
         if ((reqChangeTime != mustChangeTime) &&
-            (mustChangeTime < expirationTime))
+            (mustChangeTime < passwordExpirationTime))
         {
-          expirationTime = mustChangeTime;
+          passwordExpirationTime = mustChangeTime;
           checkWarning   = true;
         }
       }
 
-      if (expirationTime == Long.MAX_VALUE)
+      if (passwordExpirationTime == Long.MAX_VALUE)
       {
-        expirationTime    = -1;
-        shouldWarn        = ConditionResult.FALSE;
-        isFirstWarning    = ConditionResult.FALSE;
-        isPasswordExpired = ConditionResult.FALSE;
-        mayUseGraceLogin  = ConditionResult.TRUE;
+        passwordExpirationTime = -1;
+        shouldWarn             = ConditionResult.FALSE;
+        isFirstWarning         = ConditionResult.FALSE;
+        isPasswordExpired      = ConditionResult.FALSE;
+        mayUseGraceLogin       = ConditionResult.TRUE;
       }
       else if (checkWarning)
       {
@@ -2163,7 +2502,8 @@
         int warningInterval = passwordPolicy.getWarningInterval();
         if (warningInterval > 0)
         {
-          long shouldWarnTime = expirationTime - (warningInterval*1000L);
+          long shouldWarnTime =
+                    passwordExpirationTime - (warningInterval*1000L);
           if (shouldWarnTime > currentTime)
           {
             // The warning time is in the future, so we know the password isn't
@@ -2178,7 +2518,7 @@
             // expired.
             long warnedTime = getWarnedTime();
 
-            if (expirationTime > currentTime)
+            if (passwordExpirationTime > currentTime)
             {
               // The password is not expired but we should warn the user.
               shouldWarn        = ConditionResult.TRUE;
@@ -2191,7 +2531,8 @@
 
                 if (! passwordPolicy.expirePasswordsWithoutWarning())
                 {
-                  expirationTime = currentTime + (warningInterval*1000L);
+                  passwordExpirationTime =
+                       currentTime + (warningInterval*1000L);
                 }
               }
               else
@@ -2200,7 +2541,7 @@
 
                 if (! passwordPolicy.expirePasswordsWithoutWarning())
                 {
-                  expirationTime = warnedTime + (warningInterval*1000L);
+                  passwordExpirationTime = warnedTime + (warningInterval*1000L);
                 }
               }
             }
@@ -2216,8 +2557,8 @@
               }
               else if (warnedTime > 0)
               {
-                expirationTime = warnedTime + (warningInterval*1000L);
-                if (expirationTime > currentTime)
+                passwordExpirationTime = warnedTime + (warningInterval*1000L);
+                if (passwordExpirationTime > currentTime)
                 {
                   shouldWarn        = ConditionResult.TRUE;
                   isFirstWarning    = ConditionResult.FALSE;
@@ -2232,10 +2573,10 @@
               }
               else
               {
-                shouldWarn        = ConditionResult.TRUE;
-                isFirstWarning    = ConditionResult.TRUE;
-                isPasswordExpired = ConditionResult.FALSE;
-                expirationTime    = currentTime + (warningInterval*1000L);
+                shouldWarn             = ConditionResult.TRUE;
+                isFirstWarning         = ConditionResult.TRUE;
+                isPasswordExpired      = ConditionResult.FALSE;
+                passwordExpirationTime = currentTime + (warningInterval*1000L);
               }
             }
           }
@@ -2247,7 +2588,7 @@
           shouldWarn     = ConditionResult.FALSE;
           isFirstWarning = ConditionResult.FALSE;
 
-          if (currentTime > expirationTime)
+          if (currentTime > passwordExpirationTime)
           {
             isPasswordExpired = ConditionResult.TRUE;
           }
@@ -2263,7 +2604,7 @@
         shouldWarn       = ConditionResult.FALSE;
         isFirstWarning   = ConditionResult.FALSE;
 
-        if (expirationTime < currentTime)
+        if (passwordExpirationTime < currentTime)
         {
           isPasswordExpired = ConditionResult.TRUE;
         }
@@ -2279,11 +2620,11 @@
       if (debugEnabled())
       {
         TRACER.debugInfo("Returning password expiration time of %d for user " +
-            "%s.", expirationTime, userDNString);
+            "%s.", passwordExpirationTime, userDNString);
       }
     }
 
-    return expirationTime;
+    return passwordExpirationTime;
   }
 
 
@@ -2538,6 +2879,24 @@
    */
   public void setRequiredChangeTime()
   {
+    long requiredChangeByTimePolicy = passwordPolicy.getRequireChangeByTime();
+    if (requiredChangeByTimePolicy > 0)
+    {
+      setRequiredChangeTime(requiredChangeByTimePolicy);
+    }
+  }
+
+
+
+  /**
+   * 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.
+   */
+  public void setRequiredChangeTime(long requiredChangeTime)
+  {
     if (debug)
     {
       if (debugEnabled())
@@ -2547,16 +2906,14 @@
       }
     }
 
-    long requiredChangeByTimePolicy = passwordPolicy.getRequireChangeByTime();
-    if (getRequiredChangeTime() != requiredChangeByTimePolicy)
+    if (getRequiredChangeTime() != requiredChangeTime)
     {
       AttributeType type = DirectoryServer.getAttributeType(
                                OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
 
       LinkedHashSet<AttributeValue> values =
            new LinkedHashSet<AttributeValue>(1);
-      String timeValue =
-           GeneralizedTimeSyntax.format(requiredChangeByTimePolicy);
+      String timeValue = GeneralizedTimeSyntax.format(requiredChangeTime);
       values.add(new AttributeValue(type, timeValue));
 
       Attribute a = new Attribute(type,
@@ -2579,6 +2936,36 @@
 
 
   /**
+   * 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 (debug)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("Clearing required change time for user %s",
+                         userDNString);
+      }
+    }
+
+    AttributeType type = DirectoryServer.getAttributeType(
+                             OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
+    if (updateEntry)
+    {
+      userEntry.removeAttribute(type);
+    }
+    else
+    {
+      modifications.add(new Modification(ModificationType.REPLACE,
+                                         new Attribute(type), true));
+    }
+  }
+
+
+
+  /**
    * Retrieves the time that the user was first warned about an upcoming
    * expiration.
    *
@@ -2632,22 +3019,37 @@
    */
   public void setWarnedTime()
   {
+    setWarnedTime(currentTime);
+  }
+
+
+
+  /**
+   * 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.
+   */
+  public void setWarnedTime(long warnedTime)
+  {
     long warnTime = getWarnedTime();
-    if (warnTime == currentTime)
+    if (warnTime == warnedTime)
     {
       if (debug)
       {
         if (debugEnabled())
         {
           TRACER.debugInfo("Not updating warned time for user %s because " +
-              "the warned time is the same as the current time.", userDNString);
+              "the warned time is the same as the specified time.",
+              userDNString);
         }
       }
 
       return;
     }
 
-    warnedTime = currentTime;
+    this.warnedTime = warnedTime;
 
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
@@ -2879,6 +3281,58 @@
 
 
   /**
+   * 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())
+    {
+      clearGraceLoginTimes();
+      return;
+    }
+
+
+    if (debug)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugInfo("Updating grace login times for user %s",
+                         userDNString);
+      }
+    }
+
+
+    AttributeType type =
+         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC,
+                                          true);
+    LinkedHashSet<AttributeValue> values =
+         new LinkedHashSet<AttributeValue>(graceLoginTimes.size());
+    for (Long l : graceLoginTimes)
+    {
+      values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l)));
+    }
+    Attribute a =
+         new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME, values);
+
+    if (updateEntry)
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(a);
+
+      userEntry.putAttribute(type, attrList);
+    }
+    else
+    {
+      modifications.add(new Modification(ModificationType.REPLACE, a, true));
+    }
+  }
+
+
+
+  /**
    * Updates the user entry to remove any record of previous grace logins.
    */
   public void clearGraceLoginTimes()
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java
new file mode 100644
index 0000000..a8c6deb
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java
@@ -0,0 +1,1703 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.extensions;
+
+
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.opends.server.admin.std.server.ExtendedOperationHandlerCfg;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.ExtendedOperationHandler;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ExtendedOperation;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.PasswordPolicy;
+import org.opends.server.core.PasswordPolicyState;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.protocols.asn1.ASN1Element;
+import org.opends.server.protocols.asn1.ASN1Enumerated;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.asn1.ASN1Sequence;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.internal.InternalSearchOperation;
+import org.opends.server.schema.GeneralizedTimeSyntax;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DereferencePolicy;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.Modification;
+import org.opends.server.types.Privilege;
+import org.opends.server.types.ResultCode;
+import org.opends.server.types.SearchFilter;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchScope;
+
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.messages.ExtensionsMessages.*;
+import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
+/**
+ * This class implements an LDAP extended operation that can be used to query
+ * and update elements of the Directory Server password policy state for a given
+ * user.  The ASN.1 definition for the value of the extended request is:
+ * <BR>
+ * <PRE>
+ * PasswordPolicyStateValue ::= SEQUENCE {
+ *      targetUser     LDAPDN
+ *      operations     SEQUENCE OF PasswordPolicyStateOperation OPTIONAL }
+ *
+ * PasswordPolicyStateOperation ::= SEQUENCE {
+ *      opType       ENUMERATED {
+ *           getPasswordPolicyDN                          (0),
+ *           getAccountDisabledState                      (1),
+ *           setAccountDisabledState                      (2),
+ *           clearAccountDisabledState                    (3),
+ *           getAccountExpirationTime                     (4),
+ *           setAccountExpirationTime                     (5),
+ *           clearAccountExpirationTime                   (6),
+ *           getSecondsUntilAccountExpiration             (7),
+ *           getPasswordChangedTime                       (8),
+ *           setPasswordChangedTime                       (9),
+ *           clearPasswordChangedTime                     (10),
+ *           getPasswordExpirationWarnedTime              (11),
+ *           setPasswordExpirationWarnedTime              (12),
+ *           clearPasswordExpirationWarnedTime            (13),
+ *           getSecondsUntilPasswordExpiration            (14),
+ *           getSecondsUntilPasswordExpirationWarning     (15),
+ *           getAuthenticationFailureTimes                (16),
+ *           addAuthenticationFailureTime                 (17),
+ *           setAuthenticationFailureTimes                (18),
+ *           clearAuthenticationFailureTimes              (19),
+ *           getSecondsUntilAuthenticationFailureUnlock   (20),
+ *           getRemainingAuthenticationFailureCount       (21),
+ *           getLastLoginTime                             (22),
+ *           setLastLoginTime                             (23),
+ *           clearLastLoginTime                           (24),
+ *           getSecondsUntilIdleLockout                   (25),
+ *           getPasswordResetState                        (26),
+ *           setPasswordResetState                        (27),
+ *           clearPasswordResetState                      (28),
+ *           getSecondsUntilPasswordResetLockout          (29),
+ *           getGraceLoginUseTimes                        (30),
+ *           addGraceLoginUseTime                         (31),
+ *           setGraceLoginUseTimes                        (32),
+ *           clearGraceLoginUseTimes                      (33),
+ *           getRemainingGraceLoginCount                  (34),
+ *           getPasswordChangedByRequiredTime             (35),
+ *           setPasswordChangedByRequiredTime             (36),
+ *           clearPasswordChangedByRequiredTime           (37),
+ *           getSecondsUntilRequiredChangeTime            (38),
+ *           ... },
+ *      opValues     SEQUENCE OF OCTET STRING OPTIONAL }
+ * </PRE>
+ * <BR>
+ * Both the request and response values use the same encoded form, and they both
+ * use the same OID of "1.3.6.1.4.1.26027.1.6.1".  The response value will only
+ * include get* elements.  If the request did not include any operations, then
+ * the response will include all get* elements; otherwise, the response will
+ * only include the get* elements that correspond to the state fields referenced
+ * in the request (regardless of whether that operation was included in a get*,
+ * set*, add*, remove*, or clear* operation).
+ */
+public class PasswordPolicyStateExtendedOperation
+       extends ExtendedOperationHandler<ExtendedOperationHandlerCfg>
+{
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+
+
+  /**
+   * The enumerated value for the getPasswordPolicyDN operation.
+   */
+  public static final int OP_GET_PASSWORD_POLICY_DN = 0;
+
+
+
+  /**
+   * The enumerated value for the getAccountDisabledState operation.
+   */
+  public static final int OP_GET_ACCOUNT_DISABLED_STATE = 1;
+
+
+
+  /**
+   * The enumerated value for the setAccountDisabledState operation.
+   */
+  public static final int OP_SET_ACCOUNT_DISABLED_STATE = 2;
+
+
+
+  /**
+   * The enumerated value for the clearAccountDisabledState operation.
+   */
+  public static final int OP_CLEAR_ACCOUNT_DISABLED_STATE = 3;
+
+
+
+  /**
+   * The enumerated value for the getAccountExpirationTime operation.
+   */
+  public static final int OP_GET_ACCOUNT_EXPIRATION_TIME = 4;
+
+
+
+  /**
+   * The enumerated value for the setAccountExpirationTime operation.
+   */
+  public static final int OP_SET_ACCOUNT_EXPIRATION_TIME = 5;
+
+
+
+  /**
+   * The enumerated value for the clearAccountExpirationTime operation.
+   */
+  public static final int OP_CLEAR_ACCOUNT_EXPIRATION_TIME = 6;
+
+
+
+  /**
+   * The enumerated value for the getSecondsUntilAccountExpiration operation.
+   */
+  public static final int OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION = 7;
+
+
+
+  /**
+   * The enumerated value for the getPasswordChangedTime operation.
+   */
+  public static final int OP_GET_PASSWORD_CHANGED_TIME = 8;
+
+
+
+  /**
+   * The enumerated value for the setPasswordChangedTime operation.
+   */
+  public static final int OP_SET_PASSWORD_CHANGED_TIME = 9;
+
+
+
+  /**
+   * The enumerated value for the clearPasswordChangedTime operation.
+   */
+  public static final int OP_CLEAR_PASSWORD_CHANGED_TIME = 10;
+
+
+
+  /**
+   * The enumerated value for the getPasswordExpirationWarnedTime operation.
+   */
+  public static final int OP_GET_PASSWORD_EXPIRATION_WARNED_TIME = 11;
+
+
+
+  /**
+   * The enumerated value for the setPasswordExpirationWarnedTime operation.
+   */
+  public static final int OP_SET_PASSWORD_EXPIRATION_WARNED_TIME = 12;
+
+
+
+  /**
+   * The enumerated value for the clearPasswordExpirationWarnedTime operation.
+   */
+  public static final int OP_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME = 13;
+
+
+
+  /**
+   * The enumerated value for the getSecondsUntilPasswordExpiration operation.
+   */
+  public static final int OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION = 14;
+
+
+
+  /**
+   * The enumerated value for the getSecondsUntilPasswordExpirationWarning
+   * operation.
+   */
+  public static final int OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING = 15;
+
+
+
+  /**
+   * The enumerated value for the getAuthenticationFailureTimes operation.
+   */
+  public static final int OP_GET_AUTHENTICATION_FAILURE_TIMES = 16;
+
+
+
+  /**
+   * The enumerated value for the addAuthenticationFailureTime operation.
+   */
+  public static final int OP_ADD_AUTHENTICATION_FAILURE_TIME = 17;
+
+
+
+  /**
+   * The enumerated value for the setAuthenticationFailureTimes operation.
+   */
+  public static final int OP_SET_AUTHENTICATION_FAILURE_TIMES = 18;
+
+
+
+  /**
+   * The enumerated value for the clearAuthenticationFailureTimes operation.
+   */
+  public static final int OP_CLEAR_AUTHENTICATION_FAILURE_TIMES = 19;
+
+
+
+  /**
+   * The enumerated value for the getSecondsUntilAuthenticationFailureUnlock
+   * operation.
+   */
+  public static final int OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK =
+       20;
+
+
+
+  /**
+   * The enumerated value for the getRemainingAuthenticationFailureCount
+   * operation.
+   */
+  public static final int OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT = 21;
+
+
+
+  /**
+   * The enumerated value for the getLastLoginTime operation.
+   */
+  public static final int OP_GET_LAST_LOGIN_TIME = 22;
+
+
+
+  /**
+   * The enumerated value for the setLastLoginTime operation.
+   */
+  public static final int OP_SET_LAST_LOGIN_TIME = 23;
+
+
+
+  /**
+   * The enumerated value for the clearLastLoginTime operation.
+   */
+  public static final int OP_CLEAR_LAST_LOGIN_TIME = 24;
+
+
+
+  /**
+   * The enumerated value for the getSecondsUntilIdleLockout operation.
+   */
+  public static final int OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT = 25;
+
+
+
+  /**
+   * The enumerated value for the getPasswordResetState operation.
+   */
+  public static final int OP_GET_PASSWORD_RESET_STATE = 26;
+
+
+
+  /**
+   * The enumerated value for the setPasswordResetState operation.
+   */
+  public static final int OP_SET_PASSWORD_RESET_STATE = 27;
+
+
+
+  /**
+   * The enumerated value for the clearPasswordResetState operation.
+   */
+  public static final int OP_CLEAR_PASSWORD_RESET_STATE = 28;
+
+
+
+  /**
+   * The enumerated value for the getSecondsUntilPasswordResetLockout operation.
+   */
+  public static final int OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT = 29;
+
+
+
+  /**
+   * The enumerated value for the getGraceLoginUseTimes operation.
+   */
+  public static final int OP_GET_GRACE_LOGIN_USE_TIMES = 30;
+
+
+
+  /**
+   * The enumerated value for the addGraceLoginUseTime operation.
+   */
+  public static final int OP_ADD_GRACE_LOGIN_USE_TIME = 31;
+
+
+
+  /**
+   * The enumerated value for the setGraceLoginUseTimes operation.
+   */
+  public static final int OP_SET_GRACE_LOGIN_USE_TIMES = 32;
+
+
+
+  /**
+   * The enumerated value for the clearGraceLoginUseTimes operation.
+   */
+  public static final int OP_CLEAR_GRACE_LOGIN_USE_TIMES = 33;
+
+
+
+  /**
+   * The enumerated value for the getRemainingGraceLoginCount operation.
+   */
+  public static final int OP_GET_REMAINING_GRACE_LOGIN_COUNT = 34;
+
+
+
+  /**
+   * The enumerated value for the getPasswordChangedByRequiredTime operation.
+   */
+  public static final int OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME = 35;
+
+
+
+  /**
+   * The enumerated value for the setPasswordChangedByRequiredTime operation.
+   */
+  public static final int OP_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME = 36;
+
+
+
+  /**
+   * The enumerated value for the clearPasswordChangedByRequiredTime operation.
+   */
+  public static final int OP_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME = 37;
+
+
+
+  /**
+   * The enumerated value for the getSecondsUntilRequiredChangeTime operation.
+   */
+  public static final int OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME = 38;
+
+
+
+  // The set of attributes to request when retrieving a user's entry.
+  private LinkedHashSet<String> requestAttributes;
+
+  // The search filter that will be used to retrieve user entries.
+  private SearchFilter userFilter;
+
+
+
+  /**
+   * Create an instance of this password policy state extended operation.  All
+   * initialization should be performed in the
+   * {@code initializeExtendedOperationHandler} method.
+   */
+  public PasswordPolicyStateExtendedOperation()
+  {
+    super();
+  }
+
+
+  /**
+   * Initializes this extended operation handler based on the information in the
+   * provided configuration entry.  It should also register itself with the
+   * Directory Server for the particular kinds of extended operations that it
+   * will process.
+   *
+   * @param  config       The configuration that contains the information
+   *                      to use to initialize this extended operation handler.
+   *
+   * @throws  ConfigException  If an unrecoverable problem arises in the
+   *                           process of performing the initialization.
+   *
+   * @throws  InitializationException  If a problem occurs during initialization
+   *                                   that is not related to the server
+   *                                   configuration.
+   */
+  public void initializeExtendedOperationHandler(
+       ExtendedOperationHandlerCfg config)
+       throws ConfigException, InitializationException
+  {
+    // Construct the filter that will be used to retrieve user entries.
+    try
+    {
+      userFilter = SearchFilter.createFilterFromString("(objectClass=*)");
+    }
+    catch (Exception e)
+    {
+      // This should never happen.
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+
+
+    // Construct the set of request attributes.
+    requestAttributes = new LinkedHashSet<String>(2);
+    requestAttributes.add("*");
+    requestAttributes.add("+");
+
+
+    DirectoryServer.registerSupportedExtension(OID_PASSWORD_POLICY_STATE_EXTOP,
+                                               this);
+  }
+
+
+
+  /**
+   * Performs any finalization that may be necessary for this extended
+   * operation handler.  By default, no finalization is performed.
+   */
+  public void finalizeExtendedOperationHandler()
+  {
+    DirectoryServer.deregisterSupportedExtension(OID_CANCEL_REQUEST);
+  }
+
+
+
+  /**
+   * Processes the provided extended operation.
+   *
+   * @param  operation  The extended operation to be processed.
+   */
+  public void processExtendedOperation(ExtendedOperation operation)
+  {
+    operation.setResultCode(ResultCode.UNDEFINED);
+
+
+    // The user must have the password-reset privilege in order to be able to do
+    // anything with this extended operation.
+    ClientConnection clientConnection = operation.getClientConnection();
+    if (! clientConnection.hasPrivilege(Privilege.PASSWORD_RESET, operation))
+    {
+      String message = getMessage(MSGID_PWPSTATE_EXTOP_NO_PRIVILEGE);
+      operation.appendErrorMessage(message);
+      operation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+      return;
+    }
+
+
+    // There must be a request value, and it must be a sequence.  Decode it
+    // into its components.
+    ASN1OctetString requestValue = operation.getRequestValue();
+    if (requestValue == null)
+    {
+      String message = getMessage(MSGID_PWPSTATE_EXTOP_NO_REQUEST_VALUE);
+      operation.appendErrorMessage(message);
+      operation.setResultCode(ResultCode.PROTOCOL_ERROR);
+      return;
+    }
+
+    ASN1OctetString dnString;
+    ASN1Sequence    opSequence;
+    try
+    {
+      ASN1Sequence valueSequence =
+           ASN1Sequence.decodeAsSequence(requestValue.value());
+      List<ASN1Element> elements = valueSequence.elements();
+      dnString   = elements.get(0).decodeAsOctetString();
+
+      if (elements.size() == 2)
+      {
+        opSequence = elements.get(1).decodeAsSequence();
+      }
+      else
+      {
+        opSequence = null;
+      }
+    }
+    catch (Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      String message = getMessage(MSGID_PWPSTATE_EXTOP_DECODE_FAILURE,
+                                  getExceptionMessage(e));
+      operation.appendErrorMessage(message);
+      operation.setResultCode(ResultCode.PROTOCOL_ERROR);
+      return;
+    }
+
+
+    // Decode the DN and get the corresponding user entry.
+    DN targetDN;
+    try
+    {
+      targetDN = DN.decode(dnString);
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      operation.setResponseData(de);
+      return;
+    }
+
+    DN rootDN = DirectoryServer.getActualRootBindDN(targetDN);
+    if (rootDN != null)
+    {
+      targetDN = rootDN;
+    }
+
+    Entry userEntry;
+    InternalClientConnection conn =
+         new InternalClientConnection(clientConnection.getAuthenticationInfo());
+    InternalSearchOperation internalSearch =
+         conn.processSearch(targetDN, SearchScope.BASE_OBJECT,
+                            DereferencePolicy.NEVER_DEREF_ALIASES, 1, 0,
+                            false, userFilter, requestAttributes, null);
+    if (internalSearch.getResultCode() != ResultCode.SUCCESS)
+    {
+      operation.setResultCode(internalSearch.getResultCode());
+      operation.setErrorMessage(internalSearch.getErrorMessage());
+      operation.setMatchedDN(internalSearch.getMatchedDN());
+      operation.setReferralURLs(internalSearch.getReferralURLs());
+      return;
+    }
+
+    List<SearchResultEntry> matchingEntries = internalSearch.getSearchEntries();
+    if (matchingEntries.isEmpty())
+    {
+      operation.setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+      return;
+    }
+    else if (matchingEntries.size() > 1)
+    {
+      String message = getMessage(MSGID_PWPSTATE_EXTOP_MULTIPLE_ENTRIES);
+      operation.appendErrorMessage(message);
+      operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+      return;
+    }
+    else
+    {
+      userEntry = matchingEntries.get(0);
+    }
+
+
+    // Get the password policy state for the user entry.
+    PasswordPolicyState pwpState;
+    PasswordPolicy      policy;
+    try
+    {
+      pwpState = new PasswordPolicyState(userEntry, false, false);
+      policy   = pwpState.getPolicy();
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      operation.setResponseData(de);
+      return;
+    }
+
+
+    // Create a hash set that will be used to hold the types of the return
+    // types that should be included in the response.
+    boolean returnAll;
+    LinkedHashSet<Integer> returnTypes = new LinkedHashSet<Integer>();
+    if ((opSequence == null) || opSequence.elements().isEmpty())
+    {
+      returnAll = true;
+    }
+    else
+    {
+      returnAll = false;
+      for (ASN1Element element : opSequence.elements())
+      {
+        int opType;
+        ArrayList<String> opValues;
+        try
+        {
+          List<ASN1Element> opElements = element.decodeAsSequence().elements();
+          opType = opElements.get(0).decodeAsEnumerated().intValue();
+
+          if (opElements.size() == 1)
+          {
+            opValues = null;
+          }
+          else
+          {
+            List<ASN1Element> valueElements =
+                 opElements.get(1).decodeAsSequence().elements();
+            if (valueElements.isEmpty())
+            {
+              opValues = null;
+            }
+            else
+            {
+              opValues = new ArrayList<String>(valueElements.size());
+              for (ASN1Element e : valueElements)
+              {
+                opValues.add(e.decodeAsOctetString().stringValue());
+              }
+            }
+          }
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          String message = getMessage(MSGID_PWPSTATE_EXTOP_INVALID_OP_ENCODING);
+          operation.appendErrorMessage(message);
+          operation.setResultCode(ResultCode.PROTOCOL_ERROR);
+          return;
+        }
+
+        switch (opType)
+        {
+          case OP_GET_PASSWORD_POLICY_DN:
+            returnTypes.add(OP_GET_PASSWORD_POLICY_DN);
+            break;
+
+          case OP_GET_ACCOUNT_DISABLED_STATE:
+            returnTypes.add(OP_GET_ACCOUNT_DISABLED_STATE);
+            break;
+
+          case OP_SET_ACCOUNT_DISABLED_STATE:
+            if (opValues == null)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_NO_DISABLED_VALUE;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_DISABLED_VALUE_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              String value = opValues.get(0);
+              if (value.equalsIgnoreCase("true"))
+              {
+                pwpState.setDisabled(true);
+              }
+              else if (value.equalsIgnoreCase("false"))
+              {
+                pwpState.setDisabled(false);
+              }
+              else
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_DISABLED_VALUE;
+                operation.appendErrorMessage(getMessage(msgID));
+                operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_ACCOUNT_DISABLED_STATE);
+            break;
+
+          case OP_CLEAR_ACCOUNT_DISABLED_STATE:
+            pwpState.setDisabled(false);
+            returnTypes.add(OP_GET_ACCOUNT_DISABLED_STATE);
+            break;
+
+          case OP_GET_ACCOUNT_EXPIRATION_TIME:
+            returnTypes.add(OP_GET_ACCOUNT_EXPIRATION_TIME);
+            break;
+
+          case OP_SET_ACCOUNT_EXPIRATION_TIME:
+            if (opValues == null)
+            {
+              pwpState.setAccountExpirationTime(pwpState.getCurrentTime());
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              try
+              {
+                ASN1OctetString valueString =
+                     new ASN1OctetString(opValues.get(0));
+                long time = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                 valueString);
+                pwpState.setAccountExpirationTime(time);
+              }
+              catch (DirectoryException de)
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE;
+                operation.appendErrorMessage(getMessage(msgID, opValues.get(0),
+                                                        de.getErrorMessage()));
+                operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_ACCOUNT_EXPIRATION_TIME);
+            break;
+
+          case OP_CLEAR_ACCOUNT_EXPIRATION_TIME:
+            pwpState.clearAccountExpirationTime();
+            returnTypes.add(OP_GET_ACCOUNT_EXPIRATION_TIME);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION:
+            returnTypes.add(OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION);
+            break;
+
+          case OP_GET_PASSWORD_CHANGED_TIME:
+            returnTypes.add(OP_GET_PASSWORD_CHANGED_TIME);
+            break;
+
+          case OP_SET_PASSWORD_CHANGED_TIME:
+            if (opValues == null)
+            {
+              pwpState.setPasswordChangedTime();
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              try
+              {
+                ASN1OctetString valueString =
+                     new ASN1OctetString(opValues.get(0));
+                long time = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                 valueString);
+                pwpState.setPasswordChangedTime(time);
+              }
+              catch (DirectoryException de)
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE;
+                operation.appendErrorMessage(getMessage(msgID, opValues.get(0),
+                                                        de.getErrorMessage()));
+                operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_PASSWORD_CHANGED_TIME);
+            break;
+
+          case OP_CLEAR_PASSWORD_CHANGED_TIME:
+            pwpState.clearPasswordChangedTime();
+            returnTypes.add(OP_GET_PASSWORD_CHANGED_TIME);
+            break;
+
+          case OP_GET_PASSWORD_EXPIRATION_WARNED_TIME:
+            returnTypes.add(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME);
+            break;
+
+          case OP_SET_PASSWORD_EXPIRATION_WARNED_TIME:
+            if (opValues == null)
+            {
+              pwpState.setWarnedTime();
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              try
+              {
+                ASN1OctetString valueString =
+                     new ASN1OctetString(opValues.get(0));
+                long time = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                 valueString);
+                pwpState.setWarnedTime(time);
+              }
+              catch (DirectoryException de)
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE;
+                operation.appendErrorMessage(getMessage(msgID,
+                                                        opValues.get(0),
+                                                        de.getErrorMessage()));
+                operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME);
+            break;
+
+          case OP_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME:
+            pwpState.clearWarnedTime();
+            returnTypes.add(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION:
+            returnTypes.add(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING:
+            returnTypes.add(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING);
+            break;
+
+          case OP_GET_AUTHENTICATION_FAILURE_TIMES:
+            returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
+            break;
+
+          case OP_ADD_AUTHENTICATION_FAILURE_TIME:
+            if (opValues == null)
+            {
+              if (policy.getLockoutFailureCount() == 0)
+              {
+                returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
+                break;
+              }
+
+              pwpState.updateAuthFailureTimes();
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_ADD_FAILURE_TIME_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              try
+              {
+                ASN1OctetString valueString =
+                     new ASN1OctetString(opValues.get(0));
+                long time = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                 valueString);
+                List<Long> authFailureTimes = pwpState.getAuthFailureTimes();
+                ArrayList<Long> newFailureTimes =
+                     new ArrayList<Long>(authFailureTimes.size()+1);
+                newFailureTimes.addAll(authFailureTimes);
+                newFailureTimes.add(time);
+                pwpState.setAuthFailureTimes(newFailureTimes);
+              }
+              catch (DirectoryException de)
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_AUTH_FAILURE_TIME;
+                String message = getMessage(msgID, opValues.get(0),
+                                            de.getErrorMessage());
+                operation.setResultCode(de.getResultCode());
+                operation.appendErrorMessage(message);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
+            break;
+
+          case OP_SET_AUTHENTICATION_FAILURE_TIMES:
+            if (opValues == null)
+            {
+              ArrayList<Long> valueList = new ArrayList<Long>(1);
+              valueList.add(pwpState.getCurrentTime());
+              pwpState.setAuthFailureTimes(valueList);
+            }
+            else
+            {
+              ArrayList<Long> valueList = new ArrayList<Long>(opValues.size());
+              for (String s : opValues)
+              {
+                try
+                {
+                  valueList.add(
+                       GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                            new ASN1OctetString(s)));
+                }
+                catch (DirectoryException de)
+                {
+                  int msgID = MSGID_PWPSTATE_EXTOP_BAD_AUTH_FAILURE_TIME;
+                  String message = getMessage(msgID, s, de.getErrorMessage());
+                  operation.setResultCode(de.getResultCode());
+                  operation.appendErrorMessage(message);
+                  return;
+                }
+              }
+              pwpState.setAuthFailureTimes(valueList);
+            }
+
+            returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
+            break;
+
+          case OP_CLEAR_AUTHENTICATION_FAILURE_TIMES:
+            pwpState.clearFailureLockout();
+            returnTypes.add(OP_GET_AUTHENTICATION_FAILURE_TIMES);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK:
+            returnTypes.add(OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK);
+            break;
+
+          case OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT:
+            returnTypes.add(OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT);
+            break;
+
+          case OP_GET_LAST_LOGIN_TIME:
+            returnTypes.add(OP_GET_LAST_LOGIN_TIME);
+            break;
+
+          case OP_SET_LAST_LOGIN_TIME:
+            if (opValues == null)
+            {
+              pwpState.setLastLoginTime();
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              try
+              {
+                ASN1OctetString valueString =
+                     new ASN1OctetString(opValues.get(0));
+                long time = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                 valueString);
+                pwpState.setLastLoginTime(time);
+              }
+              catch (DirectoryException de)
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME;
+                operation.appendErrorMessage(getMessage(msgID, opValues.get(0),
+                                                        de.getErrorMessage()));
+                operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_LAST_LOGIN_TIME);
+            break;
+
+          case OP_CLEAR_LAST_LOGIN_TIME:
+            pwpState.clearLastLoginTime();
+            returnTypes.add(OP_GET_LAST_LOGIN_TIME);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT:
+            returnTypes.add(OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT);
+            break;
+
+          case OP_GET_PASSWORD_RESET_STATE:
+            returnTypes.add(OP_GET_PASSWORD_RESET_STATE);
+            break;
+
+          case OP_SET_PASSWORD_RESET_STATE:
+            if (opValues == null)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_NO_RESET_STATE_VALUE;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              String value = opValues.get(0);
+              if (value.equalsIgnoreCase("true"))
+              {
+                pwpState.setMustChangePassword(true);
+              }
+              else if (value.equalsIgnoreCase("false"))
+              {
+                pwpState.setMustChangePassword(false);
+              }
+              else
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE;
+                operation.appendErrorMessage(getMessage(msgID));
+                operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_PASSWORD_RESET_STATE);
+            break;
+
+          case OP_CLEAR_PASSWORD_RESET_STATE:
+            pwpState.setMustChangePassword(false);
+            returnTypes.add(OP_GET_PASSWORD_RESET_STATE);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT:
+            returnTypes.add(OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT);
+            break;
+
+          case OP_GET_GRACE_LOGIN_USE_TIMES:
+            returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
+            break;
+
+          case OP_ADD_GRACE_LOGIN_USE_TIME:
+            if (opValues == null)
+            {
+              pwpState.updateGraceLoginTimes();
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_ADD_GRACE_LOGIN_TIME_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              try
+              {
+                ASN1OctetString valueString =
+                     new ASN1OctetString(opValues.get(0));
+                long time = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                 valueString);
+                List<Long> authFailureTimes = pwpState.getGraceLoginTimes();
+                ArrayList<Long> newGraceTimes =
+                     new ArrayList<Long>(authFailureTimes.size()+1);
+                newGraceTimes.addAll(authFailureTimes);
+                newGraceTimes.add(time);
+                pwpState.setGraceLoginTimes(newGraceTimes);
+              }
+              catch (DirectoryException de)
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_GRACE_LOGIN_TIME;
+                String message = getMessage(msgID, opValues.get(0),
+                                            de.getErrorMessage());
+                operation.setResultCode(de.getResultCode());
+                operation.appendErrorMessage(message);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
+            break;
+
+          case OP_SET_GRACE_LOGIN_USE_TIMES:
+            if (opValues == null)
+            {
+              ArrayList<Long> valueList = new ArrayList<Long>(1);
+              valueList.add(pwpState.getCurrentTime());
+              pwpState.setGraceLoginTimes(valueList);
+            }
+            else
+            {
+              ArrayList<Long> valueList = new ArrayList<Long>(opValues.size());
+              for (String s : opValues)
+              {
+                try
+                {
+                  valueList.add(
+                       GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                            new ASN1OctetString(s)));
+                }
+                catch (DirectoryException de)
+                {
+                  int msgID = MSGID_PWPSTATE_EXTOP_BAD_GRACE_LOGIN_TIME;
+                  String message = getMessage(msgID, s, de.getErrorMessage());
+                  operation.setResultCode(de.getResultCode());
+                  operation.appendErrorMessage(message);
+                  return;
+                }
+              }
+              pwpState.setGraceLoginTimes(valueList);
+            }
+
+            returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
+            break;
+
+          case OP_CLEAR_GRACE_LOGIN_USE_TIMES:
+            pwpState.clearGraceLoginTimes();
+            returnTypes.add(OP_GET_GRACE_LOGIN_USE_TIMES);
+            break;
+
+          case OP_GET_REMAINING_GRACE_LOGIN_COUNT:
+            returnTypes.add(OP_GET_REMAINING_GRACE_LOGIN_COUNT);
+            break;
+
+          case OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME:
+            returnTypes.add(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
+            break;
+
+          case OP_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME:
+            if (opValues == null)
+            {
+              pwpState.setRequiredChangeTime();
+            }
+            else if (opValues.size() != 1)
+            {
+              int msgID = MSGID_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME_COUNT;
+              operation.appendErrorMessage(getMessage(msgID));
+              operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+              return;
+            }
+            else
+            {
+              try
+              {
+                ASN1OctetString valueString =
+                     new ASN1OctetString(opValues.get(0));
+                long time = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
+                                 valueString);
+                pwpState.setRequiredChangeTime(time);
+              }
+              catch (DirectoryException de)
+              {
+                int msgID = MSGID_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME;
+                operation.appendErrorMessage(getMessage(msgID, opValues.get(0),
+                                                        de.getErrorMessage()));
+                operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+                return;
+              }
+            }
+
+            returnTypes.add(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
+            break;
+
+          case OP_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME:
+            pwpState.clearRequiredChangeTime();
+            returnTypes.add(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME:
+            returnTypes.add(OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME);
+            break;
+
+          default:
+            int msgID = MSGID_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE;
+            operation.appendErrorMessage(getMessage(msgID, opType));
+            operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
+            return;
+        }
+      }
+
+
+      // If there are any modifications that need to be made to the password
+      // policy state, then apply them now.
+      List<Modification> stateMods = pwpState.getModifications();
+      if ((stateMods != null) && (! stateMods.isEmpty()))
+      {
+        ModifyOperation modifyOperation =
+             conn.processModify(targetDN, stateMods);
+        if (modifyOperation.getResultCode() != ResultCode.SUCCESS)
+        {
+          operation.setResultCode(modifyOperation.getResultCode());
+          operation.setErrorMessage(modifyOperation.getErrorMessage());
+          operation.setMatchedDN(modifyOperation.getMatchedDN());
+          operation.setReferralURLs(modifyOperation.getReferralURLs());
+          return;
+        }
+      }
+    }
+
+
+    // Construct the sequence of values to return.
+    ArrayList<ASN1Element> opElements = new ArrayList<ASN1Element>();
+    if (returnAll || returnTypes.contains(OP_GET_PASSWORD_POLICY_DN))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_POLICY_DN,
+                            policy.getConfigEntryDN().toString()));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_ACCOUNT_DISABLED_STATE))
+    {
+      opElements.add(encode(OP_GET_ACCOUNT_DISABLED_STATE,
+                            String.valueOf(pwpState.isDisabled())));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_ACCOUNT_EXPIRATION_TIME))
+    {
+      String expTimeStr;
+      long expTime = pwpState.getAccountExpirationTime();
+      if (expTime < 0)
+      {
+        expTimeStr = null;
+      }
+      else
+      {
+        expTimeStr = GeneralizedTimeSyntax.format(expTime);
+      }
+
+      opElements.add(encode(OP_GET_ACCOUNT_EXPIRATION_TIME, expTimeStr));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION))
+    {
+      String secondsStr;
+      long expTime = pwpState.getAccountExpirationTime();
+      if (expTime < 0)
+      {
+        secondsStr = null;
+      }
+      else
+      {
+        secondsStr =
+             String.valueOf((expTime - pwpState.getCurrentTime()) / 1000);
+      }
+
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION,
+                            secondsStr));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_PASSWORD_CHANGED_TIME))
+    {
+      String timeStr;
+      long changedTime = pwpState.getPasswordChangedTime();
+      if (changedTime < 0)
+      {
+        timeStr = null;
+      }
+      else
+      {
+        timeStr = GeneralizedTimeSyntax.format(changedTime);
+      }
+
+      opElements.add(encode(OP_GET_PASSWORD_CHANGED_TIME, timeStr));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME))
+    {
+      String timeStr;
+      long warnedTime = pwpState.getWarnedTime();
+      if (warnedTime < 0)
+      {
+        timeStr = null;
+      }
+      else
+      {
+        timeStr = GeneralizedTimeSyntax.format(warnedTime);
+      }
+
+      opElements.add(encode(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME, timeStr));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION))
+    {
+      String secondsStr;
+      int secondsUntilExp = pwpState.getSecondsUntilExpiration();
+      if (secondsUntilExp < 0)
+      {
+        secondsStr = null;
+      }
+      else
+      {
+        secondsStr = String.valueOf(secondsUntilExp);
+      }
+
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION,
+                            secondsStr));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING))
+    {
+      String secondsStr;
+      int secondsUntilExp = pwpState.getSecondsUntilExpiration();
+      if (secondsUntilExp < 0)
+      {
+        secondsStr = null;
+      }
+      else
+      {
+        int secondsUntilWarning = secondsUntilExp - policy.getWarningInterval();
+        if (secondsUntilWarning <= 0)
+        {
+          secondsStr = "0";
+        }
+        else
+        {
+          secondsStr = String.valueOf(secondsUntilWarning);
+        }
+      }
+
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING,
+                            secondsStr));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_AUTHENTICATION_FAILURE_TIMES))
+    {
+      opElements.add(encode(OP_GET_AUTHENTICATION_FAILURE_TIMES,
+                            pwpState.getAuthFailureTimes()));
+    }
+
+    if (returnAll || returnTypes.contains(
+                          OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK))
+    {
+      // We have to check whether the account is locked due to failures before
+      // we can get the length of time until the account is unlocked.
+      String secondsStr;
+      if (pwpState.lockedDueToFailures())
+      {
+        int seconds = pwpState.getSecondsUntilUnlock();
+        if (seconds <= 0)
+        {
+          secondsStr = null;
+        }
+        else
+        {
+          secondsStr = String.valueOf(seconds);
+        }
+      }
+      else
+      {
+        secondsStr = null;
+      }
+
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK,
+                            secondsStr));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT))
+    {
+      String remainingFailuresStr;
+      int allowedFailureCount = policy.getLockoutFailureCount();
+      if (allowedFailureCount > 0)
+      {
+        int remainingFailures =
+                 allowedFailureCount - pwpState.getAuthFailureTimes().size();
+        if (remainingFailures < 0)
+        {
+          remainingFailures = 0;
+        }
+
+        remainingFailuresStr = String.valueOf(remainingFailures);
+      }
+      else
+      {
+        remainingFailuresStr = null;
+      }
+
+      opElements.add(encode(OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT,
+                            remainingFailuresStr));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_LAST_LOGIN_TIME))
+    {
+      String timeStr;
+      long lastLoginTime = pwpState.getLastLoginTime();
+      if (lastLoginTime < 0)
+      {
+        timeStr = null;
+      }
+      else
+      {
+        timeStr = GeneralizedTimeSyntax.format(lastLoginTime);
+      }
+
+      opElements.add(encode(OP_GET_LAST_LOGIN_TIME, timeStr));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT))
+    {
+      String secondsStr;
+      int lockoutInterval = policy.getIdleLockoutInterval();
+      if (lockoutInterval > 0)
+      {
+        long lastLoginTime = pwpState.getLastLoginTime();
+        if (lastLoginTime < 0)
+        {
+          secondsStr = "0";
+        }
+        else
+        {
+          long lockoutTime = lastLoginTime + (lockoutInterval*1000);
+          long currentTime = pwpState.getCurrentTime();
+          int secondsUntilLockout = (int) ((lockoutTime - currentTime) / 1000);
+          if (secondsUntilLockout <= 0)
+          {
+            secondsStr = "0";
+          }
+          else
+          {
+            secondsStr = String.valueOf(secondsUntilLockout);
+          }
+        }
+      }
+      else
+      {
+        secondsStr = null;
+      }
+
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT, secondsStr));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_PASSWORD_RESET_STATE))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_RESET_STATE,
+                            String.valueOf(pwpState.mustChangePassword())));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT))
+    {
+      String secondsStr;
+      if (pwpState.mustChangePassword())
+      {
+        int maxAge = policy.getMaximumPasswordResetAge();
+        if (maxAge > 0)
+        {
+          long currentTime = pwpState.getCurrentTime();
+          long changedTime = pwpState.getPasswordChangedTime();
+          int changeAge = (int) ((currentTime - changedTime) / 1000);
+          int timeToLockout = maxAge - changeAge;
+          if (timeToLockout <= 0)
+          {
+            secondsStr = "0";
+          }
+          else
+          {
+            secondsStr = String.valueOf(timeToLockout);
+          }
+        }
+        else
+        {
+          secondsStr = null;
+        }
+      }
+      else
+      {
+        secondsStr = null;
+      }
+
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT,
+                            secondsStr));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_GRACE_LOGIN_USE_TIMES))
+    {
+      opElements.add(encode(OP_GET_GRACE_LOGIN_USE_TIMES,
+                            pwpState.getGraceLoginTimes()));
+    }
+
+    if (returnAll || returnTypes.contains(OP_GET_REMAINING_GRACE_LOGIN_COUNT))
+    {
+      String remainingStr;
+      int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
+      if (remainingGraceLogins <= 0)
+      {
+        remainingStr = "0";
+      }
+      else
+      {
+        remainingStr = String.valueOf(remainingGraceLogins);
+      }
+
+      opElements.add(encode(OP_GET_REMAINING_GRACE_LOGIN_COUNT, remainingStr));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME))
+    {
+      String timeStr;
+      long requiredChangeTime = pwpState.getRequiredChangeTime();
+      if (requiredChangeTime < 0)
+      {
+        timeStr = null;
+      }
+      else
+      {
+        timeStr = GeneralizedTimeSyntax.format(requiredChangeTime);
+      }
+
+      opElements.add(encode(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME, timeStr));
+    }
+
+    if (returnAll ||
+        returnTypes.contains(OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME))
+    {
+      String secondsStr;
+      long policyRequiredChangeTime = policy.getRequireChangeByTime();
+      if (policyRequiredChangeTime > 0)
+      {
+        long accountRequiredChangeTime = pwpState.getRequiredChangeTime();
+        if (accountRequiredChangeTime >= policyRequiredChangeTime)
+        {
+          secondsStr = null;
+        }
+        else
+        {
+          long currentTime = pwpState.getCurrentTime();
+          if (currentTime >= policyRequiredChangeTime)
+          {
+            secondsStr = "0";
+          }
+          else
+          {
+            secondsStr =
+                 String.valueOf((policyRequiredChangeTime-currentTime) / 1000);
+
+          }
+        }
+      }
+      else
+      {
+        secondsStr = null;
+      }
+
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME,
+                            secondsStr));
+    }
+
+    ArrayList<ASN1Element> responseValueElements =
+         new ArrayList<ASN1Element>(2);
+    responseValueElements.add(dnString);
+    responseValueElements.add(new ASN1Sequence(opElements));
+
+    ASN1OctetString responseValue =
+         new ASN1OctetString(new ASN1Sequence(responseValueElements).encode());
+
+    operation.setResponseOID(OID_PASSWORD_POLICY_STATE_EXTOP);
+    operation.setResponseValue(responseValue);
+    operation.setResultCode(ResultCode.SUCCESS);
+  }
+
+
+
+  /**
+   * Encodes the provided information in a form suitable for including in the
+   * response value.
+   *
+   * @param  opType  The operation type to use for the value.
+   * @param  value   The single value to include in the response.
+   *
+   * @return  The encoded ASN.1 element.
+   */
+  public static ASN1Element encode(int opType, String value)
+  {
+    ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
+    elements.add(new ASN1Enumerated(opType));
+
+    if (value != null)
+    {
+      ArrayList<ASN1Element> valueElements = new ArrayList<ASN1Element>(1);
+      valueElements.add(new ASN1OctetString(value));
+      elements.add(new ASN1Sequence(valueElements));
+    }
+
+    return new ASN1Sequence(elements);
+  }
+
+
+
+  /**
+   * Encodes the provided information in a form suitable for including in the
+   * response value.
+   *
+   * @param  opType  The operation type to use for the value.
+   * @param  values  The set of string values to include in the response.
+   *
+   * @return  The encoded ASN.1 element.
+   */
+  public static ASN1Element encode(int opType, String[] values)
+  {
+    ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
+    elements.add(new ASN1Enumerated(opType));
+
+    if ((values != null) && (values.length > 0))
+    {
+      ArrayList<ASN1Element> valueElements =
+           new ArrayList<ASN1Element>(values.length);
+      for (int i=0; i < values.length; i++)
+      {
+        valueElements.add(new ASN1OctetString(values[i]));
+      }
+      elements.add(new ASN1Sequence(valueElements));
+    }
+
+    return new ASN1Sequence(elements);
+  }
+
+
+
+  /**
+   * Encodes the provided information in a form suitable for including in the
+   * response value.
+   *
+   * @param  opType  The operation type to use for the value.
+   * @param  values  The set of timestamp values to include in the response.
+   *
+   * @return  The encoded ASN.1 element.
+   */
+  public static ASN1Element encode(int opType, List<Long> values)
+  {
+    ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
+    elements.add(new ASN1Enumerated(opType));
+
+    ArrayList<ASN1Element> valueElements =
+         new ArrayList<ASN1Element>(values.size());
+    for (long l : values)
+    {
+      valueElements.add(new ASN1OctetString(GeneralizedTimeSyntax.format(l)));
+    }
+    elements.add(new ASN1Sequence(valueElements));
+
+    return new ASN1Sequence(elements);
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
index 660702a..4ac4dab 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -5212,6 +5212,274 @@
 
 
   /**
+   * The message ID for the message that will be used if a user requests the
+   * password policy state extended operation but does not have adequate
+   * privileges to do so.  This does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_NO_PRIVILEGE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 502;
+
+
+
+  /**
+   * The message ID for the message that will be used if a user requests the
+   * password policy state extended operation but does not include a request
+   * value.  This does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_NO_REQUEST_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 503;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to decode the password policy state extended request value.
+   * This takes a single argument, which is a message explaining the problem
+   * that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_DECODE_FAILURE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 504;
+
+
+
+  /**
+   * The message ID for the message that will be used if the server finds
+   * multiple entries for a given DN.  This takes a single argument, which is a
+   * string representation of the provided DN.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_MULTIPLE_ENTRIES =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 505;
+
+
+
+  /**
+   * The message ID for the message that will be used if the password policy
+   * state request includes an operation with an invalid encoding.  This takes a
+   * single argument, which is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_INVALID_OP_ENCODING =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 506;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the disabled state for an account but does not provide a value.  This
+   * does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_NO_DISABLED_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 507;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the disabled state for an account but provides multiple values.  This
+   * does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_DISABLED_VALUE_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 508;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the disabled state for an account but provides an invalid value.  This
+   * does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_DISABLED_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 509;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the account expiration time but provides multiple values.  This does
+   * not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 510;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the account expiration time but provides an invalid value.  This takes
+   * two arguments, which are the provided value and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 511;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the password changed time but provides multiple values.  This does not
+   * take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 512;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the password changed time but provides an invalid value.  This takes
+   * two arguments, which are the provided value and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 513;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the password warned time but provides multiple values.  This does not
+   * take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 514;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the password warned time but provides an invalid value.  This takes
+   * two arguments, which are the provided value and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 515;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * add a failure time but provides multiple values.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_ADD_FAILURE_TIME_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 516;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * alter the failure times but provides an invalid value.  This takes two
+   * arguments, which are the provided value and a message explaining the
+   * that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_AUTH_FAILURE_TIME =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 517;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the last login time but provides multiple values.  This does not take
+   * any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 518;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the last login time but provides an invalid value.  This takes two
+   * arguments, which are the provided value and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 519;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the reset state for an account but does not provide a value.  This
+   * does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_NO_RESET_STATE_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 520;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the reset state for an account but provides multiple values.  This does
+   * does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 521;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the reset state for an account but provides an invalid value.  This
+   * does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 522;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * add a grace login time but provides multiple values.  This does not take
+   * any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_ADD_GRACE_LOGIN_TIME_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 523;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * alter the grace login times but provides an invalid value.  This takes two
+   * arguments, which are the provided value and a message explaining the
+   * that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_GRACE_LOGIN_TIME =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 524;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the required change time but provides multiple values.  This does not
+   * take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME_COUNT =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 525;
+
+
+
+  /**
+   * The message ID for the message that will be used if the user attempts to
+   * set the required change time but provides an invalid value.  This takes two
+   * arguments, which are the provided value and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 526;
+
+
+
+  /**
+   * The message ID for the message that will be used if the password policy
+   * state request includes an operation with an unrecognized type.  This takes
+   * a single argument, which is the provided operation type.
+   */
+  public static final int MSGID_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE =
+       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_SEVERE_ERROR | 527;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -7492,6 +7760,121 @@
     registerMessage(MSGID_ENTRYUUID_VATTR_NOT_SEARCHABLE,
                     "The %s attribute is not searchable and should not be " +
                     "included in otherwise unindexed search filters");
+
+
+    registerMessage(MSGID_PWPSTATE_EXTOP_NO_PRIVILEGE,
+                    "You do not have sufficient privileges to use the " +
+                    "password policy state extended operation");
+    registerMessage(MSGID_PWPSTATE_EXTOP_NO_REQUEST_VALUE,
+                    "The provided password policy state extended request " +
+                    "did not include a request value");
+    registerMessage(MSGID_PWPSTATE_EXTOP_DECODE_FAILURE,
+                    "An unexpected error occurred while attempting to decode " +
+                    "password policy state extended request value:  %s");
+    registerMessage(MSGID_PWPSTATE_EXTOP_MULTIPLE_ENTRIES,
+                    "SEVERE ERROR:  Multiple entries were found with DN %s");
+    registerMessage(MSGID_PWPSTATE_EXTOP_INVALID_OP_ENCODING,
+                    "An unexpected error occurred while attempting to decode " +
+                    "an operation from the password policy state extended " +
+                    "request:  %s");
+    registerMessage(MSGID_PWPSTATE_EXTOP_NO_DISABLED_VALUE,
+                    "No value was provided for the password policy state " +
+                    "operation intended to set the disabled state for the " +
+                    "user.  Exactly one value (either 'true' or 'false') " +
+                    "must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_DISABLED_VALUE_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to set the disabled state " +
+                    "for the user.  Exactly one value (either 'true' or " +
+                    "'false') must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_DISABLED_VALUE,
+                    "The value provided for the password policy state " +
+                    "operation  intended to set the disabled state for the " +
+                    "user was invalid.  The value must be either 'true' or " +
+                    "'false'");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to set the account expiration " +
+                    "time for the user.  Exactly one value must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_ACCT_EXP_VALUE,
+                    "The value %s provided for the password policy state " +
+                    "operation used to set the account expiration time was " +
+                    "invalid:  %s.  The value should be specifed using the " +
+                    "generalized time format");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to set the password changed " +
+                    "time for the user.  Exactly one value must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_PWCHANGETIME_VALUE,
+                    "The value %s provided for the password policy state " +
+                    "operation used to set the password changed time was " +
+                    "invalid:  %s.  The value should be specifed using the " +
+                    "generalized time format");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to set the password warned " +
+                    "time for the user.  Exactly one value must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_PWWARNEDTIME_VALUE,
+                    "The value %s provided for the password policy state " +
+                    "operation used to set the password warned time was " +
+                    "invalid:  %s.  The value should be specifed using the " +
+                    "generalized time format");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_ADD_FAILURE_TIME_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to add an authentication " +
+                    "failure time for the user.  Exactly one value must be " +
+                    "given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_AUTH_FAILURE_TIME,
+                    "The value %s provided for the password policy state " +
+                    "operation used to update the authentication failure " +
+                    "times was invalid:  %s.  The value should be specifed " +
+                    "using the generalized time format");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to set the last login time for " +
+                    "the user.  Exactly one value must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_LAST_LOGIN_TIME,
+                    "The value %s provided for the password policy state " +
+                    "operation used to set the last login time was invalid:  " +
+                    "%s.  The value should be specifed using the " +
+                    "generalized time format");
+    registerMessage(MSGID_PWPSTATE_EXTOP_NO_RESET_STATE_VALUE,
+                    "No value was provided for the password policy state " +
+                    "operation intended to set the reset state for the " +
+                    "user.  Exactly one value (either 'true' or 'false') " +
+                    "must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to set the reset state " +
+                    "for the user.  Exactly one value (either 'true' or " +
+                    "'false') must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_RESET_STATE_VALUE,
+                    "The value provided for the password policy state " +
+                    "operation  intended to set the reset state for the " +
+                    "user was invalid.  The value must be either 'true' or " +
+                    "'false'");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_ADD_GRACE_LOGIN_TIME_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to add a grace login use time " +
+                    "for the user.  Exactly one value must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_GRACE_LOGIN_TIME,
+                    "The value %s provided for the password policy state " +
+                    "operation used to update the grace login use times was " +
+                    "invalid:  %s.  The value should be specifed using the " +
+                    "generalized time format");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME_COUNT,
+                    "Multiple values were provided for the password policy " +
+                    "state operation intended to set the required change " +
+                    "time for the user.  Exactly one value must be given");
+    registerMessage(MSGID_PWPSTATE_EXTOP_BAD_REQUIRED_CHANGE_TIME,
+                    "The value %s provided for the password policy state " +
+                    "operation used to set the required change time was " +
+                    "invalid:  %s.  The value should be specifed using the " +
+                    "generalized time format");
+    registerMessage(MSGID_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE,
+                    "The password policy state extended request included an " +
+                    "operation with an invalid or unsupported operation type " +
+                    "of %s");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
index 24e71bd..9055d7e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ToolMessages.java
@@ -8028,6 +8028,918 @@
 
 
   /**
+   * The message ID for the message that will be used as the description for the
+   * password policy state tool.
+   */
+  public static final int MSGID_PWPSTATE_TOOL_DESCRIPTION =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1094;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * hostname argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_HOST =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1095;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * port argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_PORT =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1096;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * useSSL argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_USESSL =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1097;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * useStartTLS argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_USESTARTTLS =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1098;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * bindDN argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_BINDDN =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1099;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * bindPassword argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_BINDPW =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1100;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * bindPasswordFile argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_BINDPWFILE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1101;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * targetDN argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_TARGETDN =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1102;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * saslOptions argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_SASLOPTIONS =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1103;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * trustAll argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_TRUST_ALL =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1104;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * keyStorePath argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_KSFILE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1105;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * keyStorePIN argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_KSPW =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1106;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * keyStorePINFile argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_KSPWFILE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1107;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * trustStorePath argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_TSFILE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1108;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * trustStorePIN argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_TSPW =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1109;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * trustStorePINFile argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_TSPWFILE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1110;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * showUsage argument.
+   */
+  public static final int MSGID_PWPSTATE_DESCRIPTION_SHOWUSAGE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1111;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-all subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_GET_ALL =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1112;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-password-policy-dn subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_POLICY_DN =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1113;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-account-is-disabled subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_ACCOUNT_DISABLED_STATE =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1114;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-account-is-disabled subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_SET_ACCOUNT_DISABLED_STATE =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1115;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * argument used to set Boolean values.
+   */
+  public static final int MSGID_DESCRIPTION_OPERATION_BOOLEAN_VALUE =
+    CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1116;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-account-is-disabled subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_CLEAR_ACCOUNT_DISABLED_STATE =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1117;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-account-expiration-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_ACCOUNT_EXPIRATION_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1118;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-account-expiration-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_SET_ACCOUNT_EXPIRATION_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1119;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * argument used to set single values for single-valued attributes.
+   */
+  public static final int MSGID_DESCRIPTION_OPERATION_TIME_VALUE =
+    CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1120;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-account-expiration-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_CLEAR_ACCOUNT_EXPIRATION_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1121;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-seconds-until-account-expiration subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1122;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-password-changed-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_CHANGED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1123;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-password-changed-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_CHANGED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1124;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-password-changed-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_CHANGED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1125;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-password-expiration-warned-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_EXPIRATION_WARNED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1126;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-password-expiration-warned-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_EXPIRATION_WARNED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1127;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-password-expiration-warned-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1128;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-seconds-until-password-expiration subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_PASSWORD_EXP =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1129;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-seconds-until-password-expiration-warning subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_PASSWORD_EXP_WARNING =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1130;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-auth-failure-times subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_GET_AUTH_FAILURE_TIMES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1131;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * add-auth-failure-time subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_ADD_AUTH_FAILURE_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1132;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-auth-failure-times subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_SET_AUTH_FAILURE_TIMES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1133;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * argument used to set single values for multi-valued attributes.
+   */
+  public static final int MSGID_DESCRIPTION_OPERATION_TIME_VALUES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1134;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-auth-failure-times subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_CLEAR_AUTH_FAILURE_TIMES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1135;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-seconds-until-authentication-failure-unlock subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1136;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-remaining-authentication-failure-count subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_REMAINING_AUTH_FAILURE_COUNT =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1137;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-last-login-time subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_GET_LAST_LOGIN_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1138;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-last-login-time subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_SET_LAST_LOGIN_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1139;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-last-login-time subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_CLEAR_LAST_LOGIN_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1140;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-seconds-until-idle-lockout subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_IDLE_LOCKOUT =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1141;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-password-is-reset subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_RESET_STATE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1142;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-password-is-reset subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_RESET_STATE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1143;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-password-is-reset subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_RESET_STATE =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1144;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-seconds-until-reset-lockout subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_RESET_LOCKOUT =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1145;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-grace-login-use-times subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_GET_GRACE_LOGIN_USE_TIMES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1146;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * add-grace-login-use-time subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_ADD_GRACE_LOGIN_USE_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1147;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-grace-login-use-times subcommand.
+   */
+  public static final int MSGID_DESCRIPTION_PWPSTATE_SET_GRACE_LOGIN_USE_TIMES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1148;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-grace-login-use-times subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_CLEAR_GRACE_LOGIN_USE_TIMES =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1149;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-remaining-grace-login-count subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_REMAINING_GRACE_LOGIN_COUNT =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1150;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-password-changed-by-required-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_PW_CHANGED_BY_REQUIRED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1151;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * set-password-changed-by-required-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_SET_PW_CHANGED_BY_REQUIRED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1152;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * clear-password-changed-by-required-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_CLEAR_PW_CHANGED_BY_REQUIRED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1153;
+
+
+
+  /**
+   * The message ID for the message that will be used as the description for the
+   * get-seconds-until-required-time subcommand.
+   */
+  public static final int
+       MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1154;
+
+
+
+  /**
+   * The message ID for the message that will be used if no subcommand was
+   * provided on the command line.  This does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_NO_SUBCOMMAND =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1155;
+
+
+
+  /**
+   * The message ID for the message that will be used if the operation value
+   * provided could not be parsed as a Boolean value.  This takes a single
+   * argument, which is the provided value.
+   */
+  public static final int MSGID_PWPSTATE_INVALID_BOOLEAN_VALUE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1156;
+
+
+
+  /**
+   * The message ID for the message that will be used if no value was provided
+   * for an operation that requires a Boolean value.  This does not take any
+   * arguments.
+   */
+  public static final int MSGID_PWPSTATE_NO_BOOLEAN_VALUE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1157;
+
+
+
+  /**
+   * The message ID for the message that will be used if an unrecognized
+   * subcommand was provided.  This takes a single argument, which is the
+   * unrecognized subcommand.
+   */
+  public static final int MSGID_PWPSTATE_INVALID_SUBCOMMAND =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1158;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to send the extended request to the server.  This takes a single
+   * argument, which is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_CANNOT_SEND_REQUEST_EXTOP =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1159;
+
+
+
+  /**
+   * The message ID for the message that will be used if the server closed the
+   * connection without sending a response.  This does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_CONNECTION_CLOSED_READING_RESPONSE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1160;
+
+
+
+  /**
+   * The message ID for the message that will be used if the response from the
+   * server indicates that the request was not processed properly.  This takes
+   * three arguments, which are the result code, a string representation of the
+   * result code, and the error message.
+   */
+  public static final int MSGID_PWPSTATE_REQUEST_FAILED =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1161;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurred while
+   * attempting to decode the response message from the server.  This takes a
+   * single argument, which is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_CANNOT_DECODE_RESPONSE_MESSAGE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1162;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurred while
+   * attempting to decode a response op.  This takes a single argument, which is
+   * a message explaining the problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_CANNOT_DECODE_RESPONSE_OP =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1163;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the password policy DN.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_PASSWORD_POLICY_DN =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1164;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the account disabled state.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_ACCOUNT_DISABLED_STATE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1165;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the account expiration time.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_ACCOUNT_EXPIRATION_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1166;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the seconds until account expiration.
+   */
+  public static final int
+       MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_ACCOUNT_EXPIRATION =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1167;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the password changed time.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_PASSWORD_CHANGED_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1168;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the password expiration warned time.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_PASSWORD_EXPIRATION_WARNED_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1169;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the seconds until password expiration.
+   */
+  public static final int
+       MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_EXPIRATION =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1170;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the seconds until the password expiration warning.
+   */
+  public static final int
+       MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1171;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the auth failure times.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_AUTH_FAILURE_TIMES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1172;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the seconds until auth failure unlock.
+   */
+  public static final int
+       MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1173;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the remaining auth failure count.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_REMAINING_AUTH_FAILURE_COUNT =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1174;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the last login time.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_LAST_LOGIN_TIME =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1175;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the seconds until idle lockout.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_IDLE_LOCKOUT =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1176;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the password reset state.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_PASSWORD_RESET_STATE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1177;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the seconds until password reset lockout.
+   */
+  public static final int
+       MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1178;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the grace login use times.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_GRACE_LOGIN_USE_TIMES =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1179;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the remaining grace login count.
+   */
+  public static final int MSGID_PWPSTATE_LABEL_REMAINING_GRACE_LOGIN_COUNT =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1180;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the password changed by required time.
+   */
+  public static final int
+       MSGID_PWPSTATE_LABEL_PASSWORD_CHANGED_BY_REQUIRED_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1181;
+
+
+
+  /**
+   * The message ID for the message that will be used as the label when
+   * displaying the seconds until required change time.
+   */
+  public static final int
+       MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_REQUIRED_CHANGE_TIME =
+            CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 1182;
+
+
+
+  /**
+   * The message ID for the message that will be used if the response contained
+   * an unknown or invalid operation type.  This takes a single argument, which
+   * is the invalid operation type.
+   */
+  public static final int MSGID_PWPSTATE_INVALID_RESPONSE_OP_TYPE =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1183;
+
+
+
+  /**
+   * The message ID for the message that will be used if two arguments that are
+   * mutually exclusive were both provided.  This takes two arguments, which are
+   * the long identifiers for the mutually-exclusive command line arguments.
+   */
+  public static final int MSGID_PWPSTATE_MUTUALLY_EXCLUSIVE_ARGUMENTS =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1184;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to perform SSL initialization.  This takes a single argument, which
+   * is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_CANNOT_INITIALIZE_SSL =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1185;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to parse a SASL option string.  This takes a single argument, which
+   * is the SASL option string.
+   */
+  public static final int MSGID_PWPSTATE_CANNOT_PARSE_SASL_OPTION =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1186;
+
+
+
+  /**
+   * The message ID for the message that will be used if SASL options were used
+   * without specifying the SASL mechanism.  This does not take any arguments.
+   */
+  public static final int MSGID_PWPSTATE_NO_SASL_MECHANISM =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1187;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to determine the port to use to communicate with the Directory
+   * Server.  This takes two arguments, which are the name of the port argument
+   * and a message explaining the problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_CANNOT_DETERMINE_PORT =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1188;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to connect to the Directory Server.  This takes a single argument,
+   * which is a message explaining the problem that occurred.
+   */
+  public static final int MSGID_PWPSTATE_CANNOT_CONNECT =
+       CATEGORY_MASK_TOOLS | SEVERITY_MASK_SEVERE_ERROR | 1189;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -10634,6 +11546,278 @@
 
     registerMessage(MSGID_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY,
                     "Display summary usage information");
+
+
+    registerMessage(MSGID_PWPSTATE_TOOL_DESCRIPTION,
+                    "This utility may be used to retrieve and manipulate " +
+                    "the values of password policy state variables");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_HOST,
+                    "Directory server hostname or IP address");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_PORT,
+                    "Directory server port number");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_USESSL,
+                    "Use SSL for secure communication with the server");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_USESTARTTLS,
+                    "Use StartTLS to secure communication with the server");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_BINDDN,
+                    "The DN to use to bind to the server");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_BINDPW,
+                    "The password to use to bind to the server");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_BINDPWFILE,
+                    "The path to the file containing the bind password");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_TARGETDN,
+                    "The DN of the user entry for which to get and set " +
+                    "password policy state information");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_SASLOPTIONS,
+                    "SASL bind options");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_TRUST_ALL,
+                    "Trust all server SSL certificates");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_KSFILE,
+                    "Certificate keystore path");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_KSPW,
+                    "Certificate keystore PIN");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_KSPWFILE,
+                    "Certificate keystore PIN file");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_TSFILE,
+                    "Certificate trust store path");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_TSPW,
+                    "Certificate trust store PIN");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_TSPWFILE,
+                    "Certificate trust store PIN file");
+    registerMessage(MSGID_PWPSTATE_DESCRIPTION_SHOWUSAGE,
+                    "Display this usage information");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_ALL,
+                    "Display all password policy state information for the " +
+                    "user");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_POLICY_DN,
+                    "Display the DN of the password policy for the user");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_ACCOUNT_DISABLED_STATE,
+                    "Display information about whether the user account has " +
+                    "been administratively disabled");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_ACCOUNT_DISABLED_STATE,
+                    "Specify whether the user account has been " +
+                    "administratively disabled");
+    registerMessage(MSGID_DESCRIPTION_OPERATION_BOOLEAN_VALUE,
+                    "'true' to indicate that the account is disabled, or " +
+                    "'false' to indicate that it is not disabled");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_CLEAR_ACCOUNT_DISABLED_STATE,
+                    "Clear account disabled state information from the user " +
+                    "account");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_ACCOUNT_EXPIRATION_TIME,
+                    "Display when the user account will expire");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_ACCOUNT_EXPIRATION_TIME,
+                    "Specify when the user account will expire");
+    registerMessage(MSGID_DESCRIPTION_OPERATION_TIME_VALUE,
+                    "A timestamp value using the generalized time syntax");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_CLEAR_ACCOUNT_EXPIRATION_TIME,
+                    "Clear account expiration time information from the user " +
+                    "account");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION,
+         "Display the length of time in seconds until the user account " +
+         "expires");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_CHANGED_TIME,
+                    "Display the time that the user's password was last " +
+                    "changed");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_CHANGED_TIME,
+                    "Specify the time that the user's password was last " +
+                    "changed.  This should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_CHANGED_TIME,
+                    "Clear information about the time that the user's " +
+                    "password was last changed.  This should be used only " +
+                    "for testing purposes");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_EXPIRATION_WARNED_TIME,
+         "Display the time that the user first received an expiration " +
+         "warning notice");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_EXPIRATION_WARNED_TIME,
+         "Specify the time that the user first received an expiration " +
+         "warning notice.  This should be used only for testing purposes");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME,
+         "Clear information about the time that the user first received an " +
+         "expiration warning notice.  This should be used only for testing " +
+         "purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_PASSWORD_EXP,
+                    "Display length of time in seconds until the user's " +
+                    "password expires");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_PASSWORD_EXP_WARNING,
+         "Display the length of time in seconds until the user should start " +
+         "receiving password expiration warning notices");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_AUTH_FAILURE_TIMES,
+                    "Display the authentication failure times for the user");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_ADD_AUTH_FAILURE_TIME,
+                    "Add an authentication failure time to the user " +
+                    "account.  This should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_AUTH_FAILURE_TIMES,
+                    "Specify the authentication failure times for the user.  " +
+                    "This should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_OPERATION_TIME_VALUES,
+                    "A timestamp value using the generalized time syntax.  " +
+                    "Multiple timestamp values may be given by providing " +
+                    "this argument multiple times");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_CLEAR_AUTH_FAILURE_TIMES,
+                    "Clear authentication failure time information from the " +
+                    "user's account.  This should be used only for testing " +
+                    "purposes");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK,
+         "Display the length of time in seconds until the authentication " +
+         "failure lockout expires");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_REMAINING_AUTH_FAILURE_COUNT,
+                    "Display the number of remaining authentication failures " +
+                    "until the user's account is locked");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_LAST_LOGIN_TIME,
+                    "Display the time that the user last authenticated to " +
+                    "the server");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_LAST_LOGIN_TIME,
+                    "Specify the time that the user last authenticated to " +
+                    "the server.  This should be used only for testing " +
+                    "purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_CLEAR_LAST_LOGIN_TIME,
+                    "Clear the time that the user last authenticated to the " +
+                    "server.  This should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_IDLE_LOCKOUT,
+                    "Display the length of time in seconds until user's " +
+                    "account is locked because it has remained idle for " +
+                    "too long");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_RESET_STATE,
+                    "Display information about whether the user will be " +
+                    "required to change his or her password on the next " +
+                    "successful authentication");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_RESET_STATE,
+                    "Specify whether the user will be required to change his " +
+                    "or her password on the next successful authentication.  " +
+                    "This should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_RESET_STATE,
+                    "Clear information about whether the user will be " +
+                    "required to change his or her password on the next " +
+                    "successful authentication.  This should be used only " +
+                    "for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_RESET_LOCKOUT,
+                    "Display the length of time in seconds until user's " +
+                    "account is locked because the user failed to change the " +
+                    "password in a timely manner after an administrative " +
+                    "reset");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_GRACE_LOGIN_USE_TIMES,
+                    "Display the grace login use times for the user");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_ADD_GRACE_LOGIN_USE_TIME,
+                    "Add a grace login use time to the user account.  This " +
+                    "should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_GRACE_LOGIN_USE_TIMES,
+                    "Specify the grace login use times for the user.  This " +
+                    "should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_CLEAR_GRACE_LOGIN_USE_TIMES,
+                    "Clear the set of grace login use times for the user.  " +
+                    "This should be used only for testing purposes");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_REMAINING_GRACE_LOGIN_COUNT,
+                    "Display the number of grace logins remaining for the " +
+                    "user");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_GET_PW_CHANGED_BY_REQUIRED_TIME,
+                    "Display the required password change time with which " +
+                    "the user last complied");
+    registerMessage(MSGID_DESCRIPTION_PWPSTATE_SET_PW_CHANGED_BY_REQUIRED_TIME,
+                    "Specify the required password change time with which " +
+                    "the user last complied.  This should be used only for " +
+                    "testing purposes");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_CLEAR_PW_CHANGED_BY_REQUIRED_TIME,
+         "Clear information about the required password change time with " +
+         "which the user last complied.  This should be used only for " +
+         "testing purposes");
+    registerMessage(
+         MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME,
+         "Display the length of time in seconds that the user has remaining " +
+         "to change his or her password before the account becomes locked " +
+         "due to the required change time");
+    registerMessage(MSGID_PWPSTATE_MUTUALLY_EXCLUSIVE_ARGUMENTS,
+                    "ERROR:  You may not provide both the %s and the %s " +
+                    "arguments");
+    registerMessage(MSGID_PWPSTATE_CANNOT_INITIALIZE_SSL,
+                    "ERROR:  Unable to perform SSL initialization:  %s");
+    registerMessage(MSGID_PWPSTATE_CANNOT_PARSE_SASL_OPTION,
+                    "ERROR:  The provided SASL option string \"%s\" could " +
+                    "not be parsed in the form \"name=value\"");
+    registerMessage(MSGID_PWPSTATE_NO_SASL_MECHANISM,
+                    "ERROR:  One or more SASL options were provided, but " +
+                    "none of them were the \"mech\" option to specify which " +
+                    "SASL mechanism should be used");
+    registerMessage(MSGID_PWPSTATE_CANNOT_DETERMINE_PORT,
+                    "ERROR:  Cannot parse the value of the %s argument as " +
+                    "an integer value between 1 and 65535:  %s");
+    registerMessage(MSGID_PWPSTATE_CANNOT_CONNECT,
+                    "ERROR:  Cannot establish a connection to the " +
+                    "Directory Server:  %s");
+    registerMessage(MSGID_PWPSTATE_NO_SUBCOMMAND,
+                    "No subcommand was provided to indicate which password " +
+                    "policy state operation should be performed");
+    registerMessage(MSGID_PWPSTATE_INVALID_BOOLEAN_VALUE,
+                    "The provided value '%s' was invalid for the requested " +
+                    "operation.  A Boolean value of either 'true' or 'false' " +
+                    "was expected");
+    registerMessage(MSGID_PWPSTATE_NO_BOOLEAN_VALUE,
+                    "No value was specified, but the requested operation " +
+                    "requires a Boolean value of either 'true' or 'false'");
+    registerMessage(MSGID_PWPSTATE_INVALID_SUBCOMMAND,
+                    "Unrecognized subcommand '%s'");
+    registerMessage(MSGID_PWPSTATE_CANNOT_SEND_REQUEST_EXTOP,
+                    "An error occurred while attempting to send the request " +
+                    "to the server:  %s");
+    registerMessage(MSGID_PWPSTATE_CONNECTION_CLOSED_READING_RESPONSE,
+                    "The Directory Server closed the connection before the " +
+                    "response could be read");
+    registerMessage(MSGID_PWPSTATE_REQUEST_FAILED,
+                    "The server was unable to process the request:  result " +
+                    "code %d (%s), error message '%s'");
+    registerMessage(MSGID_PWPSTATE_CANNOT_DECODE_RESPONSE_MESSAGE,
+                    "The server was unable to decode the response message " +
+                    "from the server:  %s");
+    registerMessage(MSGID_PWPSTATE_CANNOT_DECODE_RESPONSE_OP,
+                    "Unable to decode information about an operation " +
+                    "contained in the response:  %s");
+    registerMessage(MSGID_PWPSTATE_LABEL_PASSWORD_POLICY_DN,
+                    "Password Policy DN");
+    registerMessage(MSGID_PWPSTATE_LABEL_ACCOUNT_DISABLED_STATE,
+                    "Account Is Disabled");
+    registerMessage(MSGID_PWPSTATE_LABEL_ACCOUNT_EXPIRATION_TIME,
+                    "Account Expiration Time");
+    registerMessage(MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_ACCOUNT_EXPIRATION,
+                    "Seconds Until Account Expiration");
+    registerMessage(MSGID_PWPSTATE_LABEL_PASSWORD_CHANGED_TIME,
+                    "Password Changed Time");
+    registerMessage(MSGID_PWPSTATE_LABEL_PASSWORD_EXPIRATION_WARNED_TIME,
+                    "Password Expiration Warned Time");
+    registerMessage(MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_EXPIRATION,
+                    "Seconds Until Password Expiration");
+    registerMessage(
+         MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING,
+         "Seconds Until Password Expiration Warning");
+    registerMessage(MSGID_PWPSTATE_LABEL_AUTH_FAILURE_TIMES,
+                    "Authentication Failure Times");
+    registerMessage(MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK,
+                    "Seconds Until Authentication Failure Unlock");
+    registerMessage(MSGID_PWPSTATE_LABEL_REMAINING_AUTH_FAILURE_COUNT,
+                    "Remaining Authentication Failure Count");
+    registerMessage(MSGID_PWPSTATE_LABEL_LAST_LOGIN_TIME,
+                    "Last Login Time");
+    registerMessage(MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_IDLE_LOCKOUT,
+                    "Seconds Until Idle Account Lockout");
+    registerMessage(MSGID_PWPSTATE_LABEL_PASSWORD_RESET_STATE,
+                    "Password Is Reset");
+    registerMessage(MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT,
+                    "Seconds Until Password Reset Lockout");
+    registerMessage(MSGID_PWPSTATE_LABEL_GRACE_LOGIN_USE_TIMES,
+                    "Grace Login Use Times");
+    registerMessage(MSGID_PWPSTATE_LABEL_REMAINING_GRACE_LOGIN_COUNT,
+                    "Remaining Grace Login Count");
+    registerMessage(MSGID_PWPSTATE_LABEL_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                    "Password Changed by Required Time");
+    registerMessage(MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_REQUIRED_CHANGE_TIME,
+                    "Seconds Until Required Change Time");
+    registerMessage(MSGID_PWPSTATE_INVALID_RESPONSE_OP_TYPE,
+                    "Unrecognized or invalid operation type:  %s");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/tools/ManageAccount.java b/opendj-sdk/opends/src/server/org/opends/server/tools/ManageAccount.java
new file mode 100644
index 0000000..36f7d77
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/tools/ManageAccount.java
@@ -0,0 +1,1688 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools;
+
+
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opends.server.protocols.asn1.ASN1Element;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.asn1.ASN1Reader;
+import org.opends.server.protocols.asn1.ASN1Sequence;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
+import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
+import org.opends.server.protocols.ldap.LDAPMessage;
+import org.opends.server.protocols.ldap.LDAPResultCode;
+import org.opends.server.types.NullOutputStream;
+import org.opends.server.util.args.Argument;
+import org.opends.server.util.args.ArgumentException;
+import org.opends.server.util.args.BooleanArgument;
+import org.opends.server.util.args.FileBasedArgument;
+import org.opends.server.util.args.IntegerArgument;
+import org.opends.server.util.args.MultiChoiceArgument;
+import org.opends.server.util.args.StringArgument;
+import org.opends.server.util.args.SubCommand;
+import org.opends.server.util.args.SubCommandArgumentParser;
+
+import static org.opends.server.extensions.
+                   PasswordPolicyStateExtendedOperation.*;
+import static org.opends.server.messages.MessageHandler.*;
+import static org.opends.server.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
+/**
+ * This class provides a tool that can be used to perform various kinds of
+ * account management using the password policy state extended operation.
+ */
+public class ManageAccount
+{
+  /**
+   * The fully-qualified name of this class.
+   */
+  private static final String CLASS_NAME =
+       "org.opends.server.tools.ManageAccount";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get all password policy
+   * state information for the user.
+   */
+  private static final String SC_GET_ALL = "get-all";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the DN of the password
+   * policy for a given user.
+   */
+  private static final String SC_GET_PASSWORD_POLICY_DN =
+       "get-password-policy-dn";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the disabled state for
+   * a user.
+   */
+  private static final String SC_GET_ACCOUNT_DISABLED_STATE =
+       "get-account-is-disabled";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the disabled state for
+   * a user.
+   */
+  private static final String SC_SET_ACCOUNT_DISABLED_STATE =
+       "set-account-is-disabled";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the disabled state
+   * for a user.
+   */
+  private static final String SC_CLEAR_ACCOUNT_DISABLED_STATE =
+       "clear-account-is-disabled";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the account expiration
+   * time.
+   */
+  private static final String SC_GET_ACCOUNT_EXPIRATION_TIME =
+       "get-account-expiration-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the account expiration
+   * time.
+   */
+  private static final String SC_SET_ACCOUNT_EXPIRATION_TIME =
+       "set-account-expiration-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the account
+   * expiration time.
+   */
+  private static final String SC_CLEAR_ACCOUNT_EXPIRATION_TIME =
+       "clear-account-expiration-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the length of time
+   * before the account expires.
+   */
+  private static final String SC_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION =
+       "get-seconds-until-account-expiration";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the time the password
+   * was last changed.
+   */
+  private static final String SC_GET_PASSWORD_CHANGED_TIME =
+       "get-password-changed-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the time the password
+   * was last changed.
+   */
+  private static final String SC_SET_PASSWORD_CHANGED_TIME =
+       "set-password-changed-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the time the password
+   * was last changed.
+   */
+  private static final String SC_CLEAR_PASSWORD_CHANGED_TIME =
+       "clear-password-changed-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the time the user was
+   * first warned about an upcoming password expiration.
+   */
+  private static final String SC_GET_PASSWORD_EXP_WARNED_TIME =
+       "get-password-expiration-warned-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the time the user was
+   * first warned about an upcoming password expiration.
+   */
+  private static final String SC_SET_PASSWORD_EXP_WARNED_TIME =
+       "set-password-expiration-warned-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the time the user was
+   * first warned about an upcoming password expiration.
+   */
+  private static final String SC_CLEAR_PASSWORD_EXP_WARNED_TIME =
+       "clear-password-expiration-warned-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the length of time
+   * before the password expires.
+   */
+  private static final String SC_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION =
+       "get-seconds-until-password-expiration";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the length of time
+   * before the user is first warned about an upcoming password expiration.
+   */
+  private static final String SC_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING =
+       "get-seconds-until-password-expiration-warning";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the authentication
+   * failure times for the user.
+   */
+  private static final String SC_GET_AUTHENTICATION_FAILURE_TIMES =
+       "get-authentication-failure-times";
+
+
+
+  /**
+   * The name of the subcommand that will be used to add an authentication
+   * failure time for the user.
+   */
+  private static final String SC_ADD_AUTHENTICATION_FAILURE_TIME =
+       "add-authentication-failure-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the authentication
+   * failure times for the user.
+   */
+  private static final String SC_SET_AUTHENTICATION_FAILURE_TIMES =
+       "set-authentication-failure-times";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the authentication
+   * failure times for the user.
+   */
+  private static final String SC_CLEAR_AUTHENTICATION_FAILURE_TIMES =
+       "clear-authentication-failure-times";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the length of time
+   * before the user's account is unlocked.
+   */
+  private static final String
+       SC_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK =
+            "get-seconds-until-authentication-failure-unlock";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the number of remaining
+   * authentication failures for the user.
+   */
+  private static final String SC_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT =
+       "get-remaining-authentication-failure-count";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the last login time for
+   * the user.
+   */
+  private static final String SC_GET_LAST_LOGIN_TIME =
+       "get-last-login-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the last login time for
+   * the user.
+   */
+  private static final String SC_SET_LAST_LOGIN_TIME =
+       "set-last-login-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the last login time
+   * for the user.
+   */
+  private static final String SC_CLEAR_LAST_LOGIN_TIME =
+       "clear-last-login-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the length of time
+   * before the account is idle locked.
+   */
+  private static final String SC_GET_SECONDS_UNTIL_IDLE_LOCKOUT =
+       "get-seconds-until-idle-lockout";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the password reset
+   * state for a user.
+   */
+  private static final String SC_GET_PASSWORD_RESET_STATE =
+       "get-password-is-reset";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the password reset
+   * state for a user.
+   */
+  private static final String SC_SET_PASSWORD_RESET_STATE =
+       "set-password-is-reset";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the password reset
+   * state for a user.
+   */
+  private static final String SC_CLEAR_PASSWORD_RESET_STATE =
+       "clear-password-is-reset";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the length of time
+   * before the password reset lockout occurs.
+   */
+  private static final String SC_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT =
+       "get-seconds-until-password-reset-lockout";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the grace login use
+   * times for the user.
+   */
+  private static final String SC_GET_GRACE_LOGIN_USE_TIMES =
+       "get-grace-login-use-times";
+
+
+
+  /**
+   * The name of the subcommand that will be used to add a grace login use time
+   * for the user.
+   */
+  private static final String SC_ADD_GRACE_LOGIN_USE_TIME =
+       "add-grace-login-use-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the grace login use
+   * times for the user.
+   */
+  private static final String SC_SET_GRACE_LOGIN_USE_TIMES =
+       "set-grace-login-use-times";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the grace login use
+   * times for the user.
+   */
+  private static final String SC_CLEAR_GRACE_LOGIN_USE_TIMES =
+       "clear-grace-login-use-times";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get number of remaining
+   * grace logins for the user.
+   */
+  private static final String SC_GET_REMAINING_GRACE_LOGIN_COUNT =
+       "get-remaining-grace-login-count";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the password changed by
+   * required time for the user.
+   */
+  private static final String SC_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME =
+       "get-password-changed-by-required-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to set the password changed by
+   * required time for the user.
+   */
+  private static final String SC_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME =
+       "set-password-changed-by-required-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to clear the password changed
+   * by required time for the user.
+   */
+  private static final String SC_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME =
+       "clear-password-changed-by-required-time";
+
+
+
+  /**
+   * The name of the subcommand that will be used to get the length of time
+   * before the user is required to change his/her password due to the required
+   * change time.
+   */
+  private static final String SC_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME =
+       "get-seconds-until-required-change-time";
+
+
+
+  /**
+   * The name of the argument that will be used for holding the value(s) to use
+   * for the target operation.
+   */
+  private static final String ARG_OP_VALUE = "opvalue";
+
+
+
+  /**
+   * The value that will be used when encoding a password policy state operation
+   * that should not have any values.
+   */
+  private static final String NO_VALUE = null;
+
+
+
+  // The ASN.1 reader used to read responses from the server.
+  private static ASN1Reader asn1Reader;
+
+  // The ASN.1 writer used to send requests to the server.
+  private static ASN1Writer asn1Writer;
+
+  // The counter that will be used for LDAP message IDs.
+  private static AtomicInteger nextMessageID;
+
+  // The connection to the server.
+  private static LDAPConnection connection;
+
+  // The print stream to use when writing messages to standard error.
+  private static PrintStream err;
+
+  // The print stream to use when writing messages to standard output.
+  private static PrintStream out;
+
+  // The DN of the user to target with the operation.
+  private static String targetDNString;
+
+  // The argument parser for this tool.
+  private static SubCommandArgumentParser argParser;
+
+
+
+  /**
+   * Parses the command-line arguments, connects to the server, and performs the
+   * appropriate processing.
+   *
+   * @param  args  The command-line arguments provided to this program.
+   */
+  public static void main(String[] args)
+  {
+    int returnCode = main(args, System.out, System.err);
+    if (returnCode != 0)
+    {
+      System.exit(returnCode);
+    }
+  }
+
+
+
+  /**
+   * Parses the command-line arguments, connects to the server, and performs the
+   * appropriate processing.
+   *
+   * @param  args       The command-line arguments provided to this program.
+   * @param  outStream  The output stream to use for standard output, or
+   *                    {@code null} if standard output is not needed.
+   * @param  errStream  The output stream to use for standard error, or
+   *                    {@code null} if standard error is not needed.
+   *
+   * @return  A result code indicating whether the processing was successful.
+   */
+  public static int main(String[] args, OutputStream outStream,
+                         OutputStream errStream)
+  {
+    if (outStream == null)
+    {
+      out = NullOutputStream.printStream();
+    }
+    else
+    {
+      out = new PrintStream(outStream);
+    }
+
+    if (errStream == null)
+    {
+      err = NullOutputStream.printStream();
+    }
+    else
+    {
+      err = new PrintStream(errStream);
+    }
+
+
+
+
+    // Parse the command-line arguments provided to the program.
+    int result = parseArgsAndConnect(args);
+    if (result < 0)
+    {
+      // This should only happen if we're only displaying usage information or
+      // doing something else other than actually running the tool.
+      return LDAPResultCode.SUCCESS;
+    }
+    else if (result != LDAPResultCode.SUCCESS)
+    {
+      return result;
+    }
+
+
+    try
+    {
+      // Use the subcommand provided to figure out how to encode the request.
+      ArrayList<ASN1Element> opElements = new ArrayList<ASN1Element>(1);
+      result = processSubcommand(opElements);
+      if (result != LDAPResultCode.SUCCESS)
+      {
+        return result;
+      }
+
+
+      // Generate the extended request and send it to the server.
+      ArrayList<ASN1Element> valueElements = new ArrayList<ASN1Element>(2);
+      valueElements.add(new ASN1OctetString(targetDNString));
+      if (! opElements.isEmpty())
+      {
+        valueElements.add(new ASN1Sequence(opElements));
+      }
+      ASN1OctetString requestValue =
+           new ASN1OctetString(new ASN1Sequence(valueElements).encode());
+
+      ExtendedRequestProtocolOp extendedRequest =
+           new ExtendedRequestProtocolOp(OID_PASSWORD_POLICY_STATE_EXTOP,
+                                         requestValue);
+
+      LDAPMessage requestMessage =
+           new LDAPMessage(nextMessageID.getAndIncrement(), extendedRequest);
+
+      try
+      {
+        asn1Writer.writeElement(requestMessage.encode());
+      }
+      catch (Exception e)
+      {
+        int    msgID   = MSGID_PWPSTATE_CANNOT_SEND_REQUEST_EXTOP;
+        String message = getMessage(msgID, getExceptionMessage(e));
+        err.println(wrapText(message, MAX_LINE_WIDTH));
+        return LDAPResultCode.CLIENT_SIDE_SERVER_DOWN;
+      }
+
+
+      // Read the response from the server.
+      ArrayList<ASN1Element> responseOpElements;
+      try
+      {
+        ASN1Element responseElement = asn1Reader.readElement();
+        if (responseElement == null)
+        {
+          int    msgID   = MSGID_PWPSTATE_CONNECTION_CLOSED_READING_RESPONSE;
+          String message = getMessage(msgID);
+          err.println(wrapText(message, MAX_LINE_WIDTH));
+          return LDAPResultCode.CLIENT_SIDE_SERVER_DOWN;
+        }
+
+        LDAPMessage responseMessage =
+             LDAPMessage.decode(responseElement.decodeAsSequence());
+        ExtendedResponseProtocolOp extendedResponse =
+             responseMessage.getExtendedResponseProtocolOp();
+
+        int resultCode = extendedResponse.getResultCode();
+        if (resultCode != LDAPResultCode.SUCCESS)
+        {
+          int msgID = MSGID_PWPSTATE_REQUEST_FAILED;
+          String message =
+               getMessage(msgID, resultCode,
+                          LDAPResultCode.toString(resultCode),
+                          String.valueOf(extendedResponse.getErrorMessage()));
+          err.println(wrapText(message, MAX_LINE_WIDTH));
+          return resultCode;
+        }
+
+        ASN1Sequence valueSequence =
+             ASN1Sequence.decodeAsSequence(extendedResponse.getValue().value());
+        responseOpElements =
+             valueSequence.elements().get(1).decodeAsSequence().elements();
+      }
+      catch (Exception e)
+      {
+        int    msgID   = MSGID_PWPSTATE_CANNOT_DECODE_RESPONSE_MESSAGE;
+        String message = getMessage(msgID, getExceptionMessage(e));
+        err.println(wrapText(message, MAX_LINE_WIDTH));
+        return LDAPResultCode.CLIENT_SIDE_SERVER_DOWN;
+      }
+
+
+      // Get the response value and parse its individual elements.
+      for (ASN1Element opElement : responseOpElements)
+      {
+        int opType;
+        ArrayList<String> opValues;
+
+        try
+        {
+          ASN1Sequence opSequence = opElement.decodeAsSequence();
+          ArrayList<ASN1Element> elements = opSequence.elements();
+          opType = elements.get(0).decodeAsEnumerated().intValue();
+          opValues = new ArrayList<String>();
+          if (elements.size() == 2)
+          {
+            for (ASN1Element e : elements.get(1).decodeAsSequence().elements())
+            {
+              opValues.add(e.decodeAsOctetString().stringValue());
+            }
+          }
+        }
+        catch (Exception e)
+        {
+          int msgID = MSGID_PWPSTATE_CANNOT_DECODE_RESPONSE_OP;
+          String message = getMessage(msgID, getExceptionMessage(e));
+          err.println(wrapText(message, MAX_LINE_WIDTH));
+          continue;
+        }
+
+        switch (opType)
+        {
+          case OP_GET_PASSWORD_POLICY_DN:
+            int msgID = MSGID_PWPSTATE_LABEL_PASSWORD_POLICY_DN;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_ACCOUNT_DISABLED_STATE:
+            msgID = MSGID_PWPSTATE_LABEL_ACCOUNT_DISABLED_STATE;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_ACCOUNT_EXPIRATION_TIME:
+            msgID = MSGID_PWPSTATE_LABEL_ACCOUNT_EXPIRATION_TIME;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION:
+            msgID = MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_ACCOUNT_EXPIRATION;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_PASSWORD_CHANGED_TIME:
+            msgID = MSGID_PWPSTATE_LABEL_PASSWORD_CHANGED_TIME;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_PASSWORD_EXPIRATION_WARNED_TIME:
+            msgID = MSGID_PWPSTATE_LABEL_PASSWORD_EXPIRATION_WARNED_TIME;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION:
+            msgID = MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_EXPIRATION;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING:
+            msgID =
+                 MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_AUTHENTICATION_FAILURE_TIMES:
+            msgID = MSGID_PWPSTATE_LABEL_AUTH_FAILURE_TIMES;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK:
+            msgID = MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT:
+            msgID = MSGID_PWPSTATE_LABEL_REMAINING_AUTH_FAILURE_COUNT;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_LAST_LOGIN_TIME:
+            msgID = MSGID_PWPSTATE_LABEL_LAST_LOGIN_TIME;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT:
+            msgID = MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_IDLE_LOCKOUT;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_PASSWORD_RESET_STATE:
+            msgID = MSGID_PWPSTATE_LABEL_PASSWORD_RESET_STATE;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT:
+            msgID = MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_GRACE_LOGIN_USE_TIMES:
+            msgID = MSGID_PWPSTATE_LABEL_GRACE_LOGIN_USE_TIMES;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_REMAINING_GRACE_LOGIN_COUNT:
+            msgID = MSGID_PWPSTATE_LABEL_REMAINING_GRACE_LOGIN_COUNT;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME:
+            msgID = MSGID_PWPSTATE_LABEL_PASSWORD_CHANGED_BY_REQUIRED_TIME;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          case OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME:
+            msgID = MSGID_PWPSTATE_LABEL_SECONDS_UNTIL_REQUIRED_CHANGE_TIME;
+            printLabelAndValues(msgID, opValues);
+            break;
+
+          default:
+            msgID = MSGID_PWPSTATE_INVALID_RESPONSE_OP_TYPE;
+            String message = getMessage(msgID, opType);
+            err.println(wrapText(message, MAX_LINE_WIDTH));
+            break;
+        }
+      }
+
+
+      // If we've gotten here, then everything completed successfully.
+      return 0;
+    }
+    finally
+    {
+      // Close the connection to the server if it's active.
+      if (connection != null)
+      {
+        connection.close(nextMessageID);
+      }
+    }
+  }
+
+
+
+  /**
+   * Initializes the argument parser for this tool, parses the provided
+   * arguments, and establishes a connection to the server.
+   *
+   * @return  A result code that indicates the result of the processing.  A
+   *          value of zero indicates that all processing completed
+   *          successfully.  A value of -1 indicates that only the usage
+   *          information was displayed and no further action is required.
+   */
+  private static int parseArgsAndConnect(String[] args)
+  {
+    int msgID = MSGID_PWPSTATE_TOOL_DESCRIPTION;
+    argParser = new SubCommandArgumentParser(CLASS_NAME, getMessage(msgID),
+                                             false);
+
+    BooleanArgument   showUsage;
+    BooleanArgument   trustAll;
+    BooleanArgument   useSSL;
+    BooleanArgument   useStartTLS;
+    FileBasedArgument bindPWFile;
+    FileBasedArgument keyStorePWFile;
+    FileBasedArgument trustStorePWFile;
+    IntegerArgument   port;
+    StringArgument    bindDN;
+    StringArgument    bindPW;
+    StringArgument    certNickname;
+    StringArgument    host;
+    StringArgument    keyStoreFile;
+    StringArgument    keyStorePW;
+    StringArgument    saslOption;
+    StringArgument    targetDN;
+    StringArgument    trustStoreFile;
+    StringArgument    trustStorePW;
+
+    try
+    {
+      host = new StringArgument("host", OPTION_SHORT_HOST,
+                                OPTION_LONG_HOST, false, false, true,
+                                OPTION_VALUE_HOST, "127.0.0.1", null,
+                                MSGID_PWPSTATE_DESCRIPTION_HOST);
+      argParser.addGlobalArgument(host);
+
+      port = new IntegerArgument("port", OPTION_SHORT_PORT,
+                                 OPTION_LONG_PORT, false, false, true,
+                                 OPTION_VALUE_PORT, 389, null, true, 1,
+                                 true, 65535, MSGID_PWPSTATE_DESCRIPTION_PORT);
+      argParser.addGlobalArgument(port);
+
+      useSSL = new BooleanArgument("usessl", OPTION_SHORT_USE_SSL,
+                                   OPTION_LONG_USE_SSL,
+                                   MSGID_PWPSTATE_DESCRIPTION_USESSL);
+      argParser.addGlobalArgument(useSSL);
+
+      useStartTLS = new BooleanArgument("usestarttls", OPTION_SHORT_START_TLS,
+                                        OPTION_LONG_START_TLS,
+                                        MSGID_PWPSTATE_DESCRIPTION_USESTARTTLS);
+      argParser.addGlobalArgument(useStartTLS);
+
+      bindDN = new StringArgument("binddn", OPTION_SHORT_BINDDN,
+                                  OPTION_LONG_BINDDN, false, false, true,
+                                  OPTION_VALUE_BINDDN, null, null,
+                                  MSGID_PWPSTATE_DESCRIPTION_BINDDN);
+      argParser.addGlobalArgument(bindDN);
+
+      bindPW = new StringArgument("bindpw", OPTION_SHORT_BINDPWD,
+                                  OPTION_LONG_BINDPWD, false, false,
+                                  true,
+                                  OPTION_VALUE_BINDPWD, null, null,
+                                  MSGID_PWPSTATE_DESCRIPTION_BINDPW);
+      argParser.addGlobalArgument(bindPW);
+
+      bindPWFile = new FileBasedArgument("bindpwfile",
+                                         OPTION_SHORT_BINDPWD_FILE,
+                                         OPTION_LONG_BINDPWD_FILE,
+                                         false, false,
+                                         OPTION_VALUE_BINDPWD_FILE,
+                                         null, null,
+                                         MSGID_PWPSTATE_DESCRIPTION_BINDPWFILE);
+      argParser.addGlobalArgument(bindPWFile);
+
+      targetDN = new StringArgument("targetdn", 'b', "targetDN", true, false,
+                                    true, "{targetDN}", null, null,
+                                    MSGID_PWPSTATE_DESCRIPTION_TARGETDN);
+      argParser.addGlobalArgument(targetDN);
+
+      saslOption = new StringArgument("sasloption", OPTION_SHORT_SASLOPTION,
+                                      OPTION_LONG_SASLOPTION, false,
+                                      true, true,
+                                      OPTION_VALUE_SASLOPTION, null, null,
+                                      MSGID_PWPSTATE_DESCRIPTION_SASLOPTIONS);
+      argParser.addGlobalArgument(saslOption);
+
+      trustAll = new BooleanArgument("trustall", 'X', "trustAll",
+                                     MSGID_PWPSTATE_DESCRIPTION_TRUST_ALL);
+      argParser.addGlobalArgument(trustAll);
+
+      keyStoreFile = new StringArgument("keystorefile",
+                                        OPTION_SHORT_KEYSTOREPATH,
+                                        OPTION_LONG_KEYSTOREPATH,
+                                        false, false, true,
+                                        OPTION_VALUE_KEYSTOREPATH,
+                                        null, null,
+                                        MSGID_PWPSTATE_DESCRIPTION_KSFILE);
+      argParser.addGlobalArgument(keyStoreFile);
+
+      keyStorePW = new StringArgument("keystorepw", OPTION_SHORT_KEYSTORE_PWD,
+                                      OPTION_LONG_KEYSTORE_PWD,
+                                      false, false, true,
+                                      OPTION_VALUE_KEYSTORE_PWD,
+                                      null, null,
+                                      MSGID_PWPSTATE_DESCRIPTION_KSPW);
+      argParser.addGlobalArgument(keyStorePW);
+
+      keyStorePWFile = new FileBasedArgument("keystorepwfile",
+                                OPTION_SHORT_KEYSTORE_PWD_FILE,
+                                OPTION_LONG_KEYSTORE_PWD_FILE, false, false,
+                                OPTION_VALUE_KEYSTORE_PWD_FILE, null, null,
+                                MSGID_PWPSTATE_DESCRIPTION_KSPWFILE);
+      argParser.addGlobalArgument(keyStorePWFile);
+
+      certNickname = new StringArgument("certnickname", 'N', "certNickname",
+                                        false, false, true, "{nickname}", null,
+                                        null, MSGID_DESCRIPTION_CERT_NICKNAME);
+      argParser.addGlobalArgument(certNickname);
+
+      trustStoreFile = new StringArgument("truststorefile",
+                                          OPTION_SHORT_TRUSTSTOREPATH,
+                                          OPTION_LONG_TRUSTSTOREPATH,
+                                          false, false, true,
+                                          OPTION_VALUE_TRUSTSTOREPATH,
+                                          null, null,
+                                          MSGID_PWPSTATE_DESCRIPTION_TSFILE);
+      argParser.addGlobalArgument(trustStoreFile);
+
+      trustStorePW = new StringArgument("truststorepw", 'T',
+                                        OPTION_LONG_TRUSTSTORE_PWD,
+                                        false, false,
+                                        true, OPTION_VALUE_TRUSTSTORE_PWD, null,
+                                        null, MSGID_PWPSTATE_DESCRIPTION_TSPW);
+      argParser.addGlobalArgument(trustStorePW);
+
+      trustStorePWFile = new FileBasedArgument("truststorepwfile",
+                                  OPTION_SHORT_TRUSTSTORE_PWD_FILE,
+                                  OPTION_LONG_TRUSTSTORE_PWD_FILE,
+                                  false, false,
+                                  OPTION_VALUE_TRUSTSTORE_PWD_FILE, null, null,
+                                  MSGID_PWPSTATE_DESCRIPTION_TSPWFILE);
+      argParser.addGlobalArgument(trustStorePWFile);
+
+      showUsage = new BooleanArgument("showusage", OPTION_SHORT_HELP,
+                                      OPTION_LONG_HELP,
+                                      MSGID_PWPSTATE_DESCRIPTION_SHOWUSAGE);
+      argParser.addGlobalArgument(showUsage);
+      argParser.setUsageArgument(showUsage, out);
+
+
+      HashSet<String> booleanValues = new HashSet<String>(2);
+      booleanValues.add("true");
+      booleanValues.add("false");
+
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_ALL;
+      new SubCommand(argParser, SC_GET_ALL, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_POLICY_DN;
+      new SubCommand(argParser, SC_GET_PASSWORD_POLICY_DN, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_ACCOUNT_DISABLED_STATE;
+      new SubCommand(argParser, SC_GET_ACCOUNT_DISABLED_STATE, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_ACCOUNT_DISABLED_STATE;
+      SubCommand sc = new SubCommand(argParser, SC_SET_ACCOUNT_DISABLED_STATE,
+                                     msgID);
+      sc.addArgument(new MultiChoiceArgument(ARG_OP_VALUE, 'O',
+                              "operationValue", true, false, true,
+                              "{true|false}", null, null, booleanValues, false,
+                              MSGID_DESCRIPTION_OPERATION_BOOLEAN_VALUE));
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_ACCOUNT_DISABLED_STATE;
+      new SubCommand(argParser, SC_CLEAR_ACCOUNT_DISABLED_STATE, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_ACCOUNT_EXPIRATION_TIME;
+      new SubCommand(argParser, SC_GET_ACCOUNT_EXPIRATION_TIME, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_ACCOUNT_EXPIRATION_TIME;
+      sc = new SubCommand(argParser, SC_SET_ACCOUNT_EXPIRATION_TIME, msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, false, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_ACCOUNT_EXPIRATION_TIME;
+      sc = new SubCommand(argParser, SC_CLEAR_ACCOUNT_EXPIRATION_TIME, msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION;
+      new SubCommand(argParser, SC_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_CHANGED_TIME;
+      new SubCommand(argParser, SC_GET_PASSWORD_CHANGED_TIME, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_CHANGED_TIME;
+      sc = new SubCommand(argParser, SC_SET_PASSWORD_CHANGED_TIME, msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, false, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_CHANGED_TIME;
+      sc = new SubCommand(argParser, SC_CLEAR_PASSWORD_CHANGED_TIME, msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_EXPIRATION_WARNED_TIME;
+      new SubCommand(argParser, SC_GET_PASSWORD_EXP_WARNED_TIME, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_EXPIRATION_WARNED_TIME;
+      sc = new SubCommand(argParser, SC_SET_PASSWORD_EXP_WARNED_TIME, msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, false, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME;
+      sc = new SubCommand(argParser, SC_CLEAR_PASSWORD_EXP_WARNED_TIME, msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_PASSWORD_EXP;
+      new SubCommand(argParser, SC_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION,
+                     msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_PASSWORD_EXP_WARNING;
+      new SubCommand(argParser,
+                     SC_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_AUTH_FAILURE_TIMES;
+      new SubCommand(argParser, SC_GET_AUTHENTICATION_FAILURE_TIMES, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_ADD_AUTH_FAILURE_TIME;
+      sc = new SubCommand(argParser, SC_ADD_AUTHENTICATION_FAILURE_TIME, msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, true, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_AUTH_FAILURE_TIMES;
+      sc = new SubCommand(argParser, SC_SET_AUTHENTICATION_FAILURE_TIMES,
+                          msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, true, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUES));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_AUTH_FAILURE_TIMES;
+      sc = new SubCommand(argParser, SC_CLEAR_AUTHENTICATION_FAILURE_TIMES,
+                          msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_AUTH_FAILURE_UNLOCK;
+      new SubCommand(argParser,
+                     SC_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_REMAINING_AUTH_FAILURE_COUNT;
+      new SubCommand(argParser, SC_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT,
+                     msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_LAST_LOGIN_TIME;
+      new SubCommand(argParser, SC_GET_LAST_LOGIN_TIME, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_LAST_LOGIN_TIME;
+      sc = new SubCommand(argParser, SC_SET_LAST_LOGIN_TIME, msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, false, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_LAST_LOGIN_TIME;
+      sc = new SubCommand(argParser, SC_CLEAR_LAST_LOGIN_TIME, msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_IDLE_LOCKOUT;
+      new SubCommand(argParser, SC_GET_SECONDS_UNTIL_IDLE_LOCKOUT, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_PASSWORD_RESET_STATE;
+      new SubCommand(argParser, SC_GET_PASSWORD_RESET_STATE, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_PASSWORD_RESET_STATE;
+      sc = new SubCommand(argParser, SC_SET_PASSWORD_RESET_STATE, msgID);
+      sc.addArgument(new MultiChoiceArgument(ARG_OP_VALUE, 'O',
+                              "operationValue", true, false, true,
+                              "{true|false}", null, null, booleanValues, false,
+                              MSGID_DESCRIPTION_OPERATION_BOOLEAN_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_RESET_STATE;
+      sc = new SubCommand(argParser, SC_CLEAR_PASSWORD_RESET_STATE, msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_RESET_LOCKOUT;
+      new SubCommand(argParser, SC_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT,
+                     msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_GRACE_LOGIN_USE_TIMES;
+      new SubCommand(argParser, SC_GET_GRACE_LOGIN_USE_TIMES, msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_ADD_GRACE_LOGIN_USE_TIME;
+      sc = new SubCommand(argParser, SC_ADD_GRACE_LOGIN_USE_TIME, msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, true, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_GRACE_LOGIN_USE_TIMES;
+      sc = new SubCommand(argParser, SC_SET_GRACE_LOGIN_USE_TIMES, msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, true, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUES));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_GRACE_LOGIN_USE_TIMES;
+      sc = new SubCommand(argParser, SC_CLEAR_GRACE_LOGIN_USE_TIMES, msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_REMAINING_GRACE_LOGIN_COUNT;
+      new SubCommand(argParser, SC_GET_REMAINING_GRACE_LOGIN_COUNT,
+                     msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_PW_CHANGED_BY_REQUIRED_TIME;
+      new SubCommand(argParser, SC_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                     msgID);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_SET_PW_CHANGED_BY_REQUIRED_TIME;
+      sc = new SubCommand(argParser, SC_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                          msgID);
+      sc.addArgument(new StringArgument(ARG_OP_VALUE, 'O', "operationValue",
+                              false, false, true, "{time}", null, null,
+                              MSGID_DESCRIPTION_OPERATION_TIME_VALUE));
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_CLEAR_PW_CHANGED_BY_REQUIRED_TIME;
+      sc = new SubCommand(argParser, SC_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                          msgID);
+      sc.setHidden(true);
+
+      msgID = MSGID_DESCRIPTION_PWPSTATE_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME;
+      new SubCommand(argParser, SC_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME,
+                     msgID);
+    }
+    catch (ArgumentException ae)
+    {
+      msgID = MSGID_CANNOT_INITIALIZE_ARGS;
+      String message = getMessage(msgID, ae.getMessage());
+
+      err.println(wrapText(message, MAX_LINE_WIDTH));
+      return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
+    }
+
+    try
+    {
+      argParser.parseArguments(args);
+    }
+    catch (ArgumentException ae)
+    {
+      msgID = MSGID_ERROR_PARSING_ARGS;
+      String message = getMessage(msgID, ae.getMessage());
+
+      err.println(wrapText(message, MAX_LINE_WIDTH));
+      err.println(argParser.getUsage());
+      return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+    }
+
+
+    // If we should just display usage or version information,
+    // then exit because it will have already been done.
+    if (argParser.usageOrVersionDisplayed())
+    {
+      return -1;
+    }
+
+
+    // Get the target DN as a string for later use.
+    targetDNString = targetDN.getValue();
+
+
+    // Create the LDAP connection options object, which will be used to
+    // customize the way that we connect to the server and specify a set of
+    // basic defaults.
+    LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
+    connectionOptions.setVersionNumber(3);
+
+
+    // See if we should use SSL or StartTLS when establishing the connection.
+    // If so, then make sure only one of them was specified.
+    if (useSSL.isPresent())
+    {
+      if (useStartTLS.isPresent())
+      {
+        msgID = MSGID_PWPSTATE_MUTUALLY_EXCLUSIVE_ARGUMENTS;
+        String message = getMessage(msgID, useSSL.getLongIdentifier(),
+                                    useStartTLS.getLongIdentifier());
+        err.println(wrapText(message, MAX_LINE_WIDTH));
+        return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+      }
+      else
+      {
+        connectionOptions.setUseSSL(true);
+      }
+    }
+    else if (useStartTLS.isPresent())
+    {
+      connectionOptions.setStartTLS(true);
+    }
+
+
+    // If we should blindly trust any certificate, then install the appropriate
+    // SSL connection factory.
+    if (useSSL.isPresent() || useStartTLS.isPresent())
+    {
+      try
+      {
+        String clientAlias;
+        if (certNickname.isPresent())
+        {
+          clientAlias = certNickname.getValue();
+        }
+        else
+        {
+          clientAlias = null;
+        }
+
+        SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory();
+        sslConnectionFactory.init(trustAll.isPresent(), keyStoreFile.getValue(),
+                                  keyStorePW.getValue(), clientAlias,
+                                  trustStoreFile.getValue(),
+                                  trustStorePW.getValue());
+
+        connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
+      }
+      catch (SSLConnectionException sce)
+      {
+        msgID = MSGID_PWPSTATE_CANNOT_INITIALIZE_SSL;
+        String message = getMessage(msgID, sce.getMessage());
+        err.println(wrapText(message, MAX_LINE_WIDTH));
+        return LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
+      }
+    }
+
+
+    // If one or more SASL options were provided, then make sure that one of
+    // them was "mech" and specified a valid SASL mechanism.
+    if (saslOption.isPresent())
+    {
+      String             mechanism = null;
+      LinkedList<String> options   = new LinkedList<String>();
+
+      for (String s : saslOption.getValues())
+      {
+        int equalPos = s.indexOf('=');
+        if (equalPos <= 0)
+        {
+          msgID = MSGID_PWPSTATE_CANNOT_PARSE_SASL_OPTION;
+          String message = getMessage(msgID, s);
+          err.println(wrapText(message, MAX_LINE_WIDTH));
+          return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+        }
+        else
+        {
+          String name  = s.substring(0, equalPos);
+
+          if (name.equalsIgnoreCase("mech"))
+          {
+            mechanism = s;
+          }
+          else
+          {
+            options.add(s);
+          }
+        }
+      }
+
+      if (mechanism == null)
+      {
+        msgID = MSGID_PWPSTATE_NO_SASL_MECHANISM;
+        String message = getMessage(msgID);
+        err.println(wrapText(message, MAX_LINE_WIDTH));
+        return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+      }
+
+      connectionOptions.setSASLMechanism(mechanism);
+
+      for (String option : options)
+      {
+        connectionOptions.addSASLProperty(option);
+      }
+    }
+
+
+    // Attempt to connect and authenticate to the Directory Server.
+    nextMessageID = new AtomicInteger(1);
+    try
+    {
+      connection = new LDAPConnection(host.getValue(), port.getIntValue(),
+                                      connectionOptions, out, err);
+      connection.connectToHost(bindDN.getValue(), bindPW.getValue(),
+                               nextMessageID);
+    }
+    catch (ArgumentException ae)
+    {
+      msgID = MSGID_PWPSTATE_CANNOT_DETERMINE_PORT;
+      String message = getMessage(msgID, port.getLongIdentifier(),
+                                  ae.getMessage());
+      err.println(wrapText(message, MAX_LINE_WIDTH));
+      return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+    }
+    catch (LDAPConnectionException lce)
+    {
+      msgID = MSGID_PWPSTATE_CANNOT_CONNECT;
+      String message = getMessage(msgID, lce.getMessage());
+      err.println(wrapText(message, MAX_LINE_WIDTH));
+      return LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR;
+    }
+
+    asn1Reader = connection.getASN1Reader();
+    asn1Writer = connection.getASN1Writer();
+
+    return LDAPResultCode.SUCCESS;
+  }
+
+
+
+  /**
+   * Processes the subcommand from the provided argument parser and appends the
+   * appropriate operation elements to the given list.
+   *
+   * @param  opElements  A list into which the operation elements shouold be
+   *                     placed.
+   *
+   * @return  A result code indicating the results of the processing.
+   */
+  private static int processSubcommand(ArrayList<ASN1Element> opElements)
+  {
+    int msgID;
+    SubCommand subCommand = argParser.getSubCommand();
+    if (subCommand == null)
+    {
+      msgID = MSGID_PWPSTATE_NO_SUBCOMMAND;
+      String message = getMessage(msgID);
+      err.println(wrapText(message, MAX_LINE_WIDTH));
+      err.println(argParser.getUsage());
+      return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+    }
+
+    String subCommandName = subCommand.getName();
+    if (subCommandName.equals(SC_GET_ALL))
+    {
+      // The list should stay empty for this one.
+    }
+    else if (subCommandName.equals(SC_GET_PASSWORD_POLICY_DN))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_POLICY_DN, NO_VALUE));
+    }
+    else if (subCommandName.equals(SC_GET_ACCOUNT_DISABLED_STATE))
+    {
+      opElements.add(encode(OP_GET_ACCOUNT_DISABLED_STATE, NO_VALUE));
+    }
+    else if (subCommandName.equals(SC_SET_ACCOUNT_DISABLED_STATE))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        String valueStr = a.getValue();
+        if (valueStr.equalsIgnoreCase("true"))
+        {
+          opElements.add(encode(OP_SET_ACCOUNT_DISABLED_STATE, "true"));
+        }
+        else if (valueStr.equalsIgnoreCase("false"))
+        {
+          opElements.add(encode(OP_SET_ACCOUNT_DISABLED_STATE, "false"));
+        }
+        else
+        {
+          msgID = MSGID_PWPSTATE_INVALID_BOOLEAN_VALUE;
+          String message = getMessage(msgID, valueStr);
+          err.println(wrapText(message, MAX_LINE_WIDTH));
+          return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+        }
+      }
+      else
+      {
+        msgID = MSGID_PWPSTATE_NO_BOOLEAN_VALUE;
+        String message = getMessage(msgID);
+        err.println(wrapText(message, MAX_LINE_WIDTH));
+        return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+      }
+    }
+    else if (subCommandName.equals(SC_CLEAR_ACCOUNT_DISABLED_STATE))
+    {
+      opElements.add(encode(OP_CLEAR_ACCOUNT_DISABLED_STATE, NO_VALUE));
+    }
+    else if (subCommandName.equals(SC_GET_ACCOUNT_EXPIRATION_TIME))
+    {
+      opElements.add(encode(OP_GET_ACCOUNT_EXPIRATION_TIME, NO_VALUE));
+    }
+    else if (subCommandName.equals(SC_SET_ACCOUNT_EXPIRATION_TIME))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        opElements.add(encode(OP_SET_ACCOUNT_EXPIRATION_TIME, a.getValue()));
+      }
+      else
+      {
+        opElements.add(encode(OP_SET_ACCOUNT_EXPIRATION_TIME, NO_VALUE));
+      }
+    }
+    else if (subCommandName.equals(SC_CLEAR_ACCOUNT_EXPIRATION_TIME))
+    {
+      opElements.add(encode(OP_CLEAR_ACCOUNT_EXPIRATION_TIME, NO_VALUE));
+    }
+    else if (subCommandName.equals(SC_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION))
+    {
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION, NO_VALUE));
+    }
+    else if (subCommandName.equals(SC_GET_PASSWORD_CHANGED_TIME))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_CHANGED_TIME, NO_VALUE));
+    }
+    else if (subCommandName.equals(SC_SET_PASSWORD_CHANGED_TIME))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        opElements.add(encode(OP_SET_PASSWORD_CHANGED_TIME, a.getValue()));
+      }
+      else
+      {
+        opElements.add(encode(OP_SET_PASSWORD_CHANGED_TIME, NO_VALUE));
+      }
+    }
+    else if (subCommandName.equals(SC_CLEAR_PASSWORD_CHANGED_TIME))
+    {
+      opElements.add(encode(OP_CLEAR_PASSWORD_CHANGED_TIME, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_PASSWORD_EXP_WARNED_TIME))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_EXPIRATION_WARNED_TIME, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_SET_PASSWORD_EXP_WARNED_TIME))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        opElements.add(encode(OP_SET_PASSWORD_EXPIRATION_WARNED_TIME,
+                              a.getValue()));
+      }
+      else
+      {
+        opElements.add(encode(OP_SET_PASSWORD_EXPIRATION_WARNED_TIME,
+                              NO_VALUE));
+      }
+    }
+    else if(subCommandName.equals(SC_CLEAR_PASSWORD_EXP_WARNED_TIME))
+    {
+      opElements.add(encode(OP_CLEAR_PASSWORD_EXPIRATION_WARNED_TIME,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION))
+    {
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(
+                 SC_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING))
+    {
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_AUTHENTICATION_FAILURE_TIMES))
+    {
+      opElements.add(encode(OP_GET_AUTHENTICATION_FAILURE_TIMES, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_ADD_AUTHENTICATION_FAILURE_TIME))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        opElements.add(encode(OP_ADD_AUTHENTICATION_FAILURE_TIME,
+                              a.getValue()));
+      }
+      else
+      {
+        opElements.add(encode(OP_ADD_AUTHENTICATION_FAILURE_TIME, NO_VALUE));
+      }
+    }
+    else if(subCommandName.equals(SC_SET_AUTHENTICATION_FAILURE_TIMES))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        ArrayList<String> valueList = new ArrayList<String>(a.getValues());
+        String[] values = new String[valueList.size()];
+        valueList.toArray(values);
+
+        opElements.add(encode(OP_SET_AUTHENTICATION_FAILURE_TIMES, values));
+      }
+      else
+      {
+        opElements.add(encode(OP_SET_AUTHENTICATION_FAILURE_TIMES, NO_VALUE));
+      }
+    }
+    else if(subCommandName.equals(SC_CLEAR_AUTHENTICATION_FAILURE_TIMES))
+    {
+      opElements.add(encode(OP_CLEAR_AUTHENTICATION_FAILURE_TIMES, NO_VALUE));
+    }
+    else if(subCommandName.equals(
+                 SC_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK))
+    {
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(
+                 SC_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT))
+    {
+      opElements.add(encode(OP_GET_REMAINING_AUTHENTICATION_FAILURE_COUNT,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_LAST_LOGIN_TIME))
+    {
+      opElements.add(encode(OP_GET_LAST_LOGIN_TIME, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_SET_LAST_LOGIN_TIME))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        opElements.add(encode(OP_SET_LAST_LOGIN_TIME, a.getValue()));
+      }
+      else
+      {
+        opElements.add(encode(OP_SET_LAST_LOGIN_TIME, NO_VALUE));
+      }
+    }
+    else if(subCommandName.equals(SC_CLEAR_LAST_LOGIN_TIME))
+    {
+      opElements.add(encode(OP_CLEAR_LAST_LOGIN_TIME, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_SECONDS_UNTIL_IDLE_LOCKOUT))
+    {
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_IDLE_LOCKOUT, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_PASSWORD_RESET_STATE))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_RESET_STATE, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_SET_PASSWORD_RESET_STATE))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        String valueStr = a.getValue();
+        if (valueStr.equalsIgnoreCase("true"))
+        {
+          opElements.add(encode(OP_SET_PASSWORD_RESET_STATE, "true"));
+        }
+        else if (valueStr.equalsIgnoreCase("false"))
+        {
+          opElements.add(encode(OP_SET_PASSWORD_RESET_STATE, "false"));
+        }
+        else
+        {
+          msgID = MSGID_PWPSTATE_INVALID_BOOLEAN_VALUE;
+          String message = getMessage(msgID, valueStr);
+          err.println(wrapText(message, MAX_LINE_WIDTH));
+          return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+        }
+      }
+      else
+      {
+        msgID = MSGID_PWPSTATE_NO_BOOLEAN_VALUE;
+        String message = getMessage(msgID);
+        err.println(wrapText(message, MAX_LINE_WIDTH));
+        return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+      }
+    }
+    else if(subCommandName.equals(SC_CLEAR_PASSWORD_RESET_STATE))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_RESET_STATE, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT))
+    {
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_GRACE_LOGIN_USE_TIMES))
+    {
+      opElements.add(encode(OP_GET_GRACE_LOGIN_USE_TIMES, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_ADD_GRACE_LOGIN_USE_TIME))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        opElements.add(encode(OP_ADD_GRACE_LOGIN_USE_TIME, a.getValue()));
+      }
+      else
+      {
+        opElements.add(encode(OP_ADD_GRACE_LOGIN_USE_TIME, NO_VALUE));
+      }
+    }
+    else if(subCommandName.equals(SC_SET_GRACE_LOGIN_USE_TIMES))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        ArrayList<String> valueList = new ArrayList<String>(a.getValues());
+        String[] values = new String[valueList.size()];
+        valueList.toArray(values);
+
+        opElements.add(encode(OP_SET_GRACE_LOGIN_USE_TIMES, values));
+      }
+      else
+      {
+        opElements.add(encode(OP_SET_GRACE_LOGIN_USE_TIMES, NO_VALUE));
+      }
+    }
+    else if(subCommandName.equals(SC_CLEAR_GRACE_LOGIN_USE_TIMES))
+    {
+      opElements.add(encode(OP_CLEAR_GRACE_LOGIN_USE_TIMES, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_REMAINING_GRACE_LOGIN_COUNT))
+    {
+      opElements.add(encode(OP_GET_REMAINING_GRACE_LOGIN_COUNT, NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME))
+    {
+      opElements.add(encode(OP_GET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME))
+    {
+      Argument a = subCommand.getArgumentForName(ARG_OP_VALUE);
+      if ((a != null) && a.isPresent())
+      {
+        opElements.add(encode(OP_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                              a.getValue()));
+      }
+      else
+      {
+        opElements.add(encode(OP_SET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                              NO_VALUE));
+      }
+    }
+    else if(subCommandName.equals(SC_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME))
+    {
+      opElements.add(encode(OP_CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+                            NO_VALUE));
+    }
+    else if(subCommandName.equals(SC_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME))
+    {
+      opElements.add(encode(OP_GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME,
+                            NO_VALUE));
+    }
+    else
+    {
+      msgID = MSGID_PWPSTATE_INVALID_SUBCOMMAND;
+      String message = getMessage(msgID, subCommandName);
+      err.println(wrapText(message, MAX_LINE_WIDTH));
+      err.println(argParser.getUsage());
+      return LDAPResultCode.CLIENT_SIDE_PARAM_ERROR;
+    }
+
+    return LDAPResultCode.SUCCESS;
+  }
+
+
+
+  /**
+   * Prints information about a password policy state variable to standard
+   * output.
+   *
+   * @param  msgID   The message ID for the message to use as the label.
+   * @param  values  The set of values for the associated state variable.
+   */
+  private static void printLabelAndValues(int msgID, ArrayList<String> values)
+  {
+    String label = getMessage(msgID);
+    if ((values == null) || values.isEmpty())
+    {
+      out.print(label);
+      out.println(":");
+    }
+    else
+    {
+      for (String value : values)
+      {
+        out.print(label);
+        out.print(":  ");
+        out.println(value);
+      }
+    }
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
index 52d9ae1..0db5653 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -695,6 +695,15 @@
 
 
   /**
+   * The OID for the password policy state extended operation (both the request
+   * and response types).
+   */
+  public static final String OID_PASSWORD_POLICY_STATE_EXTOP =
+       "1.3.6.1.4.1.26027.1.6.1";
+
+
+
+  /**
    * The request OID for the StartTLS extended operation.
    */
   public static final String OID_START_TLS_REQUEST = "1.3.6.1.4.1.1466.20037";
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/PasswordPolicyTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/PasswordPolicyTestCase.java
index 17f4068..6384d01 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/PasswordPolicyTestCase.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/PasswordPolicyTestCase.java
@@ -45,6 +45,7 @@
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.tools.LDAPModify;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.DN;
@@ -4742,6 +4743,85 @@
 
 
   /**
+   * Tests the ability of a user to bind to the server when their account
+   * includes the pwdReset operational attribute and last login time tracking is
+   * enabled.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testResetWithLastLoginTime()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: oldpassword",
+      "ds-privilege-name: bypass-acl"
+    );
+
+    try
+    {
+      TestCaseUtils.applyModifications(
+        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-force-change-on-reset",
+        "ds-cfg-force-change-on-reset: true",
+        "-",
+        "replace: ds-cfg-last-login-time-attribute",
+        "ds-cfg-last-login-time-attribute: ds-pwp-last-login-time",
+        "-",
+        "replace: ds-cfg-last-login-time-format",
+        "ds-cfg-last-login-time-format: yyyyMMdd",
+        "",
+        "dn: uid=test.user,o=test",
+        "changetype: modify",
+        "replace: userPassword",
+        "userPassword: newpassword");
+
+      String path = TestCaseUtils.createTempFile(
+        "dn: uid=test.user,o=test",
+        "changetype: modify",
+        "replace: userPassword",
+        "userPassword: newnewpassword"
+      );
+
+      String[] args =
+      {
+        "-h", "127.0.0.1",
+        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+        "-D", "uid=test.user,o=test",
+        "-w", "newpassword",
+        "-f", path
+      };
+
+      assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    }
+    finally
+    {
+      TestCaseUtils.applyModifications(
+        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
+        "changetype: modify",
+        "replace: ds-cfg-force-change-on-reset",
+        "ds-cfg-force-change-on-reset: false",
+        "-",
+        "replace: ds-cfg-last-login-time-attribute",
+        "-",
+        "replace: ds-cfg-last-login-time-format");
+    }
+  }
+
+
+
+  /**
    * Tests the <CODE>toString</CODE> methods with the default password policy.
    */
   @Test()
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/ManageAccountTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/ManageAccountTestCase.java
new file mode 100644
index 0000000..0ca38f2
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/tools/ManageAccountTestCase.java
@@ -0,0 +1,871 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools;
+
+
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.schema.GeneralizedTimeSyntax;
+
+import static org.testng.Assert.*;
+
+
+
+/**
+ * A set of test cases for the ManageAccount tool.
+ */
+public class ManageAccountTestCase
+       extends ToolsTestCase
+{
+  /**
+   * Ensures that the Directory Server is running before starting any of the
+   * tests.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void startServer()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+  }
+
+
+
+  /**
+   * Retrieves the names of all of all the subcommands available for use with
+   * the manage-account tool.
+   *
+   * @return  The names of all of the subcommands available for use with the
+   *          manage-account tool.
+   */
+  @DataProvider(name = "allSubCommands")
+  public Object[][] getAllSubCommands()
+  {
+    return new Object[][]
+    {
+      new Object[] { "get-all" },
+      new Object[] { "get-password-policy-dn" },
+      new Object[] { "get-account-is-disabled" },
+      new Object[] { "set-account-is-disabled" },
+      new Object[] { "clear-account-is-disabled" },
+      new Object[] { "get-account-expiration-time" },
+      new Object[] { "set-account-expiration-time" },
+      new Object[] { "clear-account-expiration-time" },
+      new Object[] { "get-seconds-until-account-expiration" },
+      new Object[] { "get-password-changed-time" },
+      new Object[] { "set-password-changed-time" },
+      new Object[] { "clear-password-changed-time" },
+      new Object[] { "get-password-expiration-warned-time" },
+      new Object[] { "set-password-expiration-warned-time" },
+      new Object[] { "clear-password-expiration-warned-time" },
+      new Object[] { "get-seconds-until-password-expiration" },
+      new Object[] { "get-seconds-until-password-expiration-warning" },
+      new Object[] { "get-authentication-failure-times" },
+      new Object[] { "add-authentication-failure-time" },
+      new Object[] { "set-authentication-failure-times" },
+      new Object[] { "clear-authentication-failure-times" },
+      new Object[] { "get-seconds-until-authentication-failure-unlock" },
+      new Object[] { "get-remaining-authentication-failure-count" },
+      new Object[] { "get-last-login-time" },
+      new Object[] { "set-last-login-time" },
+      new Object[] { "clear-last-login-time" },
+      new Object[] { "get-seconds-until-idle-lockout" },
+      new Object[] { "get-password-is-reset" },
+      new Object[] { "set-password-is-reset" },
+      new Object[] { "clear-password-is-reset" },
+      new Object[] { "get-seconds-until-password-reset-lockout" },
+      new Object[] { "get-grace-login-use-times" },
+      new Object[] { "add-grace-login-use-time" },
+      new Object[] { "set-grace-login-use-times" },
+      new Object[] { "clear-grace-login-use-times" },
+      new Object[] { "get-remaining-grace-login-count" },
+      new Object[] { "get-password-changed-by-required-time" },
+      new Object[] { "set-password-changed-by-required-time" },
+      new Object[] { "clear-password-changed-by-required-time" },
+      new Object[] { "get-seconds-until-required-change-time" }
+    };
+  }
+
+
+
+  /**
+   * Retrieves the names of all of the subcommands used for performing "get"
+   * operations.
+   *
+   * @return  The names of all of the subcommands used for performing "get"
+   *          operations.
+   */
+  @DataProvider(name = "getSubCommands")
+  public Object[][] getGetSubCommands()
+  {
+    return new Object[][]
+    {
+      new Object[] { "get-all" },
+      new Object[] { "get-password-policy-dn" },
+      new Object[] { "get-account-is-disabled" },
+      new Object[] { "get-account-expiration-time" },
+      new Object[] { "get-seconds-until-account-expiration" },
+      new Object[] { "get-password-changed-time" },
+      new Object[] { "get-password-expiration-warned-time" },
+      new Object[] { "get-seconds-until-password-expiration" },
+      new Object[] { "get-seconds-until-password-expiration-warning" },
+      new Object[] { "get-authentication-failure-times" },
+      new Object[] { "get-seconds-until-authentication-failure-unlock" },
+      new Object[] { "get-remaining-authentication-failure-count" },
+      new Object[] { "get-last-login-time" },
+      new Object[] { "get-seconds-until-idle-lockout" },
+      new Object[] { "get-password-is-reset" },
+      new Object[] { "get-seconds-until-password-reset-lockout" },
+      new Object[] { "get-grace-login-use-times" },
+      new Object[] { "get-remaining-grace-login-count" },
+      new Object[] { "get-password-changed-by-required-time" },
+      new Object[] { "get-seconds-until-required-change-time" }
+    };
+  }
+
+
+
+  /**
+   * Retrieves the names of the subcommands that may be used to set a Boolean
+   * value in the user's password policy state.
+   *
+   * @return  The names of all of the subcommands that may be used to set a
+   *          Boolean value in the user's password policy state.
+   */
+  @DataProvider(name = "setBooleanSubCommands")
+  public Object[][] getSetBooleanSubCommands()
+  {
+    return new Object[][]
+    {
+      new Object[] { "set-account-is-disabled" },
+      new Object[] { "set-password-is-reset" },
+    };
+  }
+
+
+
+  /**
+   * Retrieves the names of the subcommands that may be used to set time value
+   * in the user's password policy state.  This will also include the
+   * subcommands that may be used to add a value to a multivalued property.
+   *
+   * @return  The names of all of the subcommands that may be used to set a
+   *          time value in the user's password policy state.
+   */
+  @DataProvider(name = "setTimeSubCommands")
+  public Object[][] getSetTimeSubCommands()
+  {
+    return new Object[][]
+    {
+      new Object[] { "set-account-expiration-time" },
+      new Object[] { "set-password-changed-time" },
+      new Object[] { "set-password-expiration-warned-time" },
+      new Object[] { "set-authentication-failure-times" },
+      new Object[] { "add-authentication-failure-time" },
+      new Object[] { "set-last-login-time" },
+      new Object[] { "set-grace-login-use-times" },
+      new Object[] { "add-grace-login-use-time" },
+      new Object[] { "set-password-changed-by-required-time" },
+    };
+  }
+
+
+
+  /**
+   * Retrieves the names of all of all the subcommands that may be used to clear
+   * some part of the password policy state.
+   *
+   * @return  The names of all of the subcommands that may be used to clear some
+   *          part of the password policy state.
+   */
+  @DataProvider(name = "clearSubCommands")
+  public Object[][] clearAllSubCommands()
+  {
+    return new Object[][]
+    {
+      new Object[] { "clear-account-is-disabled" },
+      new Object[] { "clear-account-expiration-time" },
+      new Object[] { "clear-password-changed-time" },
+      new Object[] { "clear-password-expiration-warned-time" },
+      new Object[] { "clear-authentication-failure-times" },
+      new Object[] { "clear-last-login-time" },
+      new Object[] { "clear-password-is-reset" },
+      new Object[] { "clear-grace-login-use-times" },
+      new Object[] { "clear-password-changed-by-required-time" }
+    };
+  }
+
+
+
+  /**
+   * Tests the various sets of arguments that may be used to get usage
+   * information when no subcommand is given.
+   */
+  @Test()
+  public void testHelpNoSubCommand()
+  {
+    assertEquals(ManageAccount.main(new String[] { "-H" }, null, System.err),
+                 0);
+    assertEquals(ManageAccount.main(new String[] { "--help" }, null,
+                                    System.err),
+                 0);
+    assertEquals(ManageAccount.main(new String[] { "-?" }, null, System.err),
+                 0);
+  }
+
+
+
+  /**
+   * Tests the various sets of arguments that may be used to get usage
+   * information when a subcommand is given.
+   *
+   * @param  subCommand  The subcommand to use in the test.
+   */
+  @Test(dataProvider="allSubCommands")
+  public void testHelpWithSubCommand(String subCommand)
+  {
+    String[] args =
+    {
+      subCommand,
+      "--help"
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests the manage-account tool without any subcommand.
+   */
+  @Test()
+  public void testNoSubCommand()
+  {
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertFalse(ManageAccount.main(args, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the manage-account tool with an invalid subcommand.
+   */
+  @Test()
+  public void testInvalidSubCommand()
+  {
+    String[] args =
+    {
+      "invalid-subcommand",
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertFalse(ManageAccount.main(args, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests an attempt to use the manage-account tool as an anonymous user.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void testAnonymousUser()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      "get-all",
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "",
+      "-w", "",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertFalse(ManageAccount.main(args, null, System.err) == 0);
+  }
+
+
+
+  /**
+   * Tests an attempt to use the manage-account tool as an unprivileged user.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void testUnprivilegedUser()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      "get-all",
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "uid=test.user,o=test",
+      "-w", "password",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertFalse(ManageAccount.main(args, null, System.err) == 0);
+  }
+
+
+
+  /**
+   * Tests the ability to use the manage-account tool when using SSL.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void testUsingSSL()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      "get-all",
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapsPort()),
+      "-Z",
+      "-X",
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests the ability to use the manage-account tool when using StartTLS.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void testUsingStartTLS()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      "get-all",
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-q",
+      "-X",
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests the ability to use the manage-account tool when using SASL
+   * authentication.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void testUsingSASL()
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      "get-all",
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-o", "mech=PLAIN",
+      "-o", "authid=dn:cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "get" subcommands work without throwing
+   * exceptions or returning unexpected results.
+   *
+   * @param  subCommand  The name of the "get" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="getSubCommands")
+  public void testGetSubCommands(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "get" subcommands fail when provided with
+   * a value.
+   *
+   * @param  subCommand  The name of the "get" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="getSubCommands")
+  public void testGetSubCommandsWithValue(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+      "-O", "not-appropriate-for-this-subcommand"
+    };
+
+    assertFalse(ManageAccount.main(args, null, System.err) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "set" subcommands that take Boolean
+   * arguments work properly when given a value of "true".
+   *
+   * @param  subCommand  The name of the "set" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="setBooleanSubCommands")
+  public void testSetBooleanSubCommandsTrue(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+      "-O", "true"
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "set" subcommands that take Boolean
+   * arguments work properly when given a value of "false".
+   *
+   * @param  subCommand  The name of the "set" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="setBooleanSubCommands")
+  public void testSetBooleanSubCommandsFalse(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+      "-O", "false"
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "set" subcommands that take Boolean
+   * arguments work properly when given a non-Boolean value.
+   *
+   * @param  subCommand  The name of the "set" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="setBooleanSubCommands")
+  public void testSetBooleanSubCommandsNonBooleanValue(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+      "-O", "nonboolean"
+    };
+
+    assertFalse(ManageAccount.main(args, null, System.err) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "set" subcommands that take timestamp
+   * arguments work properly when used without any value.
+   *
+   * @param  subCommand  The name of the "set" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="setTimeSubCommands")
+  public void testSetTimeSubCommandsNoValue(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test"
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "set" subcommands that take timestamp
+   * arguments work properly when used with a value equal to the current time.
+   *
+   * @param  subCommand  The name of the "set" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="setTimeSubCommands")
+  public void testSetTimeSubCommandsCurrentTime(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+      "-O", GeneralizedTimeSyntax.format(System.currentTimeMillis())
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "set" subcommands that take timestamp
+   * arguments work properly when used with an invalid value.
+   *
+   * @param  subCommand  The name of the "set" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="setTimeSubCommands")
+  public void testSetTimeSubCommandsInvalidTime(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+      "-O", "invalid"
+    };
+
+    assertFalse(ManageAccount.main(args, null, System.err) == 0);
+  }
+
+
+
+  /**
+   * Tests to ensure that the various "clear" subcommands work without throwing
+   * exceptions or returning unexpected results.
+   *
+   * @param  subCommand  The name of the "clear" subcommand to invoke.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider="clearSubCommands")
+  public void testClearSubCommands(String subCommand)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    TestCaseUtils.addEntry(
+      "dn: uid=test.user,o=test",
+      "objectClass: top",
+      "objectClass: person",
+      "objectClass: organizationalPerson",
+      "objectClass: inetOrgPerson",
+      "uid: test.user",
+      "givenName: Test",
+      "sn: User",
+      "cn: Test User",
+      "userPassword: password"
+    );
+
+    String[] args =
+    {
+      subCommand,
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-b", "uid=test.user,o=test",
+    };
+
+    assertEquals(ManageAccount.main(args, null, System.err), 0);
+  }
+}
+

--
Gitblit v1.10.0