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

neil_a_wilson
14.33.2006 abf2b98003c52deb2319cfd64a6e280531e84324
Make a few changes in the area of account status notifications:

- Update the account status notification type structure to include the DN of
the associated user.

- Update the bind processing code to generate account status notifications for
the following conditions:
* Bind failed due to an expired account
* Bind failed due to a reset-locked account
* Bind failed due to an idle-locked account
* Bind failed due to an expired password
* The first time a password expiration warning is generated
* If the bind failure count limit is reached and the account becomes locked

- An error log account status notification handler has been added, which can
write messages to the error log when an account status notification is
generated.
1 files added
12 files modified
904 ■■■■■ changed files
opends/resource/config/config.ldif 20 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 11 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/AccountStatusNotificationHandler.java 6 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigConstants.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/BindOperation.java 188 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicyState.java 26 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandler.java 414 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 41 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 61 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/UtilityMessages.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/LDAPConnection.java 3 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AccountStatusNotification.java 28 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AccountStatusNotificationType.java 95 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -55,6 +55,26 @@
objectClass: ds-cfg-branch
cn: Account Status Notification Handlers
dn: cn=Error Log Handler,cn=Account Status Notification Handlers,cn=config
objectClass: top
objectClass: ds-cfg-account-status-notification-handler
objectClass: ds-cfg-error-log-account-status-notification-handler
cn: Error Log Handler
ds-cfg-account-status-notification-handler-class: org.opends.server.extensions.ErrorLogAccountStatusNotificationHandler
ds-cfg-account-status-notification-handler-enabled: true
ds-cfg-account-status-notification-type: account-temporarily-locked
ds-cfg-account-status-notification-type: account-permanently-locked
ds-cfg-account-status-notification-type: account-unlocked
ds-cfg-account-status-notification-type: account-idle-locked
ds-cfg-account-status-notification-type: account-reset-locked
ds-cfg-account-status-notification-type: account-disabled
ds-cfg-account-status-notification-type: account-enabled
ds-cfg-account-status-notification-type: account-expired
ds-cfg-account-status-notification-type: password-expired
ds-cfg-account-status-notification-type: password-expiring
ds-cfg-account-status-notification-type: password-reset
ds-cfg-account-status-notification-type: password-changed
dn: cn=Alert Handlers,cn=config
objectClass: top
objectClass: ds-cfg-branch
opends/resource/schema/02-config.ldif
@@ -967,6 +967,9 @@
  NAME 'ds-cfg-account-status-notification-handler-enabled'
  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.283
  NAME 'ds-cfg-account-status-notification-type'
  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 )
@@ -1322,7 +1325,13 @@
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.75
  NAME 'ds-cfg-account-status-notification-handler' SUP top STRUCTURAL
  MUST ( ds-cfg-account-status-notification-handler-class $
  MUST ( cn $ ds-cfg-account-status-notification-handler-class $
  ds-cfg-account-status-notification-handler-enabled )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.76
  NAME 'ds-cfg-error-log-account-status-notification-handler'
  SUP ds-cfg-account-status-notification-handler STRUCTURAL
  MUST ds-cfg-account-status-notification-type
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/server/org/opends/server/api/AccountStatusNotificationHandler.java
@@ -33,6 +33,7 @@
import org.opends.server.core.InitializationException;
import org.opends.server.types.AccountStatusNotification;
import org.opends.server.types.AccountStatusNotificationType;
import org.opends.server.types.DN;
import static org.opends.server.loggers.Debug.*;
@@ -99,6 +100,8 @@
   *
   * @param  notificationType  The type for this account status
   *                           notification.
   * @param  userDN            The DN of the user entry to which this
   *                           notification applies.
   * @param  messageID         The unique ID for this notification.
   * @param  message           The human-readable message for this
   *                           notification.
@@ -106,7 +109,7 @@
  public abstract void
       handleStatusNotification(
            AccountStatusNotificationType notificationType,
            int messageID, String message);
            DN userDN, int messageID, String message);
@@ -124,6 +127,7 @@
                      String.valueOf(notification));
    handleStatusNotification(notification.getNotificationType(),
                             notification.getUserDN(),
                             notification.getMessageID(),
                             notification.getMessage());
  }
opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -124,6 +124,15 @@
  /**
   * The name of the configuration attribute that specifies the set of account
   * status notification types that should trigger notifications.
   */
  public static final String ATTR_ACCT_NOTIFICATION_TYPE =
       NAME_PREFIX_CFG + "account-status-notification-type";
  /**
   * The name of the configuration attribute that indicates whether to
   * automatically add missing RDN attributes or to return an error response to
   * the client.
opends/src/server/org/opends/server/core/BindOperation.java
@@ -46,6 +46,7 @@
import org.opends.server.controls.PasswordPolicyResponseControl;
import org.opends.server.controls.PasswordPolicyWarningType;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.AccountStatusNotificationType;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
@@ -128,6 +129,9 @@
  // The bind DN used for this bind operation.
  private DN bindDN;
  // The DN of the user entry that is attempting to authenticate.
  private DN userEntryDN;
  // The DN of the user as whom a SASL authentication was attempted (regardless
  // of whether the authentication was successful) for the purpose of updating
  // password policy state information.
@@ -205,6 +209,7 @@
    this.saslCredentials = null;
    bindDN                   = null;
    userEntryDN              = null;
    responseControls         = new ArrayList<Control>(0);
    authFailureID            = 0;
    authFailureReason        = null;
@@ -261,6 +266,7 @@
    this.simplePassword  = null;
    bindDN            = null;
    userEntryDN       = null;
    responseControls  = new ArrayList<Control>(0);
    authFailureID     = 0;
    authFailureReason = null;
@@ -324,6 +330,7 @@
    pwPolicyErrorType        = null;
    pwPolicyWarningType      = null;
    pwPolicyWarningValue     = -1;
    userEntryDN              = null;
  }
@@ -379,6 +386,7 @@
    authFailureID     = 0;
    authFailureReason = null;
    saslAuthUserEntry = null;
    userEntryDN       = null;
  }
@@ -701,6 +709,24 @@
  /**
   * Retrieves the user entry DN for this bind operation.  It will only be
   * available if the bind processing has proceeded far enough to identify the
   * user attempting to authenticate or if the user DN could not be determined.
   *
   * @return  The user entry DN for this bind operation, or <CODE>null</CODE> if
   *          the bind processing has not progressed far enough to identify the
   *          user or if the user DN could not be determined.
   */
  public DN getUserEntryDN()
  {
    assert debugEnter(CLASS_NAME, "getUserEntryDN");
    return userEntryDN;
  }
  /**
   * Retrieves the time that processing started for this operation.
   *
   * @return  The time that processing started for this operation.
@@ -1122,6 +1148,10 @@
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            else
            {
              userEntryDN = userEntry.getDN();
            }
            // Check to see if the user has a password.  If not, then fail.
@@ -1172,6 +1202,11 @@
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
                   message);
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToFailures())
@@ -1200,6 +1235,11 @@
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
                   msgID, message);
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToIdleInterval())
@@ -1214,6 +1254,11 @@
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
                   msgID, message);
              break bindProcessing;
            }
@@ -1252,6 +1297,11 @@
                  setResultCode(ResultCode.INVALID_CREDENTIALS);
                  setAuthFailureReason(msgID, message);
                  pwPolicyState.generateAccountStatusNotification(
                       AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
                       msgID, message);
                  break bindProcessing;
                }
              }
@@ -1262,17 +1312,28 @@
                setResultCode(ResultCode.INVALID_CREDENTIALS);
                setAuthFailureReason(msgID, message);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
                     msgID, message);
                break bindProcessing;
              }
            }
            else if (pwPolicyState.shouldWarn())
            {
              int numSeconds = pwPolicyState.getSecondsUntilExpiration();
              String timeToExpiration = secondsToTimeString(numSeconds);
              int msgID = MSGID_BIND_PASSWORD_EXPIRING;
              String message = getMessage(msgID, timeToExpiration);
              appendErrorMessage(message);
              if (pwPolicyWarningType == null)
              {
                pwPolicyWarningType =
                     PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
                pwPolicyWarningValue =
                     pwPolicyState.getSecondsUntilExpiration();
                pwPolicyWarningValue = numSeconds;
              }
              isFirstWarning = pwPolicyState.isFirstWarning();
@@ -1418,6 +1479,16 @@
              if (isFirstWarning)
              {
                pwPolicyState.setWarnedTime();
                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
                String timeToExpiration = secondsToTimeString(numSeconds);
                int msgID = MSGID_BIND_PASSWORD_EXPIRING;
                String message = getMessage(msgID, timeToExpiration);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN,
                     msgID, message);
              }
              if (isGraceLogin)
@@ -1439,10 +1510,32 @@
              if (maxAllowedFailures > 0)
              {
                pwPolicyState.updateAuthFailureTimes();
                if (pwPolicyState.getAuthFailureTimes().size() >
                if (pwPolicyState.getAuthFailureTimes().size() >=
                    maxAllowedFailures)
                {
                  pwPolicyState.lockDueToFailures();
                  AccountStatusNotificationType notificationType;
                  int lockoutDuration = pwPolicyState.getLockoutDuration();
                  if (lockoutDuration > 0)
                  {
                    notificationType = AccountStatusNotificationType.
                                            ACCOUNT_TEMPORARILY_LOCKED;
                    msgID   = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED;
                    message = getMessage(msgID,
                                         secondsToTimeString(lockoutDuration));
                  }
                  else
                  {
                    notificationType = AccountStatusNotificationType.
                                            ACCOUNT_PERMANENTLY_LOCKED;
                    msgID   = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED;
                    message = getMessage(msgID);
                  }
                  pwPolicyState.generateAccountStatusNotification(
                       notificationType, userEntryDN, msgID, message);
                }
              }
            }
@@ -1532,7 +1625,8 @@
              // FIXME -- Need to have a way to enable debugging.
              pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false,
                                                      false);
              userDNString = String.valueOf(saslAuthUserEntry.getDN());
              userEntryDN = saslAuthUserEntry.getDN();
              userDNString = String.valueOf(userEntryDN);
            }
            catch (DirectoryException de)
            {
@@ -1560,8 +1654,14 @@
            {
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              int msgID = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
              appendErrorMessage(getMessage(msgID, userDNString));
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
              String message = getMessage(msgID, userDNString);
              appendErrorMessage(message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
                   message);
              break bindProcessing;
            }
@@ -1600,8 +1700,14 @@
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              int msgID = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
              appendErrorMessage(getMessage(msgID, userDNString));
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
              String message = getMessage(msgID, userDNString);
              appendErrorMessage(message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
                   msgID, message);
              break bindProcessing;
            }
@@ -1617,8 +1723,14 @@
                  pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
                }
                int msgID = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
                appendErrorMessage(getMessage(msgID, userDNString));
                int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
                String message = getMessage(msgID, userDNString);
                appendErrorMessage(message);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
                     msgID, message);
                break bindProcessing;
              }
@@ -1655,6 +1767,11 @@
                    setResultCode(ResultCode.INVALID_CREDENTIALS);
                    setAuthFailureReason(msgID, message);
                    pwPolicyState.generateAccountStatusNotification(
                         AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
                         msgID, message);
                    break bindProcessing;
                  }
                }
@@ -1665,17 +1782,28 @@
                  setResultCode(ResultCode.INVALID_CREDENTIALS);
                  setAuthFailureReason(msgID, message);
                  pwPolicyState.generateAccountStatusNotification(
                       AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
                       msgID, message);
                  break bindProcessing;
                }
              }
              else if (pwPolicyState.shouldWarn())
              {
                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
                String timeToExpiration = secondsToTimeString(numSeconds);
                int msgID = MSGID_BIND_PASSWORD_EXPIRING;
                String message = getMessage(msgID, timeToExpiration);
                appendErrorMessage(message);
                if (pwPolicyWarningType == null)
                {
                  pwPolicyWarningType =
                       PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
                  pwPolicyWarningValue =
                       pwPolicyState.getSecondsUntilExpiration();
                  pwPolicyWarningValue = numSeconds;
                }
                isFirstWarning = pwPolicyState.isFirstWarning();
@@ -1701,6 +1829,16 @@
              if (isFirstWarning)
              {
                pwPolicyState.setWarnedTime();
                int numSeconds = pwPolicyState.getSecondsUntilExpiration();
                String timeToExpiration = secondsToTimeString(numSeconds);
                int msgID = MSGID_BIND_PASSWORD_EXPIRING;
                String message = getMessage(msgID, timeToExpiration);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.PASSWORD_EXPIRING, bindDN,
                     msgID, message);
              }
              if (isGraceLogin)
@@ -1809,10 +1947,34 @@
                if (maxAllowedFailures > 0)
                {
                  pwPolicyState.updateAuthFailureTimes();
                  if (pwPolicyState.getAuthFailureTimes().size() >
                  if (pwPolicyState.getAuthFailureTimes().size() >=
                      maxAllowedFailures)
                  {
                    pwPolicyState.lockDueToFailures();
                    AccountStatusNotificationType notificationType;
                    int msgID;
                    String message;
                    int lockoutDuration = pwPolicyState.getLockoutDuration();
                    if (lockoutDuration > 0)
                    {
                      notificationType = AccountStatusNotificationType.
                                              ACCOUNT_TEMPORARILY_LOCKED;
                      msgID   = MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED;
                      message = getMessage(msgID,
                                     secondsToTimeString(lockoutDuration));
                    }
                    else
                    {
                      notificationType = AccountStatusNotificationType.
                                              ACCOUNT_PERMANENTLY_LOCKED;
                      msgID   = MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED;
                      message = getMessage(msgID);
                    }
                    pwPolicyState.generateAccountStatusNotification(
                         notificationType, userEntryDN, msgID, message);
                  }
                }
              }
opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -1770,6 +1770,23 @@
  /**
   * Retrieves the length of time in seconds that a user's account will be
   * locked due to failed attempts before it will be automatically unlocked.
   *
   * @return  The length of time in seconds that a user's account will be
   *          locked due to failed attempts before it will be automatically
   *          unlocked, or zero if accounts will not be automatically unlocked.
   */
  public int getLockoutDuration()
  {
    assert debugEnter(CLASS_NAME, "getLockoutDuration");
    return passwordPolicy.getLockoutDuration();
  }
  /**
   * Retrieves the length of time in seconds until the user's account is
   * automatically unlocked.  This should only be called after calling
   * <CODE>lockedDueToFailures</CODE>.
@@ -4122,15 +4139,17 @@
   * Generates an account status notification for this user.
   *
   * @param  notificationType  The type for the account status notification.
   * @param  userDN            The DN of the user entry to which this
   *                           notification applies.
   * @param  messageID         The unique ID for the notification.
   * @param  message           The human-readable message for the notification.
   */
  public void generateAccountStatusNotification(
                   AccountStatusNotificationType notificationType,
                   int messageID, String message)
                   DN userDN, int messageID, String message)
  {
    assert debugEnter(CLASS_NAME, "generateAccountStatusNotification",
                      String.valueOf(notificationType),
                      String.valueOf(notificationType), String.valueOf(userDN),
                      String.valueOf(messageID), String.valueOf(message));
@@ -4143,7 +4162,8 @@
    for (AccountStatusNotificationHandler handler : handlers)
    {
      handler.handleStatusNotification(notificationType, messageID, message);
      handler.handleStatusNotification(notificationType, userDN, messageID,
                                       message);
    }
  }
