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

jarnou
03.29.2007 fbda6e0892dcfcc8dd43d21f6fb134aabb8d0cac
opends/src/server/org/opends/server/core/BindOperation.java
@@ -26,414 +26,33 @@
 */
package org.opends.server.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
import org.opends.server.api.plugin.PreParsePluginResult;
import org.opends.server.controls.AuthorizationIdentityResponseControl;
import org.opends.server.controls.PasswordExpiredControl;
import org.opends.server.controls.PasswordExpiringControl;
import org.opends.server.controls.PasswordPolicyErrorType;
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;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.AuthenticationType;
import org.opends.server.types.ByteString;
import org.opends.server.types.CancelRequest;
import org.opends.server.types.CancelResult;
import org.opends.server.types.Control;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DisconnectReason;
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.LockManager;
import org.opends.server.types.Operation;
import org.opends.server.types.OperationType;
import org.opends.server.types.ResultCode;
import org.opends.server.types.operation.PostOperationBindOperation;
import org.opends.server.types.operation.PostResponseBindOperation;
import org.opends.server.types.operation.PreOperationBindOperation;
import org.opends.server.types.operation.PreParseBindOperation;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.core.CoreConstants.*;
import static org.opends.server.loggers.AccessLogger.*;
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.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation that may be used to authenticate a user to
 * the Directory Server.  Note that for security restrictions, response messages
 * that may be returned to the client must be carefully cleaned to ensure that
 * they do not provide a malicious client with information that may be useful in
 * an attack.  This does impact the debugability of the server, but that can
 * be addressed by calling the <CODE>setAuthFailureReason</CODE> method, which
 * can provide a reason for a failure in a form that will not be returned to the
 * client but may be written to a log file.
 * This interface defines an operation that may be used to authenticate a user
 * to the Directory Server.  Note that for security restrictions, response
 * messages that may be returned to the client must be carefully cleaned to
 * ensure that they do not provide a malicious client with information that may
 * be useful in an attack.  This does impact the debugability of the server,
 * but that can be addressed by calling the <CODE>setAuthFailureReason</CODE>
 * method, which can provide a reason for a failure in a form that will not be
 * returned to the client but may be written to a log file.
 */
public class BindOperation
             extends Operation
             implements PreParseBindOperation, PreOperationBindOperation,
                        PostOperationBindOperation, PostResponseBindOperation
