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

neil_a_wilson
12.25.2007 785fcca7ef16dd93aaa3ca22e17a812ab6ac250a
Update the password policy configuration to support a new attribute,
ds-cfg-state-update-failure-policy. This attribute makes it possible to
control how the server should handle failures that may occur when attempting to
update password policy state information during a bind operation. This
attribute allows the following values:

- ignore -- If an otherwise successful bind attempt encounters a failure when trying to update the password policy state information for a user, then log an error message but allow that bind to succeed.

- reactive -- If an otherwise successful bind attempt encounters a failure when trying to update the password policy state information for a user, then cause the bind to fail.

- proactive -- If the server can detect ahead of time that the password policy state update could fail (e.g., if the entire server or target backend is in read-only mode) and it is known that a successful or failed bind attempt would need to update the password policy state information, then reject the bind before any processing is performed. If it gets past this phase and the attempt to update the state information later fails, then it will have the same behavior as the "reactive" policy.

Note that bind attempts by root users will always be treated using the "ignore" policy to ensure that they are not locked out in the event of a significant problem (e.g., disk full).


OpenDS Issue Number: 1810
8 files modified
486 ■■■■■ changed files
opends/resource/config/config.ldif 2 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 7 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/PasswordPolicyConfiguration.xml 53 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicy.java 24 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicyState.java 21 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 17 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 76 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java 286 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1072,6 +1072,7 @@
ds-cfg-require-secure-authentication: false
ds-cfg-require-secure-password-changes: false
ds-cfg-skip-validation-for-administrators: false
ds-cfg-state-update-failure-policy: reactive
dn: cn=Root Password Policy,cn=Password Policies,cn=config
objectClass: top
@@ -1099,6 +1100,7 @@
ds-cfg-require-secure-authentication: false
ds-cfg-require-secure-password-changes: false
ds-cfg-skip-validation-for-administrators: false
ds-cfg-state-update-failure-policy: ignore
dn: cn=Password Storage Schemes,cn=config
objectClass: top
opends/resource/schema/02-config.ldif
@@ -1475,6 +1475,9 @@
  NAME 'ds-cfg-strip-syntax-minimum-upper-bound'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.443
  NAME 'ds-cfg-state-update-failure-policy'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1803,8 +1806,8 @@
  ds-cfg-password-validator-dn $ ds-cfg-previous-last-login-time-format $
  ds-cfg-require-change-by-time $ ds-cfg-require-secure-authentication $
  ds-cfg-require-secure-password-changes $
  ds-cfg-skip-validation-for-administrators )
  X-ORIGIN 'OpenDS Directory Server' )
  ds-cfg-skip-validation-for-administrators $
  ds-cfg-state-update-failure-policy ) X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.63 NAME
  'ds-cfg-jmx-connection-handler' SUP ds-cfg-connection-handler
  STRUCTURAL MUST ( ds-cfg-listen-port $ ds-cfg-ssl-cert-nickname $
opends/src/admin/defn/org/opends/server/admin/std/PasswordPolicyConfiguration.xml
@@ -944,4 +944,57 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="state-update-failure-policy" mandatory="false"
  multi-valued="false">
    <adm:synopsis>
      Specifies how the server should deal with the inability to update password
      policy state information during an authentication attempt.  In particular,
      it may be used to control whether an otherwise successful bind operation
      should fail if a failure occurs while attempting to update password policy
      state information (e.g., to clear a record of previous authentication
      failures or to update the last login time), or even whether to reject a
      bind request if it is known aheaed of time that it will not be possible to
      update the authentication failure times in the event of an unsuccessful
      bind attempt (e.g., if the backend writability mode is disabled).
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>reactive</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="ignore">
          <adm:synopsis>
            If a bind attempt would otherwise be successful, then do not reject
            it if a problem occurs while attempting to update the password
            policy state information for the user.
          </adm:synopsis>
        </adm:value>
        <adm:value name="reactive">
          <adm:synopsis>
            Even if a bind attempt would otherwise be successful, reject it if a
            problem occurs while attempting to update the password policy state
            information for the user.
          </adm:synopsis>
        </adm:value>
        <adm:value name="proactive">
          <adm:synopsis>
            Proactively reject any bind attempt if it is known ahead of time
            that it would not be possible to update the user's password policy
            state information.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.443</ldap:oid>
        <ldap:name>ds-cfg-state-update-failure-policy</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/server/org/opends/server/core/PasswordPolicy.java
@@ -39,6 +39,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
import org.opends.server.admin.std.server.PasswordPolicyCfg;
import org.opends.server.admin.std.server.PasswordValidatorCfg;
import org.opends.server.api.AccountStatusNotificationHandler;
@@ -215,6 +216,11 @@
  private CopyOnWriteArrayList<String> previousLastLoginTimeFormats =
       new CopyOnWriteArrayList<String>();
  // The state update failure policy.
  private PasswordPolicyCfgDefn.StateUpdateFailurePolicy
       stateUpdateFailurePolicy =
            PasswordPolicyCfgDefn.StateUpdateFailurePolicy.REACTIVE;
  /**
@@ -801,6 +807,11 @@
    // Get the idle lockout duration.
    this.idleLockoutInterval = (int) configuration.getIdleLockoutInterval();
    // Get the state update failure policy.
    this.stateUpdateFailurePolicy = configuration.getStateUpdateFailurePolicy();
    /*
     *  Holistic validation.
     */
@@ -1448,6 +1459,19 @@
  /**
   * Retrieves the state update failure policy for this password policy.
   *
   * @return  The state update failure policy for this password policy.
   */
  public PasswordPolicyCfgDefn.StateUpdateFailurePolicy
              getStateUpdateFailurePolicy()
  {
    return stateUpdateFailurePolicy;
  }
  /**
   * Retrieves a string representation of this password policy.
   *
   * @return  A string representation of this password policy.
opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -39,11 +39,13 @@
import java.util.List;
import java.util.Set;
import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
import org.opends.server.admin.std.server.PasswordValidatorCfg;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.api.PasswordGenerator;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.PasswordValidator;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.LDAPAttribute;
@@ -57,9 +59,12 @@
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.Operation;
@@ -68,9 +73,8 @@
import org.opends.server.util.TimeThread;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
@@ -4082,8 +4086,21 @@
      String message = getMessage(msgID, userDNString,
                            String.valueOf(internalModify.getErrorMessage()));
      // If this is a root user, or if the password policy says that we should
      // ignore these problems, then log a warning message.  Otherwise, cause
      // the bind to fail.
      if ((DirectoryServer.isRootDN(userEntry.getDN()) ||
          (passwordPolicy.getStateUpdateFailurePolicy() ==
           PasswordPolicyCfgDefn.StateUpdateFailurePolicy.IGNORE)))
      {
        logError(ErrorLogCategory.PASSWORD_POLICY,
                 ErrorLogSeverity.SEVERE_WARNING, message, msgID);
      }
      else
      {
      throw new DirectoryException(resultCode, message, msgID);
    }
  }
}
}
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6257,6 +6257,18 @@
  /**
   * The message ID for the message that will be used if a bind attempt is
   * rejected because either the entire server or the user's backend has a
   * writability mode of "disabled" and the server would not be able to update
   * the authentication failure count or the last login time.  This takes a
   * single argument, which is the target user DN.
   */
  public static final int MSGID_BIND_OPERATION_WRITABILITY_DISABLED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 628;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -8329,6 +8341,11 @@
    registerMessage(MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND,
                    "Rejecting a simple bind request for user %s because the " +
                    "password policy requires secure authentication");
    registerMessage(MSGID_BIND_OPERATION_WRITABILITY_DISABLED,
                    "Rejecting a bind request for user %s because either " +
                    "the entire server or the user's backend has a " +
                    "writability mode of 'disabled' and password policy " +
                    "state updates would not be allowed");
    registerMessage(MSGID_BIND_OPERATION_ACCOUNT_DISABLED,
                    "Rejecting a bind request for user %s because the " +
                    "account has been administrative disabled");
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -45,6 +45,7 @@
import java.util.Map;
import java.util.concurrent.locks.Lock;
import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
import org.opends.server.api.AttributeSyntax;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
@@ -108,13 +109,14 @@
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Operation;
import org.opends.server.types.Privilege;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.types.Operation;
import org.opends.server.types.WritabilityMode;
import org.opends.server.util.Validator;
import org.opends.server.workflowelement.LeafWorkflowElement;
@@ -3313,8 +3315,8 @@
            // Check to see if the user has a password.  If not, then fail.
            // FIXME -- We need to have a way to enable/disable debugging.
            pwPolicyState = new PasswordPolicyState(userEntry, false, false);
            AttributeType pwType
                 = pwPolicyState.getPolicy().getPasswordAttribute();
            PasswordPolicy policy = pwPolicyState.getPolicy();
            AttributeType  pwType = policy.getPasswordAttribute();
            List<Attribute> pwAttr = userEntry.getAttribute(pwType);
            if ((pwAttr == null) || (pwAttr.isEmpty()))
@@ -3328,9 +3330,35 @@
            }
            // If the password policy is configured to track authentication
            // failures or keep the last login time and the associated backend
            // is disabled, then we may need to reject the bind immediately.
            if ((policy.getStateUpdateFailurePolicy() ==
                 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
                ((policy.getLockoutFailureCount() > 0) ||
                 ((policy.getLastLoginTimeAttribute() != null) &&
                  (policy.getLastLoginTimeFormat() != null))) &&
                ((DirectoryServer.getWritabilityMode() ==
                  WritabilityMode.DISABLED) ||
                 (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
            {
              // This policy isn't applicable to root users, so if it's a root
              // user then ignore it.
              if (! DirectoryServer.isRootDN(bindDN))
              {
                int    msgID   = MSGID_BIND_OPERATION_WRITABILITY_DISABLED;
                String message = getMessage(msgID, String.valueOf(bindDN));
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                localOp.setAuthFailureReason(msgID, message);
                break bindProcessing;
              }
            }
            // Check to see if the authentication must be done in a secure
            // manner.  If so, then the client connection must be secure.
            if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
            if (policy.requireSecureAuthentication() &&
                (! clientConnection.isSecure()))
            {
              int    msgID   = MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND;
@@ -3429,8 +3457,7 @@
                pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
              }
              int maxGraceLogins
                   = pwPolicyState.getPolicy().getGraceLoginCount();
              int maxGraceLogins = policy.getGraceLoginCount();
              if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
              {
                List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
@@ -3731,7 +3758,7 @@
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              localOp.setAuthFailureReason(msgID, message);
              if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
              if (policy.getLockoutFailureCount() > 0)
              {
                pwPolicyState.updateAuthFailureTimes();
                if (pwPolicyState.lockedDueToFailures())
@@ -3895,7 +3922,33 @@
          // regardless of whether the authentication was successful.
          if (pwPolicyState != null)
          {
            if (pwPolicyState.isDisabled())
            PasswordPolicy policy = pwPolicyState.getPolicy();
            // If the password policy is configured to track authentication
            // failures or keep the last login time and the associated backend
            // is disabled, then we may need to reject the bind immediately.
            if ((policy.getStateUpdateFailurePolicy() ==
                 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.PROACTIVE) &&
                ((policy.getLockoutFailureCount() > 0) ||
                 ((policy.getLastLoginTimeAttribute() != null) &&
                  (policy.getLastLoginTimeFormat() != null))) &&
                ((DirectoryServer.getWritabilityMode() ==
                  WritabilityMode.DISABLED) ||
                 (backend.getWritabilityMode() == WritabilityMode.DISABLED)))
            {
              // This policy isn't applicable to root users, so if it's a root
              // user then ignore it.
              if (! DirectoryServer.isRootDN(bindDN))
              {
                int    msgID   = MSGID_BIND_OPERATION_WRITABILITY_DISABLED;
                String message = getMessage(msgID, userDNString);
                localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
                localOp.setAuthFailureReason(msgID, message);
                break bindProcessing;
              }
            }
            else if (pwPolicyState.isDisabled())
            {
              localOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
@@ -3919,7 +3972,7 @@
              break bindProcessing;
            }
            if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
            if (policy.requireSecureAuthentication() &&
                (! clientConnection.isSecure()) &&
                (! saslHandler.isSecure(saslMechanism)))
            {
@@ -3996,8 +4049,7 @@
                  pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
                }
                int maxGraceLogins
                     = pwPolicyState.getPolicy().getGraceLoginCount();
                int maxGraceLogins = policy.getGraceLoginCount();
                if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
                {
                  List<Long> graceLoginTimes =
@@ -5472,7 +5524,7 @@
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation modify plugins.
        // Invoke the pre-operation add plugins.
        if (!localOp.isSynchronizationOperation())
        {
          PreOperationPluginResult preOpResult =
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BindOperationTestCase.java
@@ -51,6 +51,7 @@
import org.opends.server.protocols.ldap.BindResponseProtocolOp;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.tools.LDAPSearch;
import org.opends.server.types.Attribute;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.AuthenticationType;
@@ -1904,7 +1905,7 @@
   * cause the connection to no longer be associated with the previous identity.
   * This helps provide coverage for issue #1392.
   *
   * @throws  Exception
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRebindClearsAuthInfo()
@@ -1962,5 +1963,288 @@
    s.close();
  }
  /**
   * Tests to ensure that the "ignore" password policy state update policy
   * works as expected.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testIgnoreStateUpdateFailurePolicy()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.applyModifications(
      "dn: uid=test.user,o=test",
      "changetype: add",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
      "changetype: modify",
      "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",
      "-",
      "replace: ds-cfg-state-update-failure-policy",
      "ds-cfg-state-update-failure-policy: ignore",
      "",
      "dn: cn=config",
      "changetype: modify",
      "replace: ds-cfg-writability-mode",
      "ds-cfg-writability-mode: disabled"
    );
    try
    {
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "uid=test.user,o=test",
        "-w", "password",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
                   0);
      args = new String[]
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.applyModifications(
        "dn: cn=config",
        "changetype: modify",
        "replace: ds-cfg-writability-mode",
        "ds-cfg-writability-mode: enabled",
        "",
        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
        "changetype: modify",
        "replace: ds-cfg-last-login-time-attribute",
        "-",
        "replace: ds-cfg-last-login-time-format",
        "-",
        "replace: ds-cfg-state-update-failure-policy",
        "ds-cfg-state-update-failure-policy: reactive"
      );
    }
  }
  /**
   * Tests to ensure that the "reactive" password policy state update policy
   * works as expected.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testReactiveStateUpdateFailurePolicy()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.applyModifications(
      "dn: uid=test.user,o=test",
      "changetype: add",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
      "changetype: modify",
      "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",
      "-",
      "replace: ds-cfg-state-update-failure-policy",
      "ds-cfg-state-update-failure-policy: reactive",
      "",
      "dn: cn=config",
      "changetype: modify",
      "replace: ds-cfg-writability-mode",
      "ds-cfg-writability-mode: disabled"
    );
    try
    {
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "uid=test.user,o=test",
        "-w", "password",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      int rc = LDAPSearch.mainSearch(args, false, System.out, System.err);
      assertFalse(rc == 0);
      assertFalse(rc == LDAPResultCode.INVALID_CREDENTIALS);
      args = new String[]
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.applyModifications(
        "dn: cn=config",
        "changetype: modify",
        "replace: ds-cfg-writability-mode",
        "ds-cfg-writability-mode: enabled",
        "",
        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
        "changetype: modify",
        "replace: ds-cfg-last-login-time-attribute",
        "-",
        "replace: ds-cfg-last-login-time-format",
        "-",
        "replace: ds-cfg-state-update-failure-policy",
        "ds-cfg-state-update-failure-policy: reactive"
      );
    }
  }
  /**
   * Tests to ensure that the "proactive" password policy state update policy
   * works as expected.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testProactiveStateUpdateFailurePolicy()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    TestCaseUtils.applyModifications(
      "dn: uid=test.user,o=test",
      "changetype: add",
      "objectClass: top",
      "objectClass: person",
      "objectClass: organizationalPerson",
      "objectClass: inetOrgPerson",
      "uid: test.user",
      "givenName: Test",
      "sn: User",
      "cn: Test User",
      "userPassword: password",
      "",
      "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
      "changetype: modify",
      "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",
      "-",
      "replace: ds-cfg-state-update-failure-policy",
      "ds-cfg-state-update-failure-policy: proactive",
      "",
      "dn: cn=config",
      "changetype: modify",
      "replace: ds-cfg-writability-mode",
      "ds-cfg-writability-mode: disabled"
    );
    try
    {
      String[] args =
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "uid=test.user,o=test",
        "-w", "password",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
                   LDAPResultCode.INVALID_CREDENTIALS);
      args = new String[]
      {
        "-h", "127.0.0.1",
        "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
        "-D", "cn=Directory Manager",
        "-w", "password",
        "-b", "",
        "-s", "base",
        "(objectClass=*)"
      };
      assertEquals(LDAPSearch.mainSearch(args, false, System.out, System.err),
                   0);
    }
    finally
    {
      TestCaseUtils.applyModifications(
        "dn: cn=config",
        "changetype: modify",
        "replace: ds-cfg-writability-mode",
        "ds-cfg-writability-mode: enabled",
        "",
        "dn: cn=Default Password Policy,cn=Password Policies,cn=config",
        "changetype: modify",
        "replace: ds-cfg-last-login-time-attribute",
        "-",
        "replace: ds-cfg-last-login-time-format",
        "-",
        "replace: ds-cfg-state-update-failure-policy",
        "ds-cfg-state-update-failure-policy: reactive"
      );
    }
  }
}