opends/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandler.java
New file
@@ -0,0 +1,414 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.config.ConfigAttribute;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.config.MultiChoiceConfigAttribute;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.InitializationException;
import org.opends.server.types.AccountStatusNotificationType;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.ResultCode;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an account status notification handler that will write
 * information about status notifications using the Directory Server's error
 * logging facility.
 */
public class ErrorLogAccountStatusNotificationHandler
       extends AccountStatusNotificationHandler
       implements ConfigurableComponent
{
  /**
   * The fully-qualified name of this class for debugging purposes.
   */
  private static final String CLASS_NAME =
       "org.opends.server.extensions.ErrorLogAccountStatusNotificationHandler";
  /**
   * The set of names for the account status notification types that may be
   * logged by this notification handler.
   */
  private static final HashSet<String> NOTIFICATION_TYPE_NAMES =
       new HashSet<String>();
  // The DN of the configuration entry for this notification handler.
  private DN configEntryDN;
  // The set of notification types that should generate log messages.
  private HashSet<AccountStatusNotificationType> notificationTypes;
  static
  {
    for (AccountStatusNotificationType t :
         AccountStatusNotificationType.values())
    {
      NOTIFICATION_TYPE_NAMES.add(t.getNotificationTypeName());
    }
  }
  /**
   * Initializes this account status notification handler based on the
   * information in the provided configuration entry.
   *
   * @param  configEntry  The configuration entry that contains the information
   *                      to use to initialize this account status notification
   *                      handler.
   *
   * @throws  ConfigException  If the provided entry does not contain a valid
   *                           configuration for this account status
   *                           notification handler.
   *
   * @throws  InitializationException  If a problem occurs during initialization
   *                                   that is not related to the server
   *                                   configuration.
   */
  public void initializeStatusNotificationHandler(ConfigEntry configEntry)
       throws ConfigException, InitializationException
  {
    assert debugEnter(CLASS_NAME, "initializeStatusNotificationHandler",
                      String.valueOf(configEntry));
    configEntryDN = configEntry.getDN();
    // Initialize the set of notification types that should generate log
    // messages.
    int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES;
    MultiChoiceConfigAttribute typesStub =
         new MultiChoiceConfigAttribute(ATTR_ACCT_NOTIFICATION_TYPE,
                                        getMessage(msgID), true, true, false,
                                        NOTIFICATION_TYPE_NAMES);
    try
    {
      MultiChoiceConfigAttribute typesAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(typesStub);
      notificationTypes = new HashSet<AccountStatusNotificationType>();
      for (String s : typesAttr.activeValues())
      {
        AccountStatusNotificationType t =
             AccountStatusNotificationType.typeForName(s);
        if (t == null)
        {
          msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE;
          String message = getMessage(msgID, String.valueOf(configEntryDN), s);
          throw new ConfigException(msgID, message);
        }
        else
        {
          notificationTypes.add(t);
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeStatusNotificationHandler",
                            e);
      msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
    DirectoryServer.registerConfigurableComponent(this);
    DirectoryServer.registerAccountStatusNotificationHandler(configEntryDN,
                                                             this);
  }
  /**
   * Performs any processing that may be necessary in conjunction with the
   * provided account status notification type.
   *
   * @param  notificationType  The type for this account status notification.
   * @param  userDN            The DN of the user entry to which this
   *                           notification applies.
   * @param  messageID         The unique ID for this notification.
   * @param  message           The human-readable message for this notification.
   */
  public void handleStatusNotification(AccountStatusNotificationType
                                            notificationType,
                                       DN userDN, int messageID, String message)
  {
    assert debugEnter(CLASS_NAME, "handleStatusNotification",
                      String.valueOf(notificationType), String.valueOf(userDN),
                      String.valueOf(messageID), String.valueOf(message));
    if (notificationTypes.contains(notificationType))
    {
      int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_NOTIFICATION;
      logError(ErrorLogCategory.PASSWORD_POLICY, ErrorLogSeverity.NOTICE,
               msgID, notificationType.getNotificationTypeName(),
               String.valueOf(userDN), messageID, message);
    }
  }
  /**
   * Retrieves the DN of the configuration entry with which this component is
   * associated.
   *
   * @return  The DN of the configuration entry with which this component is
   *          associated.
   */
  public DN getConfigurableComponentEntryDN()
  {
    assert debugEnter(CLASS_NAME, "getConfigurableComponentEntryDN");
    return configEntryDN;
  }
  /**
   * Retrieves the set of configuration attributes that are associated with this
   * configurable component.
   *
   * @return  The set of configuration attributes that are associated with this
   *          configurable component.
   */
  public List<ConfigAttribute> getConfigurationAttributes()
  {
    assert debugEnter(CLASS_NAME, "getConfigurationAttributes");
    LinkedList<ConfigAttribute> attrList = new LinkedList<ConfigAttribute>();
    LinkedList<String> typeNames = new LinkedList<String>();
    for (AccountStatusNotificationType t : notificationTypes)
    {
      typeNames.add(t.getNotificationTypeName());
    }
    int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES;
    attrList.add(new MultiChoiceConfigAttribute(ATTR_ACCT_NOTIFICATION_TYPE,
                                                getMessage(msgID), true, true,
                                                false, NOTIFICATION_TYPE_NAMES,
                                                typeNames));
    return attrList;
  }
  /**
   * Indicates whether the provided configuration entry has an
   * acceptable configuration for this component.  If it does not,
   * then detailed information about the problem(s) should be added to
   * the provided list.
   *
   * @param  configEntry          The configuration entry for which to
   *                              make the determination.
   * @param  unacceptableReasons  A list that can be used to hold
   *                              messages about why the provided
   *                              entry does not have an acceptable
   *                              configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an
   *          acceptable configuration for this component, or
   *          <CODE>false</CODE> if not.
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                      List<String> unacceptableReasons)
  {
    assert debugEnter(CLASS_NAME, "hasAcceptableConfiguration",
                      String.valueOf(configEntry), "List<String>");
    // Initialize the set of notification types that should generate log
    // messages.
    int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES;
    MultiChoiceConfigAttribute typesStub =
         new MultiChoiceConfigAttribute(ATTR_ACCT_NOTIFICATION_TYPE,
                                        getMessage(msgID), true, true, false,
                                        NOTIFICATION_TYPE_NAMES);
    try
    {
      MultiChoiceConfigAttribute typesAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(typesStub);
      HashSet<AccountStatusNotificationType> types =
           new HashSet<AccountStatusNotificationType>();
      for (String s : typesAttr.activeValues())
      {
        AccountStatusNotificationType t =
             AccountStatusNotificationType.typeForName(s);
        if (t == null)
        {
          msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE;
          String message = getMessage(msgID, String.valueOf(configEntryDN), s);
          unacceptableReasons.add(message);
          return false;
        }
        else
        {
          types.add(t);
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "hasAcceptableConfiguration", e);
      msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES;
      String message = getMessage(msgID, String.valueOf(configEntryDN),
                                  stackTraceToSingleLineString(e));
      unacceptableReasons.add(message);
      return false;
    }
    // If we've gotten here, then everything is OK.
    return true;
  }
  /**
   * Makes a best-effort attempt to apply the configuration contained
   * in the provided entry.  Information about the result of this
   * processing should be added to the provided message list.
   * Information should always be added to this list if a
   * configuration change could not be applied.  If detailed results
   * are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not
   * changed) should also be included.
   *
   * @param  configEntry      The entry containing the new
   *                          configuration to apply for this
   *                          component.
   * @param  detailedResults  Indicates whether detailed information
   *                          about the processing should be added to
   *                          the list.
   *
   * @return  Information about the result of the configuration
   *          update.
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
  {
    assert debugEnter(CLASS_NAME, "applyNewConfiguration",
                      String.valueOf(configEntry),
                      String.valueOf(detailedResults));
    ResultCode resultCode = ResultCode.SUCCESS;
    boolean adminActionRequired = false;
    ArrayList<String> messages = new ArrayList<String>();
    // Initialize the set of notification types that should generate log
    // messages.
    HashSet<AccountStatusNotificationType> types =
         new HashSet<AccountStatusNotificationType>();
    int msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES;
    MultiChoiceConfigAttribute typesStub =
         new MultiChoiceConfigAttribute(ATTR_ACCT_NOTIFICATION_TYPE,
                                        getMessage(msgID), true, true, false,
                                        NOTIFICATION_TYPE_NAMES);
    try
    {
      MultiChoiceConfigAttribute typesAttr =
           (MultiChoiceConfigAttribute)
           configEntry.getConfigAttribute(typesStub);
      for (String s : typesAttr.activeValues())
      {
        AccountStatusNotificationType t =
             AccountStatusNotificationType.typeForName(s);
        if (t == null)
        {
          resultCode = ResultCode.UNWILLING_TO_PERFORM;
          msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE;
          messages.add(getMessage(msgID, String.valueOf(configEntryDN), s));
        }
        else
        {
          types.add(t);
        }
      }
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "hasAcceptableConfiguration", e);
      resultCode = DirectoryServer.getServerErrorResultCode();
      msgID = MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES;
      messages.add(getMessage(msgID, String.valueOf(configEntryDN),
                              stackTraceToSingleLineString(e)));
    }
    if (resultCode == ResultCode.SUCCESS)
    {
      this.notificationTypes = types;
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -5725,6 +5725,37 @@
  /**
   * The message ID for the message that will be used if the user's password is
   * about to expire.  This takes a single argument, which is the length of time
   * until the password expires.
   */
  public static final int MSGID_BIND_PASSWORD_EXPIRING =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_WARNING | 547;
  /**
   * The message ID for the message that will be used if the user's account
   * becomes temporarily locked due to too many failed attempts.  This takes a
   * single argument, which is a string representation of the length of time
   * until the account is unlocked.
   */
  public static final int MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 548;
  /**
   * The message ID for the message that will be used if the user's account
   * becomes permanently locked due to too many failed attempts.  This does not
   * take any arguments.
   */
  public static final int MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 549;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -6513,9 +6544,19 @@
                    "The user-specific time limit value %s contained in " +
                    "user entry %s could not be parsed as an integer.  The " +
                    "default server time limit will be used.");
    registerMessage(MSGID_BIND_PASSWORD_EXPIRING,
                    "The user password is about to expire (time to " +
                    "expiration:  %s).");
    registerMessage(MSGID_BIND_OPERATION_WRONG_PASSWORD,
                    "The password provided by the user did not match any " +
                    "password(s) stored in the user's entry.");
    registerMessage(MSGID_BIND_ACCOUNT_TEMPORARILY_LOCKED,
                    "The account has been locked as a result of too many " +
                    "failed authentication attempts (time to unlock:  %s).");
    registerMessage(MSGID_BIND_ACCOUNT_PERMANENTLY_LOCKED,
                    "The account has been locked as a result of too many " +
                    "failed authentication attempts.  It may only be " +
                    "unlocked by an administrator.");
    registerMessage(MSGID_BIND_OPERATION_PASSWORD_VALIDATION_EXCEPTION,
                    "An unexpected error occurred while attempting to " +
                    "validate the provided password:  %s.");
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -3928,6 +3928,50 @@
  /**
   * The message ID for the message that will be used as the description for the
   * notification types configuration attribute.  This does not take any
   * arguments.
   */
  public static final int
       MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES =
            CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 372;
  /**
   * The message ID for the message that will be used if an invalid notification
   * type is specified.  This takes two arguments, which are the DN of the
   * configuration entry and the invalid notification type.
   */
  public static final int MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 373;
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to determine the account status notification types.  This takes
   * two arguments, which are the DN of the configuration entry and a string
   * representation of the exception that occurred.
   */
  public static final int
       MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES =
            CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 374;
  /**
   * The message ID for the message that will be written to the error log
   * whenever an account status notification is generated.  This takes four
   * arguments, which are the name of the account status notification type, the
   * user DN, the message ID, and the message string.
   */
  public static final int MSGID_ERRORLOG_ACCTNOTHANDLER_NOTIFICATION =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_NOTICE | 375;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -5729,6 +5773,23 @@
                    "value for configuration attribute " +
                    ATTR_PASSWORD_FORMAT + ", which is used to specify the " +
                    "format for the generated passwords:  %s.");
    registerMessage(
         MSGID_ERRORLOG_ACCTNOTHANDLER_DESCRIPTION_NOTIFICATION_TYPES,
         "Specifies the status notification types for which log messages " +
         "should be generated.  It is a multivalued attribute, and changes " +
         "will take effect immediately.");
    registerMessage(MSGID_ERRORLOG_ACCTNOTHANDLER_INVALID_TYPE,
                    "Configuration entry %s contains unrecognized account " +
                    "status notification type %s.");
    registerMessage(MSGID_ERRORLOG_ACCTNOTHANDLER_CANNOT_GET_NOTIFICATION_TYPES,
                    "An error occurred while attempting to determine " +
                    "the account status notification types from " +
                    "configuration entry %s:  %s.");
    registerMessage(MSGID_ERRORLOG_ACCTNOTHANDLER_NOTIFICATION,
                    "Account-Status-Notification type='%s' userdn='%s' " +
                    "id=%d msg='%s'");
  }
}
opends/src/server/org/opends/server/messages/UtilityMessages.java
@@ -2004,7 +2004,7 @@
    registerMessage(MSGID_ACCTNOTTYPE_PASSWORD_EXPIRING,
                    "password-expiring");
    registerMessage(MSGID_ACCTNOTTYPE_PASSWORD_RESET,
                    "password-reset-by-administrator");
                    "password-reset");
    registerMessage(MSGID_ACCTNOTTYPE_PASSWORD_CHANGED,
                    "password-changed");
  }