public interface BindOperation extends Operation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  // The credentials used for SASL authentication.
  private ASN1OctetString saslCredentials;
  // The server SASL credentials provided to the client in the response.
  private ASN1OctetString serverSASLCredentials;
  // The authentication info for this bind operation.
  private AuthenticationInfo authInfo;
  // The authentication type used for this bind operation.
  private AuthenticationType authType;
  // Indicates whether the warning notification that should be sent to the user
  // would be the first warning.
  private boolean isFirstWarning;
  // Indicates whether the authentication should use a grace login if it is
  // successful.
  private boolean isGraceLogin;
  // Indicates whether the user's password must be changed before any other
  // operations will be allowed.
  private boolean mustChangePassword;
  // Indicates whether the client included the password policy control in the
  // bind request.
  private boolean pwPolicyControlRequested;
  // The raw, unprocessed bind DN as contained in the client request.
  private ByteString rawBindDN;
  // The password used for simple authentication.
  private ByteString simplePassword;
  // 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 entry of the user that successfully authenticated during processing of
  // this bind operation.
  private Entry authenticatedUserEntry;
  // 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.
  private Entry saslAuthUserEntry;
  // The unique ID associated with the failure reason message.
  private int authFailureID;
  // The password policy warning value that should be included in the response
  // control.
  private int pwPolicyWarningValue;
  // The set of response controls for this bind operation.
  private List<Control> responseControls;
  // The time that processing started on this operation.
  private long processingStartTime;
  // The time that processing ended on this operation.
  private long processingStopTime;
  // The password policy error type that should be included in the response
  // control
  private PasswordPolicyErrorType pwPolicyErrorType;
  // The password policy warning type that should be included in the response
  // control
  private PasswordPolicyWarningType pwPolicyWarningType;
  // The password policy state information for this bind operation.
  private PasswordPolicyState pwPolicyState;
  // A message explaining the reason for the authentication failure.
  private String authFailureReason;
  // A string representation of the protocol version for this bind operation.
  private String protocolVersion;
  // The SASL mechanism used for SASL authentication.
  private String saslMechanism;
  /**
   * Creates a new simple bind operation with the provided information.
   *
   * @param  clientConnection  The client connection with which this operation
   *                           is associated.
   * @param  operationID       The operation ID for this operation.
   * @param  messageID         The message ID of the request with which this
   *                           operation is associated.
   * @param  requestControls   The set of controls included in the request.
   * @param  protocolVersion   The string representation of the protocol version
   *                           associated with this bind request.
   * @param  rawBindDN         The raw, unprocessed bind DN as provided in the
   *                           request from the client.
   * @param  simplePassword    The password to use for the simple
   *                           authentication.
   */
  public BindOperation(ClientConnection clientConnection, long operationID,
                       int messageID, List<Control> requestControls,
                       String protocolVersion, ByteString rawBindDN,
                       ByteString simplePassword)
  {
    super(clientConnection, operationID, messageID, requestControls);
    this.protocolVersion = protocolVersion;
    this.authType        = AuthenticationType.SIMPLE;
    this.saslMechanism   = null;
    this.saslCredentials = null;
    if (rawBindDN == null)
    {
      this.rawBindDN = new ASN1OctetString();
    }
    else
    {
      this.rawBindDN = rawBindDN;
    }
    if (simplePassword == null)
    {
      this.simplePassword = new ASN1OctetString();
    }
    else
    {
      this.simplePassword = simplePassword;
    }
    bindDN                   = null;
    userEntryDN              = null;
    responseControls         = new ArrayList<Control>(0);
    authFailureID            = 0;
    authFailureReason        = null;
    authenticatedUserEntry   = null;
    saslAuthUserEntry        = null;
    isFirstWarning           = false;
    isGraceLogin             = false;
    mustChangePassword       = false;
    pwPolicyControlRequested = false;
    pwPolicyErrorType        = null;
    pwPolicyWarningType      = null;
    pwPolicyWarningValue     = -1;
  }
  /**
   * Creates a new SASL bind operation with the provided information.
   *
   * @param  clientConnection  The client connection with which this operation
   *                           is associated.
   * @param  operationID       The operation ID for this operation.
   * @param  messageID         The message ID of the request with which this
   *                           operation is associated.
   * @param  requestControls   The set of controls included in the request.
   * @param  protocolVersion   The string representation of the protocol version
   *                           associated with this bind request.
   * @param  rawBindDN         The raw, unprocessed bind DN as provided in the
   *                           request from the client.
   * @param  saslMechanism     The SASL mechanism included in the request.
   * @param  saslCredentials   The optional SASL credentials included in the
   *                           request.
   */
  public BindOperation(ClientConnection clientConnection, long operationID,
                       int messageID, List<Control> requestControls,
                       String protocolVersion, ByteString rawBindDN,
                       String saslMechanism, ASN1OctetString saslCredentials)
  {
    super(clientConnection, operationID, messageID, requestControls);
    this.protocolVersion = protocolVersion;
    this.authType        = AuthenticationType.SASL;
    this.saslMechanism   = saslMechanism;
    this.saslCredentials = saslCredentials;
    this.simplePassword  = null;
    if (rawBindDN == null)
    {
      this.rawBindDN = new ASN1OctetString();
    }
    else
    {
      this.rawBindDN = rawBindDN;
    }
    bindDN                 = null;
    userEntryDN            = null;
    responseControls       = new ArrayList<Control>(0);
    authFailureID          = 0;
    authFailureReason      = null;
    authenticatedUserEntry = null;
    saslAuthUserEntry      = null;
  }
  /**
   * Creates a new simple bind operation with the provided information.
   *
   * @param  clientConnection  The client connection with which this operation
   *                           is associated.
   * @param  operationID       The operation ID for this operation.
   * @param  messageID         The message ID of the request with which this
   *                           operation is associated.
   * @param  requestControls   The set of controls included in the request.
   * @param  protocolVersion   The string representation of the protocol version
   *                           associated with this bind request.
   * @param  bindDN            The bind DN for this bind operation.
   * @param  simplePassword    The password to use for the simple
   *                           authentication.
   */
  public BindOperation(ClientConnection clientConnection, long operationID,
                       int messageID, List<Control> requestControls,
                       String protocolVersion, DN bindDN,
                       ByteString simplePassword)
  {
    super(clientConnection, operationID, messageID, requestControls);
    this.protocolVersion = protocolVersion;
    this.authType        = AuthenticationType.SIMPLE;
    this.bindDN          = bindDN;
    this.saslMechanism   = null;
    this.saslCredentials = null;
    if (bindDN == null)
    {
      rawBindDN = new ASN1OctetString();
    }
    else
    {
      rawBindDN = new ASN1OctetString(bindDN.toString());
    }
    if (simplePassword == null)
    {
      this.simplePassword = new ASN1OctetString();
    }
    else
    {
      this.simplePassword = simplePassword;
    }
    responseControls         = new ArrayList<Control>(0);
    authFailureID            = 0;
    authFailureReason        = null;
    authenticatedUserEntry   = null;
    saslAuthUserEntry        = null;
    isFirstWarning           = false;
    isGraceLogin             = false;
    mustChangePassword       = false;
    pwPolicyControlRequested = false;
    pwPolicyErrorType        = null;
    pwPolicyWarningType      = null;
    pwPolicyWarningValue     = -1;
    userEntryDN              = null;
  }
  /**
   * Creates a new SASL bind operation with the provided information.
   *
   * @param  clientConnection  The client connection with which this operation
   *                           is associated.
   * @param  operationID       The operation ID for this operation.
   * @param  messageID         The message ID of the request with which this
   *                           operation is associated.
   * @param  requestControls   The set of controls included in the request.
   * @param  protocolVersion   The string representation of the protocol version
   *                           associated with this bind request.
   * @param  bindDN            The bind DN for this bind operation.
   * @param  saslMechanism     The SASL mechanism included in the request.
   * @param  saslCredentials   The optional SASL credentials included in the
   *                           request.
   */
  public BindOperation(ClientConnection clientConnection, long operationID,
                       int messageID, List<Control> requestControls,
                       String protocolVersion, DN bindDN,
                       String saslMechanism, ASN1OctetString saslCredentials)
  {
    super(clientConnection, operationID, messageID, requestControls);
    this.protocolVersion = protocolVersion;
    this.authType        = AuthenticationType.SASL;
    this.bindDN          = bindDN;
    this.saslMechanism   = saslMechanism;
    this.saslCredentials = saslCredentials;
    this.simplePassword  = null;
    if (bindDN == null)
    {
      rawBindDN = new ASN1OctetString();
    }
    else
    {
      rawBindDN = new ASN1OctetString(bindDN.toString());
    }
    responseControls       = new ArrayList<Control>(0);
    authFailureID          = 0;
    authFailureReason      = null;
    authenticatedUserEntry = null;
    saslAuthUserEntry      = null;
    userEntryDN            = null;
  }
  /**
   * Retrieves the authentication type for this bind operation.
   *
   * @return  The authentication type for this bind operation.
   */
  public final AuthenticationType getAuthenticationType()
  {
    return authType;
  }
  public abstract AuthenticationType getAuthenticationType();
  /**
   * Retrieves the raw, unprocessed bind DN for this bind operation as contained
@@ -443,12 +62,7 @@
   * @return  The raw, unprocessed bind DN for this bind operation as contained
   *          in the client request.
   */
  public final ByteString getRawBindDN()
  {
    return rawBindDN;
  }
  public abstract ByteString getRawBindDN();
  /**
   * Specifies the raw, unprocessed bind DN for this bind operation.  This
@@ -456,21 +70,7 @@
   *
   * @param  rawBindDN  The raw, unprocessed bind DN for this bind operation.
   */
  public final void setRawBindDN(ByteString rawBindDN)
  {
    if (rawBindDN == null)
    {
      this.rawBindDN = new ASN1OctetString();
    }
    else
    {
      this.rawBindDN = rawBindDN;
    }
    bindDN = null;
  }
  public abstract void setRawBindDN(ByteString rawBindDN);
  /**
   * Retrieves a string representation of the protocol version associated with
@@ -479,12 +79,7 @@
   * @return  A string representation of the protocol version associated with
   *          this bind request.
   */
  public String getProtocolVersion()
  {
    return protocolVersion;
  }
  public String getProtocolVersion();
  /**
   * Specifies the string representation of the protocol version associated with
@@ -493,12 +88,7 @@
   * @param  protocolVersion  The string representation of the protocol version
   *                          associated with this bind request.
   */
  public void setProtocolVersion(String protocolVersion)
  {
    this.protocolVersion = protocolVersion;
  }
  public void setProtocolVersion(String protocolVersion);
  /**
   * Retrieves the bind DN for this bind operation.  This method should not be
@@ -509,24 +99,14 @@
   * @return  The bind DN for this bind operation, or <CODE>null</CODE> if the
   *          raw DN has not yet been processed.
   */
  public final DN getBindDN()
  {
    return bindDN;
  }
  public abstract DN getBindDN();
  /**
   * Retrieves the simple authentication password for this bind operation.
   *
   * @return  The simple authentication password for this bind operation.
   */
  public final ByteString getSimplePassword()
  {
    return simplePassword;
  }
  public abstract ByteString getSimplePassword();
  /**
   * Specifies the simple authentication password for this bind operation.
@@ -534,23 +114,7 @@
   * @param  simplePassword  The simple authentication password for this bind
   *                         operation.
   */
  public final void setSimplePassword(ByteString simplePassword)
  {
    if (simplePassword == null)
    {
      this.simplePassword = new ASN1OctetString();
    }
    else
    {
      this.simplePassword = simplePassword;
    }
    authType        = AuthenticationType.SIMPLE;
    saslMechanism   = null;
    saslCredentials = null;
  }
  public abstract void setSimplePassword(ByteString simplePassword);
  /**
   * Retrieves the SASL mechanism for this bind operation.
@@ -558,12 +122,7 @@
   * @return  The SASL mechanism for this bind operation, or <CODE>null</CODE>
   *          if the bind does not use SASL authentication.
   */
  public final String getSASLMechanism()
  {
    return  saslMechanism;
  }
  public abstract String getSASLMechanism();
  /**
   * Retrieves the SASL credentials for this bind operation.
@@ -571,12 +130,7 @@
   * @return  The SASL credentials for this bind operation, or <CODE>null</CODE>
   *          if there are none or if the bind does not use SASL authentication.
   */
  public final ASN1OctetString getSASLCredentials()
  {
    return saslCredentials;
  }
  public abstract ASN1OctetString getSASLCredentials();
  /**
   * Specifies the SASL credentials for this bind operation.
@@ -585,17 +139,8 @@
   * @param  saslCredentials  The SASL credentials for this bind operation, or
   *                          <CODE>null</CODE> if there are none.
   */
  public final void setSASLCredentials(String saslMechanism,
                                       ASN1OctetString saslCredentials)
  {
    this.saslMechanism   = saslMechanism;
    this.saslCredentials = saslCredentials;
    authType       = AuthenticationType.SASL;
    simplePassword = null;
  }
  public abstract void setSASLCredentials(String saslMechanism,
      ASN1OctetString saslCredentials);
  /**
   * Retrieves the set of server SASL credentials to include in the bind
@@ -604,12 +149,7 @@
   * @return  The set of server SASL credentials to include in the bind
   *          response, or <CODE>null</CODE> if there are none.
   */
  public final ASN1OctetString getServerSASLCredentials()
  {
    return serverSASLCredentials;
  }
  public abstract ASN1OctetString getServerSASLCredentials();
  /**
   * Specifies the set of server SASL credentials to include in the bind
@@ -618,13 +158,8 @@
   * @param  serverSASLCredentials  The set of server SASL credentials to
   *                                include in the bind response.
   */
  public final void setServerSASLCredentials(ASN1OctetString
                                                  serverSASLCredentials)
  {
    this.serverSASLCredentials = serverSASLCredentials;
  }
  public abstract void setServerSASLCredentials(
      ASN1OctetString serverSASLCredentials);
  /**
   * Retrieves the user entry associated with the SASL authentication attempt.
@@ -636,12 +171,7 @@
   *          <CODE>null</CODE> if it was not a SASL authentication or the SASL
   *          processing was not able to map the request to a user.
   */
  public final Entry getSASLAuthUserEntry()
  {
    return saslAuthUserEntry;
  }
  public abstract Entry getSASLAuthUserEntry();
  /**
   * Specifies the user entry associated with the SASL authentication attempt.
@@ -652,12 +182,7 @@
   * @param  saslAuthUserEntry  The user entry associated with the SASL
   *                            authentication attempt.
   */
  public final void setSASLAuthUserEntry(Entry saslAuthUserEntry)
  {
    this.saslAuthUserEntry = saslAuthUserEntry;
  }
  public abstract void setSASLAuthUserEntry(Entry saslAuthUserEntry);
  /**
   * Retrieves a human-readable message providing the reason that the
@@ -666,12 +191,7 @@
   * @return  A human-readable message providing the reason that the
   *          authentication failed, or <CODE>null</CODE> if none is available.
   */
  public final String getAuthFailureReason()
  {
    return authFailureReason;
  }
  public abstract String getAuthFailureReason();
  /**
   * Retrieves the unique identifier for the authentication failure reason, if
@@ -680,12 +200,7 @@
   * @return  The unique identifier for the authentication failure reason, or
   *          zero if none is available.
   */
  public final int getAuthFailureID()
  {
    return authFailureID;
  }
  public abstract int getAuthFailureID();
  /**
   * Specifies the reason that the authentication failed.
@@ -695,21 +210,7 @@
   * @param  reason  A human-readable message providing the reason that the
   *                 authentication failed.
   */
  public final void setAuthFailureReason(int id, String reason)
  {
    if (id < 0)
    {
      authFailureID = 0;
    }
    else
    {
      authFailureID = id;
    }
    authFailureReason = reason;
  }
  public abstract void setAuthFailureReason(int id, String reason);
  /**
   * Retrieves the user entry DN for this bind operation.  It will only be
@@ -720,12 +221,7 @@
   *          the bind processing has not progressed far enough to identify the
   *          user or if the user DN could not be determined.
   */
  public final DN getUserEntryDN()
  {
    return userEntryDN;
  }
  public abstract DN getUserEntryDN();
  /**
   * Retrieves the authentication info that resulted from processing this bind
@@ -734,12 +230,7 @@
   * @return  The authentication info that resulted from processing this bind
   *          operation.
   */
  public final AuthenticationInfo getAuthenticationInfo()
  {
    return authInfo;
  }
  public abstract AuthenticationInfo getAuthenticationInfo();
  /**
   * Specifies the authentication info that resulted from processing this bind
@@ -749,1674 +240,17 @@
   * @param  authInfo  The authentication info that resulted from processing
   *                   this bind operation.
   */
  public final void setAuthenticationInfo(AuthenticationInfo authInfo)
  {
    this.authInfo = authInfo;
  }
  public abstract void setAuthenticationInfo(AuthenticationInfo authInfo);
  /**
   * {@inheritDoc}
   * Set the user entry DN for this bind operation.
   *
   * @param  userEntryDN  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.
   */
  @Override()
  public final long getProcessingStartTime()
  {
    return processingStartTime;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final long getProcessingStopTime()
  {
    return processingStopTime;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final long getProcessingTime()
  {
    return (processingStopTime - processingStartTime);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final OperationType getOperationType()
  {
    // Note that no debugging will be done in this method because it is a likely
    // candidate for being called by the logging subsystem.
    return OperationType.BIND;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void disconnectClient(DisconnectReason disconnectReason,
                                     boolean sendNotification, String message,
                                     int messageID)
  {
    // Since bind operations can't be cancelled, we don't need to do anything
    // but forward the request on to the client connection.
    clientConnection.disconnect(disconnectReason, sendNotification, message,
                                messageID);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final String[][] getRequestLogElements()
  {
    // Note that no debugging will be done in this method because it is a likely
    // candidate for being called by the logging subsystem.
    if (authType == AuthenticationType.SASL)
    {
      return new String[][]
      {
        new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) },
        new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() },
        new String[] { LOG_ELEMENT_SASL_MECHANISM, saslMechanism }
      };
    }
    else
    {
      return new String[][]
      {
        new String[] { LOG_ELEMENT_BIND_DN, String.valueOf(rawBindDN) },
        new String[] { LOG_ELEMENT_AUTH_TYPE, authType.toString() }
      };
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final String[][] getResponseLogElements()
  {
    // Note that no debugging will be done in this method because it is a likely
    // candidate for being called by the logging subsystem.
    String resultCode = String.valueOf(getResultCode().getIntValue());
    String errorMessage;
    StringBuilder errorMessageBuffer = getErrorMessage();
    if (errorMessageBuffer == null)
    {
      errorMessage = null;
    }
    else
    {
      errorMessage = errorMessageBuffer.toString();
    }
    String matchedDNStr;
    DN matchedDN = getMatchedDN();
    if (matchedDN == null)
    {
      matchedDNStr = null;
    }
    else
    {
      matchedDNStr = matchedDN.toString();
    }
    String referrals;
    List<String> referralURLs = getReferralURLs();
    if ((referralURLs == null) || referralURLs.isEmpty())
    {
      referrals = null;
    }
    else
    {
      StringBuilder buffer = new StringBuilder();
      Iterator<String> iterator = referralURLs.iterator();
      buffer.append(iterator.next());
      while (iterator.hasNext())
      {
        buffer.append(", ");
        buffer.append(iterator.next());
      }
      referrals = buffer.toString();
    }
    String processingTime =
         String.valueOf(processingStopTime - processingStartTime);
    return new String[][]
    {
      new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
      new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
      new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
      new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
      new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
    };
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final List<Control> getResponseControls()
  {
    return responseControls;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void addResponseControl(Control control)
  {
    responseControls.add(control);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void removeResponseControl(Control control)
  {
    responseControls.remove(control);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void run()
  {
    // Start the processing timer and initially set the result to indicate that
    // the result is unknown.
    processingStartTime = System.currentTimeMillis();
    setResultCode(ResultCode.UNDEFINED);
    boolean returnAuthzID    = false;
    int     sizeLimit        = DirectoryServer.getSizeLimit();
    int     timeLimit        = DirectoryServer.getTimeLimit();
    int     lookthroughLimit = DirectoryServer.getLookthroughLimit();
    // Set a flag to indicate that a bind operation is in progress.  This should
    // ensure that no new operations will be accepted for this client until the
    // bind is complete.
    clientConnection.setBindInProgress(true);
    // Wipe out any existing authentication for the client connection and create
    // a placeholder that will be used if the bind is successful.
    clientConnection.setUnauthenticated();
    authInfo = null;
    // Abandon any operations that may be in progress for the client.
    String cancelReason = getMessage(MSGID_CANCELED_BY_BIND_REQUEST);
    CancelRequest cancelRequest = new CancelRequest(true, cancelReason);
    clientConnection.cancelAllOperationsExcept(cancelRequest, getMessageID());
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    boolean skipPostOperation = false;
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
bindProcessing:
    {
      // Invoke the pre-parse bind plugins.
      PreParsePluginResult preParseResult =
           pluginConfigManager.invokePreParseBindPlugins(this);
      if (preParseResult.connectionTerminated())
      {
        // There's no point in continuing with anything.  Log the request and
        // result and return.
        setResultCode(ResultCode.CANCELED);
        int msgID = MSGID_CANCELED_BY_PREPARSE_DISCONNECT;
        appendErrorMessage(getMessage(msgID));
        processingStopTime = System.currentTimeMillis();
        logBindRequest(this);
        logBindResponse(this);
        pluginConfigManager.invokePostResponseBindPlugins(this);
        return;
      }
      else if (preParseResult.sendResponseImmediately())
      {
        skipPostOperation = true;
        logBindRequest(this);
        break bindProcessing;
      }
      else if (preParseResult.skipCoreProcessing())
      {
        skipPostOperation = false;
        break bindProcessing;
      }
      // Log the bind request message.
      logBindRequest(this);
      // Process the bind DN to convert it from the raw form as provided by the
      // client into the form required for the rest of the bind processing.
      try
      {
        if (bindDN == null)
        {
          bindDN = DN.decode(rawBindDN);
        }
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResultCode(ResultCode.INVALID_CREDENTIALS);
        setAuthFailureReason(de.getMessageID(), de.getErrorMessage());
        break bindProcessing;
      }
      // Check to see if the client has permission to perform the
      // bind.
      // FIXME: for now assume that this will check all permission
      // pertinent to the operation. This includes any controls
      // specified.
      if (AccessControlConfigManager.getInstance()
          .getAccessControlHandler().isAllowed(this) == false) {
        setResultCode(ResultCode.INVALID_CREDENTIALS);
        int    msgID   = MSGID_BIND_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS;
        String message = getMessage(msgID, String.valueOf(bindDN));
        setAuthFailureReason(msgID, message);
        skipPostOperation = true;
        break bindProcessing;
      }
      // Check to see if there are any controls in the request.  If so, then see
      // if there is any special processing required.
      List<Control> requestControls = getRequestControls();
      if ((requestControls != null) && (! requestControls.isEmpty()))
      {
        for (int i=0; i < requestControls.size(); i++)
        {
          Control c   = requestControls.get(i);
          String  oid = c.getOID();
          if (oid.equals(OID_AUTHZID_REQUEST))
          {
            returnAuthzID = true;
          }
          else if (oid.equals(OID_PASSWORD_POLICY_CONTROL))
          {
            pwPolicyControlRequested = true;
          }
          // NYI -- Add support for additional controls.
          else if (c.isCritical())
          {
            setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
            int msgID = MSGID_BIND_UNSUPPORTED_CRITICAL_CONTROL;
            appendErrorMessage(getMessage(msgID, String.valueOf(oid)));
            break bindProcessing;
          }
        }
      }
      // Check to see if this is a simple bind or a SASL bind and process
      // accordingly.
      switch (authType)
      {
        case SIMPLE:
          // See if this is an anonymous bind.  If so, then determine whether
          // to allow it.
          if ((simplePassword == null) || (simplePassword.value().length == 0))
          {
            // If the server is in lockdown mode, then fail.
            if (DirectoryServer.lockdownMode())
            {
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
              setAuthFailureReason(msgID, getMessage(msgID));
              processingStopTime = System.currentTimeMillis();
              logBindResponse(this);
              break bindProcessing;
            }
            // If there is a bind DN, then see whether that is acceptable.
            if (DirectoryServer.bindWithDNRequiresPassword() &&
                ((bindDN != null) && (! bindDN.isNullDN())))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              int    msgID   = MSGID_BIND_DN_BUT_NO_PASSWORD;
              String message = getMessage(msgID);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            // Invoke the pre-operation bind plugins.
            PreOperationPluginResult preOpResult =
                 pluginConfigManager.invokePreOperationBindPlugins(this);
            if (preOpResult.connectionTerminated())
            {
              // There's no point in continuing with anything.  Log the result
              // and return.
              setResultCode(ResultCode.CANCELED);
              int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
              appendErrorMessage(getMessage(msgID));
              processingStopTime = System.currentTimeMillis();
              logBindResponse(this);
              pluginConfigManager.invokePostResponseBindPlugins(this);
              return;
            }
            else if (preOpResult.sendResponseImmediately())
            {
              skipPostOperation = true;
              break bindProcessing;
            }
            else if (preOpResult.skipCoreProcessing())
            {
              skipPostOperation = false;
              break bindProcessing;
            }
            setResultCode(ResultCode.SUCCESS);
            authInfo = new AuthenticationInfo();
            break bindProcessing;
          }
          // See if the bind DN is actually one of the alternate root DNs
          // defined in the server.  If so, then replace it with the actual DN
          // for that user.
          DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
          if (actualRootDN != null)
          {
            bindDN = actualRootDN;
          }
          // Get the user entry based on the bind DN.  If it does not exist,
          // then fail.
          Lock userLock = null;
          for (int i=0; i < 3; i++)
          {
            userLock = LockManager.lockRead(bindDN);
            if (userLock != null)
            {
              break;
            }
          }
          if (userLock == null)
          {
            int    msgID   = MSGID_BIND_OPERATION_CANNOT_LOCK_USER;
            String message = getMessage(msgID, String.valueOf(bindDN));
            setResultCode(DirectoryServer.getServerErrorResultCode());
            setAuthFailureReason(msgID, message);
            break bindProcessing;
          }
          try
          {
            Entry userEntry;
            try
            {
              userEntry = DirectoryServer.getEntry(bindDN);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(de.getMessageID(),
                                   de.getErrorMessage());
              userEntry = null;
              break bindProcessing;
            }
            if (userEntry == null)
            {
              int    msgID   = MSGID_BIND_OPERATION_UNKNOWN_USER;
              String message = getMessage(msgID, String.valueOf(bindDN));
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            else
            {
              userEntryDN = userEntry.getDN();
            }
            // 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();
            List<Attribute> pwAttr = userEntry.getAttribute(pwType);
            if ((pwAttr == null) || (pwAttr.isEmpty()))
            {
              int    msgID   = MSGID_BIND_OPERATION_NO_PASSWORD;
              String message = getMessage(msgID, String.valueOf(bindDN));
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              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() &&
                (! clientConnection.isSecure()))
            {
              int    msgID   = MSGID_BIND_OPERATION_INSECURE_SIMPLE_BIND;
              String message = getMessage(msgID, String.valueOf(bindDN));
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            // Check to see if the user is administratively disabled or locked.
            if (pwPolicyState.isDisabled())
            {
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_DISABLED;
              String message = getMessage(msgID, String.valueOf(bindDN));
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            else if (pwPolicyState.isAccountExpired())
            {
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
              String message = getMessage(msgID, String.valueOf(bindDN));
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
                   message);
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToFailures())
            {
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED;
              String message = getMessage(msgID, String.valueOf(bindDN));
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToMaximumResetAge())
            {
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
              String message = getMessage(msgID, String.valueOf(bindDN));
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
                   msgID, message);
              break bindProcessing;
            }
            else if (pwPolicyState.lockedDueToIdleInterval())
            {
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
              String message = getMessage(msgID, String.valueOf(bindDN));
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
                   msgID, message);
              break bindProcessing;
            }
            // Determine whether the password is expired, or whether the user
            // should be warned about an upcoming expiration.
            if (pwPolicyState.isPasswordExpired())
            {
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
              }
              int maxGraceLogins
                   = pwPolicyState.getPolicy().getGraceLoginCount();
              if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
              {
                List<Long> graceLoginTimes = pwPolicyState.getGraceLoginTimes();
                if ((graceLoginTimes == null) ||
                    (graceLoginTimes.size() < maxGraceLogins))
                {
                  isGraceLogin       = true;
                  mustChangePassword = true;
                  if (pwPolicyWarningType == null)
                  {
                    pwPolicyWarningType =
                         PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
                    pwPolicyWarningValue = maxGraceLogins -
                                           (graceLoginTimes.size() + 1);
                  }
                }
                else
                {
                  int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
                  String message = getMessage(msgID, String.valueOf(bindDN));
                  setResultCode(ResultCode.INVALID_CREDENTIALS);
                  setAuthFailureReason(msgID, message);
                  pwPolicyState.generateAccountStatusNotification(
                       AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
                       msgID, message);
                  break bindProcessing;
                }
              }
              else
              {
                int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
                String message = getMessage(msgID, String.valueOf(bindDN));
                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 = numSeconds;
              }
              isFirstWarning = pwPolicyState.isFirstWarning();
            }
            // Check to see if the user's password has been reset.
            if (pwPolicyState.mustChangePassword())
            {
              mustChangePassword = true;
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
              }
            }
            // Invoke the pre-operation bind plugins.
            PreOperationPluginResult preOpResult =
                 pluginConfigManager.invokePreOperationBindPlugins(this);
            if (preOpResult.connectionTerminated())
            {
              // There's no point in continuing with anything.  Log the result
              // and return.
              setResultCode(ResultCode.CANCELED);
              int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
              appendErrorMessage(getMessage(msgID));
              processingStopTime = System.currentTimeMillis();
              logBindResponse(this);
              pluginConfigManager.invokePostResponseBindPlugins(this);
              return;
            }
            else if (preOpResult.sendResponseImmediately())
            {
              skipPostOperation = true;
              break bindProcessing;
            }
            else if (preOpResult.skipCoreProcessing())
            {
              skipPostOperation = false;
              break bindProcessing;
            }
            // Determine whether the provided password matches any of the stored
            // passwords for the user.
            if (pwPolicyState.passwordMatches(simplePassword))
            {
              setResultCode(ResultCode.SUCCESS);
              boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN());
              if (DirectoryServer.lockdownMode() && (! isRoot))
              {
                setResultCode(ResultCode.INVALID_CREDENTIALS);
                int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
                setAuthFailureReason(msgID, getMessage(msgID));
                break bindProcessing;
              }
              authInfo = new AuthenticationInfo(userEntry, simplePassword,
                                                isRoot);
              // See if the user's entry contains a custom size limit.
              AttributeType attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
                                                 true);
              List<Attribute> attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS;
                    String message =
                         getMessage(msgID, String.valueOf(userEntry.getDN()));
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      sizeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(),
                                      String.valueOf(userEntry.getDN()));
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom time limit.
              attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
                                                 true);
              attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS;
                    String message =
                         getMessage(msgID, String.valueOf(userEntry.getDN()));
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      timeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(),
                                      String.valueOf(userEntry.getDN()));
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom lookthrough limit.
              attrType =
                   DirectoryServer.getAttributeType(
                       OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
              attrList = userEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS;
                    String message =
                         getMessage(msgID, String.valueOf(userEntry.getDN()));
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      lookthroughLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID =
                          MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(),
                                      String.valueOf(userEntry.getDN()));
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
              pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
              pwPolicyState.clearFailureLockout();
              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)
              {
                pwPolicyState.updateGraceLoginTimes();
              }
              pwPolicyState.setLastLoginTime();
            }
            else
            {
              int    msgID   = MSGID_BIND_OPERATION_WRONG_PASSWORD;
              String message = getMessage(msgID);
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
              {
                pwPolicyState.updateAuthFailureTimes();
                if (pwPolicyState.lockedDueToFailures())
                {
                  AccountStatusNotificationType notificationType;
                  int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
                  if (lockoutDuration > -1)
                  {
                    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);
                }
              }
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }
            int    msgID   = MSGID_BIND_OPERATION_PASSWORD_VALIDATION_EXCEPTION;
            String message = getMessage(msgID, getExceptionMessage(e));
            setResultCode(DirectoryServer.getServerErrorResultCode());
            setAuthFailureReason(msgID, message);
            break bindProcessing;
          }
          finally
          {
            // No matter what, make sure to unlock the user's entry.
            LockManager.unlock(bindDN, userLock);
          }
          break;
        case SASL:
          // Get the appropriate authentication handler for this request based
          // on the SASL mechanism.  If there is none, then fail.
          SASLMechanismHandler saslHandler =
               DirectoryServer.getSASLMechanismHandler(saslMechanism);
          if (saslHandler == null)
          {
            setResultCode(ResultCode.AUTH_METHOD_NOT_SUPPORTED);
            int    msgID   = MSGID_BIND_OPERATION_UNKNOWN_SASL_MECHANISM;
            String message = getMessage(msgID, saslMechanism);
            appendErrorMessage(message);
            setAuthFailureReason(msgID, message);
            break bindProcessing;
          }
          // Check to see if the client has sufficient permission to perform the
          // bind.
          // NYI
          // Invoke the pre-operation bind plugins.
          PreOperationPluginResult preOpResult =
               pluginConfigManager.invokePreOperationBindPlugins(this);
          if (preOpResult.connectionTerminated())
          {
            // There's no point in continuing with anything.  Log the result
            // and return.
            setResultCode(ResultCode.CANCELED);
            int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
            appendErrorMessage(getMessage(msgID));
            processingStopTime = System.currentTimeMillis();
            logBindResponse(this);
            pluginConfigManager.invokePostResponseBindPlugins(this);
            return;
          }
          else if (preOpResult.sendResponseImmediately())
          {
            skipPostOperation = true;
            break bindProcessing;
          }
          else if (preOpResult.skipCoreProcessing())
          {
            skipPostOperation = false;
            break bindProcessing;
          }
          // Actually process the SASL bind.
          saslHandler.processSASLBind(this);
          // If the server is operating in lockdown mode, then we will need to
          // ensure that the authentication was successful and performed as a
          // root user to continue.
          if (DirectoryServer.lockdownMode())
          {
            ResultCode resultCode = getResultCode();
            if (resultCode != ResultCode.SASL_BIND_IN_PROGRESS)
            {
              if ((resultCode != ResultCode.SUCCESS) ||
                  (saslAuthUserEntry == null) ||
                  (! DirectoryServer.isRootDN(saslAuthUserEntry.getDN())))
              {
                setResultCode(ResultCode.INVALID_CREDENTIALS);
                int msgID = MSGID_BIND_REJECTED_LOCKDOWN_MODE;
                setAuthFailureReason(msgID, getMessage(msgID));
                break bindProcessing;
              }
            }
          }
          // Create the password policy state object.
          String userDNString;
          if (saslAuthUserEntry == null)
          {
            pwPolicyState = null;
            userDNString  = null;
          }
          else
          {
            try
            {
              // FIXME -- Need to have a way to enable debugging.
              pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false,
                                                      false);
              userEntryDN = saslAuthUserEntry.getDN();
              userDNString = String.valueOf(userEntryDN);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              setResponseData(de);
              break bindProcessing;
            }
          }
          // Perform password policy checks that will need to be completed
          // regardless of whether the authentication was successful.
          if (pwPolicyState != null)
          {
            if (pwPolicyState.isDisabled())
            {
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_DISABLED;
              String message = getMessage(msgID, userDNString);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            else if (pwPolicyState.isAccountExpired())
            {
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_EXPIRED;
              String message = getMessage(msgID, userDNString);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_EXPIRED, bindDN, msgID,
                   message);
              break bindProcessing;
            }
            if (pwPolicyState.getPolicy().requireSecureAuthentication() &&
                (! clientConnection.isSecure()) &&
                (! saslHandler.isSecure(saslMechanism)))
            {
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              int    msgID   = MSGID_BIND_OPERATION_INSECURE_SASL_BIND;
              String message = getMessage(msgID, saslMechanism, userDNString);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            if (pwPolicyState.lockedDueToFailures())
            {
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_FAILURE_LOCKED;
              String message = getMessage(msgID, userDNString);
              setAuthFailureReason(msgID, message);
              break bindProcessing;
            }
            if (pwPolicyState.lockedDueToIdleInterval())
            {
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              if (pwPolicyErrorType == null)
              {
                pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
              }
              int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_IDLE_LOCKED;
              String message = getMessage(msgID, userDNString);
              setAuthFailureReason(msgID, message);
              pwPolicyState.generateAccountStatusNotification(
                   AccountStatusNotificationType.ACCOUNT_IDLE_LOCKED, bindDN,
                   msgID, message);
              break bindProcessing;
            }
            if (saslHandler.isPasswordBased(saslMechanism))
            {
              if (pwPolicyState.lockedDueToMaximumResetAge())
              {
                setResultCode(ResultCode.INVALID_CREDENTIALS);
                if (pwPolicyErrorType == null)
                {
                  pwPolicyErrorType = PasswordPolicyErrorType.ACCOUNT_LOCKED;
                }
                int    msgID   = MSGID_BIND_OPERATION_ACCOUNT_RESET_LOCKED;
                String message = getMessage(msgID, userDNString);
                setAuthFailureReason(msgID, message);
                pwPolicyState.generateAccountStatusNotification(
                     AccountStatusNotificationType.ACCOUNT_RESET_LOCKED, bindDN,
                     msgID, message);
                break bindProcessing;
              }
              if (pwPolicyState.isPasswordExpired())
              {
                if (pwPolicyErrorType == null)
                {
                  pwPolicyErrorType = PasswordPolicyErrorType.PASSWORD_EXPIRED;
                }
                int maxGraceLogins
                     = pwPolicyState.getPolicy().getGraceLoginCount();
                if ((maxGraceLogins > 0) && pwPolicyState.mayUseGraceLogin())
                {
                  List<Long> graceLoginTimes =
                       pwPolicyState.getGraceLoginTimes();
                  if ((graceLoginTimes == null) ||
                      (graceLoginTimes.size() < maxGraceLogins))
                  {
                    isGraceLogin       = true;
                    mustChangePassword = true;
                    if (pwPolicyWarningType == null)
                    {
                      pwPolicyWarningType =
                           PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
                      pwPolicyWarningValue =
                           maxGraceLogins - (graceLoginTimes.size() + 1);
                    }
                  }
                  else
                  {
                    int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
                    String message = getMessage(msgID, String.valueOf(bindDN));
                    setResultCode(ResultCode.INVALID_CREDENTIALS);
                    setAuthFailureReason(msgID, message);
                    pwPolicyState.generateAccountStatusNotification(
                         AccountStatusNotificationType.PASSWORD_EXPIRED, bindDN,
                         msgID, message);
                    break bindProcessing;
                  }
                }
                else
                {
                  int    msgID   = MSGID_BIND_OPERATION_PASSWORD_EXPIRED;
                  String message = getMessage(msgID, String.valueOf(bindDN));
                  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 = numSeconds;
                }
                isFirstWarning = pwPolicyState.isFirstWarning();
              }
            }
          }
          // Determine whether the authentication was successful and perform
          // any remaining password policy processing accordingly.  Also check
          // for a custom size/time limit.
          ResultCode resultCode = getResultCode();
          if (resultCode == ResultCode.SUCCESS)
          {
            if (pwPolicyState != null)
            {
              if (saslHandler.isPasswordBased(saslMechanism) &&
                  pwPolicyState.mustChangePassword())
              {
                mustChangePassword = true;
              }
              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)
              {
                pwPolicyState.updateGraceLoginTimes();
              }
              pwPolicyState.setLastLoginTime();
              // See if the user's entry contains a custom size limit.
              AttributeType attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_SIZE_LIMIT,
                                                 true);
              List<Attribute> attrList =
                   saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_SIZE_LIMITS;
                    String message = getMessage(msgID, userDNString);
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      sizeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_SIZE_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(), userDNString);
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom time limit.
              attrType =
                   DirectoryServer.getAttributeType(OP_ATTR_USER_TIME_LIMIT,
                                                    true);
              attrList = saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_TIME_LIMITS;
                    String message = getMessage(msgID, userDNString);
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      timeLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID = MSGID_BIND_CANNOT_PROCESS_USER_TIME_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(), userDNString);
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
              // See if the user's entry contains a custom lookthrough limit.
              attrType =
                   DirectoryServer.getAttributeType(
                       OP_ATTR_USER_LOOKTHROUGH_LIMIT, true);
              attrList = saslAuthUserEntry.getAttribute(attrType);
              if ((attrList != null) && (attrList.size() == 1))
              {
                Attribute a = attrList.get(0);
                LinkedHashSet<AttributeValue>  values = a.getValues();
                Iterator<AttributeValue> iterator = values.iterator();
                if (iterator.hasNext())
                {
                  AttributeValue v = iterator.next();
                  if (iterator.hasNext())
                  {
                    int msgID = MSGID_BIND_MULTIPLE_USER_LOOKTHROUGH_LIMITS;
                    String message = getMessage(msgID, userDNString);
                    logError(ErrorLogCategory.CORE_SERVER,
                             ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                  }
                  else
                  {
                    try
                    {
                      lookthroughLimit = Integer.parseInt(v.getStringValue());
                    }
                    catch (Exception e)
                    {
                      if (debugEnabled())
                      {
                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
                      }
                      int msgID =
                          MSGID_BIND_CANNOT_PROCESS_USER_LOOKTHROUGH_LIMIT;
                      String message =
                           getMessage(msgID, v.getStringValue(), userDNString);
                      logError(ErrorLogCategory.CORE_SERVER,
                               ErrorLogSeverity.SEVERE_WARNING, message, msgID);
                    }
                  }
                }
              }
            }
          }
          else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
          {
            // FIXME -- Is any special processing needed here?
          }
          else
          {
            if (pwPolicyState != null)
            {
              if (saslHandler.isPasswordBased(saslMechanism))
              {
                if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
                {
                  pwPolicyState.updateAuthFailureTimes();
                  if (pwPolicyState.lockedDueToFailures())
                  {
                    AccountStatusNotificationType notificationType;
                    int msgID;
                    String message;
                    int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
                    if (lockoutDuration > -1)
                    {
                      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);
                  }
                }
              }
            }
          }
          break;
        default:
          // Send a protocol error response to the client and disconnect.
          // NYI
          return;
      }
    }
    // Update the user's account with any password policy changes that may be
    // required.
    try
    {
      if (pwPolicyState != null)
      {
        pwPolicyState.updateUserEntry();
      }
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      setResponseData(de);
    }
    // Invoke the post-operation bind plugins.
    if (! skipPostOperation)
    {
      PostOperationPluginResult postOpResult =
           pluginConfigManager.invokePostOperationBindPlugins(this);
      if (postOpResult.connectionTerminated())
      {
        // There's no point in continuing with anything.  Log the result
        // and return.
        setResultCode(ResultCode.CANCELED);
        int msgID = MSGID_CANCELED_BY_PREOP_DISCONNECT;
        appendErrorMessage(getMessage(msgID));
        processingStopTime = System.currentTimeMillis();
        logBindResponse(this);
        pluginConfigManager.invokePostResponseBindPlugins(this);
        return;
      }
    }
    // Update the authentication information for the user.
    if ((getResultCode() == ResultCode.SUCCESS) && (authInfo != null))
    {
      authenticatedUserEntry = authInfo.getAuthenticationEntry();
      clientConnection.setAuthenticationInfo(authInfo);
      clientConnection.setSizeLimit(sizeLimit);
      clientConnection.setTimeLimit(timeLimit);
      clientConnection.setLookthroughLimit(lookthroughLimit);
      clientConnection.setMustChangePassword(mustChangePassword);
      if (returnAuthzID)
      {
        responseControls.add(new AuthorizationIdentityResponseControl(
                                      authInfo.getAuthorizationDN()));
      }
    }
    // See if we need to send a password policy control to the client.  If so,
    // then add it to the response.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      if (pwPolicyControlRequested)
      {
        PasswordPolicyResponseControl pwpControl =
             new PasswordPolicyResponseControl(pwPolicyWarningType,
                                               pwPolicyWarningValue,
                                               pwPolicyErrorType);
        responseControls.add(pwpControl);
      }
      else
      {
        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
        {
          responseControls.add(new PasswordExpiredControl());
        }
        else if (pwPolicyWarningType ==
                 PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
        {
          responseControls.add(new PasswordExpiringControl(
                                        pwPolicyWarningValue));
        }
      }
    }
    else
    {
      if (pwPolicyControlRequested)
      {
        PasswordPolicyResponseControl pwpControl =
             new PasswordPolicyResponseControl(pwPolicyWarningType,
                                               pwPolicyWarningValue,
                                               pwPolicyErrorType);
        responseControls.add(pwpControl);
      }
      else
      {
        if (pwPolicyErrorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
        {
          responseControls.add(new PasswordExpiredControl());
        }
      }
    }
    // Unset the "bind in progress" flag to allow other operations to be
    // processed.
    // FIXME -- Make sure this also gets unset at every possible point at which
    // the bind could fail and this method could return early.
    clientConnection.setBindInProgress(false);
    // Stop the processing timer.
    processingStopTime = System.currentTimeMillis();
    // Send the bind response to the client.
    clientConnection.sendResponse(this);
    // Log the bind response.
    logBindResponse(this);
    // Invoke the post-response bind plugins.
    pluginConfigManager.invokePostResponseBindPlugins(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final CancelResult cancel(CancelRequest cancelRequest)
  {
    cancelRequest.addResponseMessage(getMessage(MSGID_CANNOT_CANCEL_BIND));
    return CancelResult.CANNOT_CANCEL;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public final CancelRequest getCancelRequest()
  {
    return null;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  protected boolean setCancelRequest(CancelRequest cancelRequest)
  {
    // Bind operations cannot be canceled.
    return false;
  }
  public abstract void setUserEntryDN(DN userEntryDN);
  /**
   * {@inheritDoc}
   */
  @Override()
  public final void toString(StringBuilder buffer)
  {
    buffer.append("BindOperation(connID=");
    buffer.append(clientConnection.getConnectionID());
    buffer.append(", opID=");
    buffer.append(operationID);
    buffer.append(", protocol=\"");
    buffer.append(clientConnection.getProtocol());
    buffer.append(" ");
    buffer.append(protocolVersion);
    buffer.append("\", dn=");
    buffer.append(rawBindDN);
    buffer.append(", authType=");
    buffer.append(authType);
    buffer.append(")");
  }
}