opends/src/server/org/opends/server/tools/LDAPConnection.java
@@ -47,7 +47,8 @@
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.CoreMessages.
                   MSGID_RESULT_CLIENT_SIDE_CONNECT_ERROR;
import static org.opends.server.messages.ToolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
opends/src/server/org/opends/server/types/AccountStatusNotification.java
@@ -49,6 +49,9 @@
  // The notification type for this account status notification.
  private AccountStatusNotificationType notificationType;
  // The DN of the user entry to which this notification applies.
  private DN userDN;
  // The message ID for the account status notification message.
  private int messageID;
@@ -64,19 +67,22 @@
   *
   * @param  notificationType  The type for this account status
   *                           notification.
   * @param  userDN            The DN of the user entry to which
   *                           this notification applies.
   * @param  messageID         The unique ID for this notification.
   * @param  message           The human-readable message for this
   *                           notification.
   */
  public AccountStatusNotification(
              AccountStatusNotificationType notificationType,
              int messageID, String message)
              DN userDN, int messageID, String message)
  {
    assert debugEnter(CLASS_NAME, String.valueOf(notificationType),
                      String.valueOf(messageID),
                      String.valueOf(message));
    this.notificationType = notificationType;
    this.userDN           = userDN;
    this.messageID        = messageID;
    this.message          = message;
  }
@@ -100,6 +106,22 @@
  /**
   * Retrieves the DN of the user entry to which this notification
   * applies.
   *
   * @return  The DN of the user entry to which this notification
   *          applies.
   */
  public DN getUserDN()
  {
    assert debugEnter(CLASS_NAME, "getUserDN");
    return userDN;
  }
  /**
   * Retrieves the message ID for the account status notification
   * message.
   *
@@ -143,8 +165,8 @@
    assert debugEnter(CLASS_NAME, "toString");
    return "AccountStatusNotification(type=" +
           String.valueOf(notificationType) + ",id=" + messageID +
           ",message=" + message + ")";
           notificationType.getNotificationTypeName() + ",dn=" +
           userDN + ",id=" + messageID + ",message=" + message + ")";
  }
}
opends/src/server/org/opends/server/types/AccountStatusNotificationType.java
@@ -215,6 +215,89 @@
  /**
   * Retrieves the account status notification type with the specified
   * name.
   *
   * @param  name  The name for the account status notification type
   *               to retrieve.
   *
   * @return  The requested account status notification type, or
   *          <CODE>null</CODE> if there is no type with the given
   *          name.
   */
  public static AccountStatusNotificationType typeForName(String name)
  {
    String lowerName = toLowerCase(name);
    if (lowerName.equals(getMessage(
             MSGID_ACCTNOTTYPE_ACCOUNT_TEMPORARILY_LOCKED)))
    {
      return ACCOUNT_TEMPORARILY_LOCKED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_ACCOUNT_PERMANENTLY_LOCKED)))
    {
      return ACCOUNT_PERMANENTLY_LOCKED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_ACCOUNT_UNLOCKED)))
    {
      return ACCOUNT_UNLOCKED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_ACCOUNT_IDLE_LOCKED)))
    {
      return ACCOUNT_IDLE_LOCKED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_ACCOUNT_RESET_LOCKED)))
    {
      return ACCOUNT_RESET_LOCKED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_ACCOUNT_DISABLED)))
    {
      return ACCOUNT_DISABLED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_ACCOUNT_ENABLED)))
    {
      return ACCOUNT_ENABLED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_ACCOUNT_EXPIRED)))
    {
      return ACCOUNT_EXPIRED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_PASSWORD_EXPIRED)))
    {
      return PASSWORD_EXPIRED;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_PASSWORD_EXPIRING)))
    {
      return PASSWORD_EXPIRING;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_PASSWORD_RESET)))
    {
      return PASSWORD_RESET;
    }
    else if (lowerName.equals(getMessage(
                  MSGID_ACCTNOTTYPE_PASSWORD_CHANGED)))
    {
      return PASSWORD_CHANGED;
    }
    else
    {
      return null;
    }
  }
  /**
   * Retrieves the notification type identifier for this account
   * status notification type.
   *
@@ -229,6 +312,18 @@
  /**
   * Retrieves the name for this account status notification type.
   *
   * @return  The name for this account status notification type.
   */
  public String getNotificationTypeName()
  {
    return getMessage(notificationTypeID);
  }
  /**
   * Retrieves a string representation of this account status
   * notification type.
   *