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

Jean-Noël Rouvignac
26.51.2016 8a180ad417c26429cd3774c0046165c40ad1010a
LDAPAuthenticationHandler.java: Remove code duplication

*.java:
LDAPMessage.getControls() never returns null, removed null checks from client code
4 files modified
2696 ■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPClientConnection.java 286 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPAuthenticationHandler.java 1832 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPPasswordModify.java 13 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/tools/LDAPAuthenticationHandlerTestCase.java 565 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPClientConnection.java
@@ -16,12 +16,24 @@
 */
package org.opends.server.protocols.ldap;
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.loggers.AccessLogger.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.ByteChannel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.cert.Certificate;
import java.util.Collection;
import java.util.Iterator;
@@ -45,23 +57,38 @@
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConnectionHandler;
import org.opends.server.core.*;
import org.opends.server.core.AbandonOperationBasis;
import org.opends.server.core.AddOperationBasis;
import org.opends.server.core.BindOperationBasis;
import org.opends.server.core.CompareOperationBasis;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ExtendedOperationBasis;
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.SearchOperation;
import org.opends.server.core.SearchOperationBasis;
import org.opends.server.core.UnbindOperationBasis;
import org.opends.server.extensions.ConnectionSecurityProvider;
import org.opends.server.extensions.RedirectingByteChannel;
import org.opends.server.extensions.TLSByteChannel;
import org.opends.server.extensions.TLSCapableConnection;
import org.opends.server.types.*;
import org.opends.server.types.AuthenticationType;
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.IntermediateResponse;
import org.opends.server.types.Operation;
import org.opends.server.types.OperationType;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.TimeThread;
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.loggers.AccessLogger.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an LDAP client connection, which is a type of
 * client connection that will be accepted by an instance of the LDAP
@@ -231,13 +258,9 @@
              // We've been blocked for too long.
              throw new ClosedChannelException();
            }
            else
            {
              waitTime = stopTime - currentTime;
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys()
                .iterator();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext())
            {
              SelectionKey k = iterator.next();
@@ -375,7 +398,7 @@
  private final LDAPConnectionHandler connectionHandler;
  /** The statistics tracker associated with this client connection. */
  private final LDAPStatistics statTracker;
  private boolean useNanoTime;
  private final boolean useNanoTime;
  /** The connection ID assigned to this connection. */
  private final long connectionID;
@@ -395,7 +418,7 @@
  /** The string representation of the address of the server to which the client has connected. */
  private final String serverAddress;
  private ASN1ByteChannelReader asn1Reader;
  private final ASN1ByteChannelReader asn1Reader;
  private final int bufferSize;
  private final RedirectingByteChannel saslChannel;
  private final RedirectingByteChannel tlsChannel;
@@ -440,20 +463,20 @@
    serverPort = socket.getLocalPort();
    statTracker = this.connectionHandler.getStatTracker();
    if (keepStats)
    {
      statTracker.updateConnect();
      this.useNanoTime=DirectoryServer.getUseNanoTime();
    }
    else
    {
      this.useNanoTime = false;
    }
    bufferSize = connectionHandler.getBufferSize();
    tlsChannel =
        RedirectingByteChannel.getRedirectingByteChannel(
            timeoutClientChannel);
    saslChannel =
        RedirectingByteChannel.getRedirectingByteChannel(tlsChannel);
    tlsChannel = RedirectingByteChannel.getRedirectingByteChannel(timeoutClientChannel);
    saslChannel = RedirectingByteChannel.getRedirectingByteChannel(tlsChannel);
    this.asn1Reader = new ASN1ByteChannelReader(saslChannel, bufferSize, connectionHandler.getMaxRequestSize());
    if (connectionHandler.useSSL())
@@ -981,20 +1004,20 @@
    // Indicate that this connection is no longer valid.
    connectionValid = false;
    final LocalizableMessage cancelMessage;
    if (message != null)
    {
      LocalizableMessageBuilder msgBuilder = new LocalizableMessageBuilder();
      msgBuilder.append(disconnectReason.getClosureMessage());
      msgBuilder.append(": ");
      msgBuilder.append(message);
      cancelAllOperations(new CancelRequest(true, msgBuilder
          .toMessage()));
      cancelMessage = new LocalizableMessageBuilder()
          .append(disconnectReason.getClosureMessage())
          .append(": ")
          .append(message)
          .toMessage();
    }
    else
    {
      cancelAllOperations(new CancelRequest(true, disconnectReason
          .getClosureMessage()));
      cancelMessage = disconnectReason.getClosureMessage();
    }
    cancelAllOperations(new CancelRequest(true, cancelMessage));
    finalizeConnectionInternal();
    // If there is a write selector for this connection, then close it.
@@ -1008,45 +1031,8 @@
    {
      try
      {
        int resultCode;
        switch (disconnectReason)
        {
        case PROTOCOL_ERROR:
          resultCode = LDAPResultCode.PROTOCOL_ERROR;
          break;
        case SERVER_SHUTDOWN:
          resultCode = LDAPResultCode.UNAVAILABLE;
          break;
        case SERVER_ERROR:
          resultCode = DirectoryServer.getServerErrorResultCode().intValue();
          break;
        case ADMIN_LIMIT_EXCEEDED:
        case IDLE_TIME_LIMIT_EXCEEDED:
        case MAX_REQUEST_SIZE_EXCEEDED:
        case IO_TIMEOUT:
          resultCode = LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
          break;
        case CONNECTION_REJECTED:
          resultCode = LDAPResultCode.CONSTRAINT_VIOLATION;
          break;
        case INVALID_CREDENTIALS:
          resultCode = LDAPResultCode.INVALID_CREDENTIALS;
          break;
        default:
          resultCode = LDAPResultCode.OTHER;
          break;
        }
        LocalizableMessage errMsg;
        if (message == null)
        {
          errMsg =
              INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
        }
        else
        {
          errMsg = message;
        }
        int resultCode = toResultCode(disconnectReason);
        LocalizableMessage errMsg = message != null ? message : INFO_LDAP_CLIENT_GENERIC_NOTICE_OF_DISCONNECTION.get();
        ExtendedResponseProtocolOp notificationOp =
            new ExtendedResponseProtocolOp(resultCode, errMsg, null,
@@ -1069,15 +1055,12 @@
    // NYI -- Deregister the client connection from any server components that
    // might know about it.
    // Log a disconnect message.
    logDisconnect(this, disconnectReason, message);
    try
    {
      PluginConfigManager pluginManager =
          DirectoryServer.getPluginConfigManager();
      pluginManager.invokePostDisconnectPlugins(this, disconnectReason,
          message);
      PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager();
      pluginManager.invokePostDisconnectPlugins(this, disconnectReason, message);
    }
    catch (Exception e)
    {
@@ -1085,6 +1068,30 @@
    }
  }
  private int toResultCode(DisconnectReason disconnectReason)
  {
    switch (disconnectReason)
    {
    case PROTOCOL_ERROR:
      return LDAPResultCode.PROTOCOL_ERROR;
    case SERVER_SHUTDOWN:
      return LDAPResultCode.UNAVAILABLE;
    case SERVER_ERROR:
      return DirectoryServer.getServerErrorResultCode().intValue();
    case ADMIN_LIMIT_EXCEEDED:
    case IDLE_TIME_LIMIT_EXCEEDED:
    case MAX_REQUEST_SIZE_EXCEEDED:
    case IO_TIMEOUT:
      return LDAPResultCode.ADMIN_LIMIT_EXCEEDED;
    case CONNECTION_REJECTED:
      return LDAPResultCode.CONSTRAINT_VIOLATION;
    case INVALID_CREDENTIALS:
      return LDAPResultCode.INVALID_CREDENTIALS;
    default:
      return LDAPResultCode.OTHER;
    }
  }
  /**
   * Retrieves the set of operations in progress for this client
   * connection. This list must not be altered by any caller.
@@ -1235,28 +1242,24 @@
      CancelRequest cancelRequest)
  {
    Operation op = operationsInProgress.get(messageID);
    if (op == null)
    if (op != null)
    {
      return op.cancel(cancelRequest);
    }
      // See if the operation is in the list of persistent searches.
      for (PersistentSearch ps : getPersistentSearches())
      {
        if (ps.getMessageID() == messageID)
        {
          // We only need to find the first persistent search
          // associated with the provided message ID. The persistent
          // search will ensure that all other related persistent
          // searches are cancelled.
        // associated with the provided message ID. The persistent search
        // will ensure that all other related persistent searches are cancelled.
          return ps.cancel();
        }
      }
      return new CancelResult(ResultCode.NO_SUCH_OPERATION, null);
    }
    else
    {
      return op.cancel(cancelRequest);
    }
  }
  /**
   * Attempts to cancel all operations in progress on this connection.
@@ -1535,15 +1538,13 @@
      switch (message.getProtocolOpType())
      {
      case OP_TYPE_ABANDON_REQUEST:
        result = processAbandonRequest(message, opControls);
        return result;
        return processAbandonRequest(message, opControls);
      case OP_TYPE_ADD_REQUEST:
        result = processAddRequest(message, opControls);
        return result;
        return processAddRequest(message, opControls);
      case OP_TYPE_BIND_REQUEST:
        boolean isSaslBind = message.getBindRequestProtocolOp().getAuthenticationType() == AuthenticationType.SASL;
        bindInProgress.set(true);
        if(message.getBindRequestProtocolOp().
            getAuthenticationType() == AuthenticationType.SASL)
        if (isSaslBind)
        {
          saslBindInProgress.set(true);
        }
@@ -1551,45 +1552,36 @@
        if(!result)
        {
          bindInProgress.set(false);
          if(message.getBindRequestProtocolOp().
              getAuthenticationType() == AuthenticationType.SASL)
          if (isSaslBind)
          {
            saslBindInProgress.set(false);
          }
        }
        return result;
      case OP_TYPE_COMPARE_REQUEST:
        result = processCompareRequest(message, opControls);
        return result;
        return processCompareRequest(message, opControls);
      case OP_TYPE_DELETE_REQUEST:
        result = processDeleteRequest(message, opControls);
        return result;
        return processDeleteRequest(message, opControls);
      case OP_TYPE_EXTENDED_REQUEST:
        if(message.getExtendedRequestProtocolOp().getOID().equals(
            OID_START_TLS_REQUEST))
        boolean isStartTlsRequest = OID_START_TLS_REQUEST.equals(message.getExtendedRequestProtocolOp().getOID());
        if (isStartTlsRequest)
        {
          startTLSInProgress.set(true);
        }
        result = processExtendedRequest(message, opControls);
        if(!result &&
            message.getExtendedRequestProtocolOp().getOID().equals(
                OID_START_TLS_REQUEST))
        if (!result && isStartTlsRequest)
        {
          startTLSInProgress.set(false);
        }
        return result;
      case OP_TYPE_MODIFY_REQUEST:
        result = processModifyRequest(message, opControls);
        return result;
        return processModifyRequest(message, opControls);
      case OP_TYPE_MODIFY_DN_REQUEST:
        result = processModifyDNRequest(message, opControls);
        return result;
        return processModifyDNRequest(message, opControls);
      case OP_TYPE_SEARCH_REQUEST:
        result = processSearchRequest(message, opControls);
        return result;
        return processSearchRequest(message, opControls);
      case OP_TYPE_UNBIND_REQUEST:
        result = processUnbindRequest(message, opControls);
        return result;
        return processUnbindRequest(message, opControls);
      default:
        LocalizableMessage msg =
            ERR_LDAP_DISCONNECT_DUE_TO_INVALID_REQUEST_TYPE.get(message
@@ -1626,11 +1618,9 @@
   */
  private boolean processAbandonRequest(LDAPMessage message, List<Control> controls)
  {
    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      disconnectControlsNotAllowed();
      return false;
    }
@@ -1672,16 +1662,14 @@
   */
  private boolean processAddRequest(LDAPMessage message, List<Control> controls)
  {
    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      AddResponseProtocolOp responseOp =
          new AddResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(message, responseOp);
      disconnectControlsNotAllowed();
      return false;
    }
@@ -1712,6 +1700,16 @@
    return connectionValid;
  }
  private void sendLDAPMessage(LDAPMessage message, ProtocolOp responseOp)
  {
    sendLDAPMessage(new LDAPMessage(message.getMessageID(), responseOp));
  }
  private void disconnectControlsNotAllowed()
  {
    disconnect(DisconnectReason.PROTOCOL_ERROR, false, ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
  }
  /**
   * Processes the provided LDAP message as a bind request.
   *
@@ -1752,16 +1750,14 @@
        return false;
      }
      if (controls != null && !controls.isEmpty())
      if (!controls.isEmpty())
      {
        // LDAPv2 clients aren't allowed to send controls.
        BindResponseProtocolOp responseOp =
            new BindResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
                ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
        sendLDAPMessage(new LDAPMessage(message.getMessageID(),
            responseOp));
        disconnect(DisconnectReason.PROTOCOL_ERROR, false,
            ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
        sendLDAPMessage(message, responseOp);
        disconnectControlsNotAllowed();
        return false;
      }
@@ -1857,16 +1853,14 @@
   */
  private boolean processCompareRequest(LDAPMessage message, List<Control> controls)
  {
    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      CompareResponseProtocolOp responseOp =
          new CompareResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(message, responseOp);
      disconnectControlsNotAllowed();
      return false;
    }
@@ -1913,16 +1907,14 @@
   */
  private boolean processDeleteRequest(LDAPMessage message, List<Control> controls)
  {
    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      DeleteResponseProtocolOp responseOp =
          new DeleteResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(message, responseOp);
      disconnectControlsNotAllowed();
      return false;
    }
@@ -2031,16 +2023,14 @@
   */
  private boolean processModifyRequest(LDAPMessage message, List<Control> controls)
  {
    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      ModifyResponseProtocolOp responseOp =
          new ModifyResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(message, responseOp);
      disconnectControlsNotAllowed();
      return false;
    }
@@ -2088,16 +2078,14 @@
   */
  private boolean processModifyDNRequest(LDAPMessage message, List<Control> controls)
  {
    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      ModifyDNResponseProtocolOp responseOp =
          new ModifyDNResponseProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(message, responseOp);
      disconnectControlsNotAllowed();
      return false;
    }
@@ -2145,16 +2133,14 @@
  private boolean processSearchRequest(LDAPMessage message,
      List<Control> controls)
  {
    if (ldapVersion == 2 && controls != null && !controls.isEmpty())
    if (ldapVersion == 2 && !controls.isEmpty())
    {
      // LDAPv2 clients aren't allowed to send controls.
      SearchResultDoneProtocolOp responseOp =
          new SearchResultDoneProtocolOp(LDAPResultCode.PROTOCOL_ERROR,
              ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(new LDAPMessage(message.getMessageID(),
          responseOp));
      disconnect(DisconnectReason.PROTOCOL_ERROR, false,
          ERR_LDAPV2_CONTROLS_NOT_ALLOWED.get());
      sendLDAPMessage(message, responseOp);
      disconnectControlsNotAllowed();
      return false;
    }
opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPAuthenticationHandler.java
@@ -16,6 +16,13 @@
 */
package org.opends.server.tools;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -31,6 +38,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicInteger;
@@ -44,11 +52,10 @@
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import com.forgerock.opendj.cli.ClientException;
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.ReturnCode;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
@@ -61,12 +68,9 @@
import org.opends.server.types.LDAPException;
import org.opends.server.util.Base64;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import com.forgerock.opendj.cli.ClientException;
import com.forgerock.opendj.cli.ConsoleApplication;
import com.forgerock.opendj.cli.ReturnCode;
/**
 * This class provides a generic interface that LDAP clients can use to perform
@@ -88,42 +92,32 @@
public class LDAPAuthenticationHandler
       implements PrivilegedExceptionAction<Object>, CallbackHandler
{
  /** The bind DN for GSSAPI authentication. */
  private ByteSequence gssapiBindDN;
  /** The LDAP reader that will be used to read data from the server. */
  private final LDAPReader reader;
  /** The LDAP writer that will be used to send data to the server. */
  private final LDAPWriter writer;
  /**
   * The atomic integer that will be used to obtain message IDs for request
   * messages.
   */
  /** The atomic integer that will be used to obtain message IDs for request messages. */
  private final AtomicInteger nextMessageID;
  /** An array filled with the inner pad byte. */
  private byte[] iPad;
  /** An array filled with the outer pad byte. */
  private byte[] oPad;
  /** The authentication password for GSSAPI authentication. */
  private char[] gssapiAuthPW;
  /** The message digest that will be used to create MD5 hashes. */
  private MessageDigest md5Digest;
  /** The secure random number generator for use by this authentication handler. */
  private SecureRandom secureRandom;
  /** The bind DN for GSSAPI authentication. */
  private ByteSequence gssapiBindDN;
  /** The authentication ID for GSSAPI authentication. */
  private String gssapiAuthID;
  /** The authorization ID for GSSAPI authentication. */
  private String gssapiAuthzID;
  /** The authentication password for GSSAPI authentication. */
  private char[] gssapiAuthPW;
  /** The quality of protection for GSSAPI authentication. */
  private String gssapiQoP;
@@ -200,36 +194,23 @@
   *          specified SASL mechanism, mapped from the property names to their
   *          corresponding descriptions.
   */
  public static LinkedHashMap<String,LocalizableMessage> getSASLProperties(
          String mechanism)
  public static Map<String, LocalizableMessage> getSASLProperties(String mechanism)
  {
    String upperName = toUpperCase(mechanism);
    if (upperName.equals(SASL_MECHANISM_ANONYMOUS))
    switch (toUpperCase(mechanism))
    {
    case SASL_MECHANISM_ANONYMOUS:
      return getSASLAnonymousProperties();
    }
    else if (upperName.equals(SASL_MECHANISM_CRAM_MD5))
    {
    case SASL_MECHANISM_CRAM_MD5:
      return getSASLCRAMMD5Properties();
    }
    else if (upperName.equals(SASL_MECHANISM_DIGEST_MD5))
    {
    case SASL_MECHANISM_DIGEST_MD5:
      return getSASLDigestMD5Properties();
    }
    else if (upperName.equals(SASL_MECHANISM_EXTERNAL))
    {
    case SASL_MECHANISM_EXTERNAL:
      return getSASLExternalProperties();
    }
    else if (upperName.equals(SASL_MECHANISM_GSSAPI))
    {
    case SASL_MECHANISM_GSSAPI:
      return getSASLGSSAPIProperties();
    }
    else if (upperName.equals(SASL_MECHANISM_PLAIN))
    {
    case SASL_MECHANISM_PLAIN:
      return getSASLPlainProperties();
    }
    else
    {
    default:
      // This is an unsupported mechanism.
      return null;
    }
@@ -279,21 +260,26 @@
        bindPassword = ByteString.empty();
    }
    // Make sure that critical elements aren't null.
    if (bindDN == null)
    {
      bindDN = ByteString.empty();
    }
    sendSimpleBindRequest(ldapVersion, bindDN, bindPassword, requestControls);
    // Create the bind request and send it to the server.
    LDAPMessage responseMessage = readBindResponse(ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE);
    responseControls.addAll(responseMessage.getControls());
    checkConnected(responseMessage);
    return checkSuccessfulSimpleBind(responseMessage);
  }
  private void sendSimpleBindRequest(int ldapVersion, ByteSequence bindDN, ByteSequence bindPassword,
      List<Control> requestControls) throws ClientException
  {
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(bindDN.toByteString(), ldapVersion,
             bindPassword.toByteString());
    LDAPMessage bindRequestMessage =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest,
                         requestControls);
        new BindRequestProtocolOp(bindDN.toByteString(), ldapVersion, bindPassword.toByteString());
    LDAPMessage bindRequestMessage = new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest, requestControls);
    try
    {
@@ -301,88 +287,45 @@
    }
    catch (IOException ioe)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_SEND_SIMPLE_BIND.get(getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SIMPLE_BIND.get(getExceptionMessage(ioe));
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_SEND_SIMPLE_BIND.get(getExceptionMessage(e));
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SIMPLE_BIND.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR, message, e);
    }
    // Read the response from the server.
    LDAPMessage responseMessage;
    try
    {
      responseMessage = reader.readMessage();
      if (responseMessage == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(ioe));
      throw new ClientException(
          ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(
          ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // See if there are any controls in the response.  If so, then add them to
    // the response controls list.
    List<Control> respControls = responseMessage.getControls();
    if (respControls != null && !respControls.isEmpty())
  private BindResponseProtocolOp checkSuccessfulBind(LDAPMessage responseMessage, String saslMechanism)
      throws LDAPException
    {
      responseControls.addAll(respControls);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    generateError(responseMessage);
    BindResponseProtocolOp bindResponse =
         responseMessage.getBindResponseProtocolOp();
    BindResponseProtocolOp bindResponse = responseMessage.getBindResponseProtocolOp();
    int resultCode = bindResponse.getResultCode();
    if (resultCode == ReturnCode.SUCCESS.get())
    if (resultCode != ReturnCode.SUCCESS.get())
    {
      // FIXME -- Need to look for things like password expiration warning,
      // reset notice, etc.
      // FIXME -- Add support for referrals.
      LocalizableMessage message = ERR_LDAPAUTH_SASL_BIND_FAILED.get(saslMechanism);
      throw new LDAPException(resultCode, bindResponse.getErrorMessage(), message, bindResponse.getMatchedDN(), null);
    }
    // FIXME -- Need to look for things like password expiration warning, reset notice, etc.
    return bindResponse;
  }
  private String checkSuccessfulSimpleBind(LDAPMessage responseMessage) throws LDAPException
  {
    BindResponseProtocolOp bindResponse = responseMessage.getBindResponseProtocolOp();
    int resultCode = bindResponse.getResultCode();
    if (resultCode != ReturnCode.SUCCESS.get())
    {
      // FIXME -- Add support for referrals.
      LocalizableMessage message = ERR_LDAPAUTH_SIMPLE_BIND_FAILED.get();
      throw new LDAPException(resultCode, bindResponse.getErrorMessage(), message, bindResponse.getMatchedDN(), null);
    }
    // FIXME -- Need to look for things like password expiration warning, reset notice, etc.
      return null;
    }
    // FIXME -- Add support for referrals.
    LocalizableMessage message = ERR_LDAPAUTH_SIMPLE_BIND_FAILED.get();
    throw new LDAPException(resultCode, bindResponse.getErrorMessage(),
                            message, bindResponse.getMatchedDN(), null);
  }
  /**
   * Processes a SASL bind using the provided information.  If the bind fails,
   * then an exception will be thrown with information about the reason for the
@@ -431,49 +374,29 @@
    if (mechanism == null || mechanism.length() == 0)
    {
      LocalizableMessage message = ERR_LDAPAUTH_NO_SASL_MECHANISM.get();
      throw new ClientException(
          ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
      throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
    }
    // Look at the mechanism name and call the appropriate method to process
    // the request.
    // Look at the mechanism name and call the appropriate method to process the request.
    saslMechanism = toUpperCase(mechanism);
    if (saslMechanism.equals(SASL_MECHANISM_ANONYMOUS))
    switch (saslMechanism)
    {
      return doSASLAnonymous(bindDN, saslProperties, requestControls,
                             responseControls);
    }
    else if (saslMechanism.equals(SASL_MECHANISM_CRAM_MD5))
    {
      return doSASLCRAMMD5(bindDN, bindPassword, saslProperties,
                           requestControls, responseControls);
    }
    else if (saslMechanism.equals(SASL_MECHANISM_DIGEST_MD5))
    {
      return doSASLDigestMD5(bindDN, bindPassword, saslProperties,
                             requestControls, responseControls);
    }
    else if (saslMechanism.equals(SASL_MECHANISM_EXTERNAL))
    {
      return doSASLExternal(bindDN, saslProperties, requestControls,
                            responseControls);
    }
    else if (saslMechanism.equals(SASL_MECHANISM_GSSAPI))
    {
      return doSASLGSSAPI(bindDN, bindPassword, saslProperties, requestControls,
                          responseControls);
    }
    else if (saslMechanism.equals(SASL_MECHANISM_PLAIN))
    {
      return doSASLPlain(bindDN, bindPassword, saslProperties, requestControls,
                         responseControls);
    }
    else
    {
    case SASL_MECHANISM_ANONYMOUS:
      return doSASLAnonymous(bindDN, saslProperties, requestControls, responseControls);
    case SASL_MECHANISM_CRAM_MD5:
      return doSASLCRAMMD5(bindDN, bindPassword, saslProperties, requestControls, responseControls);
    case SASL_MECHANISM_DIGEST_MD5:
      return doSASLDigestMD5(bindDN, bindPassword, saslProperties, requestControls, responseControls);
    case SASL_MECHANISM_EXTERNAL:
      return doSASLExternal(bindDN, saslProperties, requestControls, responseControls);
    case SASL_MECHANISM_GSSAPI:
      return doSASLGSSAPI(bindDN, bindPassword, saslProperties, requestControls, responseControls);
    case SASL_MECHANISM_PLAIN:
      return doSASLPlain(bindDN, bindPassword, saslProperties, requestControls, responseControls);
    default:
      LocalizableMessage message = ERR_LDAPAUTH_UNSUPPORTED_SASL_MECHANISM.get(mechanism);
      throw new ClientException(
          ReturnCode.CLIENT_SIDE_AUTH_UNKNOWN, message);
      throw new ClientException(ReturnCode.CLIENT_SIDE_AUTH_UNKNOWN, message);
    }
  }
@@ -502,7 +425,7 @@
   * @throws  LDAPException  If the bind fails or some other server-side problem
   *                         occurs during processing.
   */
  public String doSASLAnonymous(ByteSequence bindDN,
  private String doSASLAnonymous(ByteSequence bindDN,
                     Map<String,List<String>> saslProperties,
                     List<Control> requestControls,
                     List<Control> responseControls)
@@ -510,151 +433,38 @@
  {
    String trace = null;
    // Evaluate the properties provided.  The only one we'll allow is the trace
    // property, but it is not required.
    if (saslProperties == null || saslProperties.isEmpty())
    // The only allowed property is the trace property, but it is not required.
    if (saslProperties != null)
    {
      // This is fine because there are no required properties for this mechanism.
    }
    else
      for (Entry<String, List<String>> entry : saslProperties.entrySet())
    {
      for (String name : saslProperties.keySet())
      {
        String name = entry.getKey();
        List<String> values = entry.getValue();
        if (name.equalsIgnoreCase(SASL_PROPERTY_TRACE))
        {
          // This is acceptable, and we'll take any single value.
          List<String> values = saslProperties.get(name);
          Iterator<String> iterator = values.iterator();
          if (iterator.hasNext())
          {
            trace = iterator.next();
            if (iterator.hasNext())
            {
              LocalizableMessage message = ERR_LDAPAUTH_TRACE_SINGLE_VALUED.get();
              throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
            }
          }
          trace = getSingleValue(values, ERR_LDAPAUTH_TRACE_SINGLE_VALUED);
        }
        else
        {
          LocalizableMessage message = ERR_LDAPAUTH_INVALID_SASL_PROPERTY.get(
              name, SASL_MECHANISM_ANONYMOUS);
          throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                                    message);
          throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
        }
      }
    }
    // Construct the bind request and send it to the server.
    ByteString saslCredentials;
    if (trace == null)
    {
      saslCredentials = null;
    }
    else
    {
      saslCredentials = ByteString.valueOfUtf8(trace);
    }
    ByteString saslCredentials = trace != null ? ByteString.valueOfUtf8(trace) : null;
    sendBindRequest(SASL_MECHANISM_ANONYMOUS, bindDN, saslCredentials, requestControls);
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(bindDN.toByteString(),
             SASL_MECHANISM_ANONYMOUS, saslCredentials);
    LDAPMessage requestMessage =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest,
                         requestControls);
    try
    {
      writer.writeMessage(requestMessage);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
          SASL_MECHANISM_ANONYMOUS, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
          SASL_MECHANISM_ANONYMOUS, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR, message, e);
    }
    // Read the response from the server.
    LDAPMessage responseMessage;
    try
    {
      responseMessage = reader.readMessage();
      if (responseMessage == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // See if there are any controls in the response.  If so, then add them to
    // the response controls list.
    List<Control> respControls = responseMessage.getControls();
    if (respControls != null && ! respControls.isEmpty())
    {
      responseControls.addAll(respControls);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    generateError(responseMessage);
    BindResponseProtocolOp bindResponse =
         responseMessage.getBindResponseProtocolOp();
    int resultCode = bindResponse.getResultCode();
    if (resultCode == ReturnCode.SUCCESS.get())
    {
      // FIXME -- Need to look for things like password expiration warning,
      // reset notice, etc.
    LDAPMessage responseMessage = readBindResponse(ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE);
    responseControls.addAll(responseMessage.getControls());
    checkConnected(responseMessage);
    checkSuccessfulBind(responseMessage, SASL_MECHANISM_ANONYMOUS);
      return null;
    }
    // FIXME -- Add support for referrals.
    LocalizableMessage message =
        ERR_LDAPAUTH_SASL_BIND_FAILED.get(SASL_MECHANISM_ANONYMOUS);
    throw new LDAPException(resultCode, bindResponse.getErrorMessage(),
                            message, bindResponse.getMatchedDN(), null);
  }
  /**
   * Retrieves the set of properties that a client may provide when performing a
   * SASL ANONYMOUS bind, mapped from the property names to their corresponding
@@ -664,7 +474,7 @@
   *          SASL ANONYMOUS bind, mapped from the property names to their
   *          corresponding descriptions.
   */
  public static LinkedHashMap<String, LocalizableMessage> getSASLAnonymousProperties()
  private static LinkedHashMap<String, LocalizableMessage> getSASLAnonymousProperties()
  {
    LinkedHashMap<String,LocalizableMessage> properties = new LinkedHashMap<>(1);
@@ -701,7 +511,7 @@
   * @throws  LDAPException  If the bind fails or some other server-side problem
   *                         occurs during processing.
   */
  public String doSASLCRAMMD5(ByteSequence bindDN,
  private String doSASLCRAMMD5(ByteSequence bindDN,
                     ByteSequence bindPassword,
                     Map<String,List<String>> saslProperties,
                     List<Control> requestControls,
@@ -721,20 +531,21 @@
              ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
    }
    for (String name : saslProperties.keySet())
    for (Entry<String, List<String>> entry : saslProperties.entrySet())
    {
      String name = entry.getKey();
      List<String> values = entry.getValue();
      String lowerName = toLowerCase(name);
      if (lowerName.equals(SASL_PROPERTY_AUTHID))
      {
        authID = getAuthID(saslProperties, authID, name);
        authID = getSingleValue(values, ERR_LDAPAUTH_AUTHID_SINGLE_VALUED);
      }
      else
      {
        LocalizableMessage message = ERR_LDAPAUTH_INVALID_SASL_PROPERTY.get(
            name, SASL_MECHANISM_CRAM_MD5);
        throw new ClientException(
                ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
        throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
      }
    }
@@ -755,108 +566,13 @@
        bindPassword = ByteString.empty();
    }
    sendInitialBindRequest(SASL_MECHANISM_CRAM_MD5, bindDN);
    // Construct the initial bind request to send to the server.  In this case,
    // we'll simply indicate that we want to use CRAM-MD5 so the server will
    // send us the challenge.
    BindRequestProtocolOp bindRequest1 =
         new BindRequestProtocolOp(bindDN.toByteString(),
             SASL_MECHANISM_CRAM_MD5, null);
    // FIXME -- Should we include request controls in both stages or just the
    // second stage?
    LDAPMessage requestMessage1 =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest1);
    LDAPMessage responseMessage1 =
        readBindResponse(ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE, SASL_MECHANISM_CRAM_MD5);
    checkConnected(responseMessage1);
    try
    {
      writer.writeMessage(requestMessage1);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_INITIAL_SASL_BIND.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_INITIAL_SASL_BIND.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR, message, e);
    }
    // Read the response from the server.
    LDAPMessage responseMessage1;
    try
    {
      responseMessage1 = reader.readMessage();
      if (responseMessage1 == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE.get(
              SASL_MECHANISM_CRAM_MD5, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    switch (responseMessage1.getProtocolOpType())
    {
      case OP_TYPE_BIND_RESPONSE:
        // We'll deal with this later.
        break;
      case OP_TYPE_EXTENDED_RESPONSE:
        ExtendedResponseProtocolOp extendedResponse =
             responseMessage1.getExtendedResponseProtocolOp();
        String responseOID = extendedResponse.getOID();
        if (responseOID != null &&
            responseOID.equals(OID_NOTICE_OF_DISCONNECTION))
        {
          LocalizableMessage message = ERR_LDAPAUTH_SERVER_DISCONNECT.
              get(extendedResponse.getResultCode(),
                  extendedResponse.getErrorMessage());
          throw new LDAPException(extendedResponse.getResultCode(), message);
        }
        else
        {
          LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_EXTENDED_RESPONSE.get(extendedResponse);
          throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
        }
      default:
        LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_RESPONSE.get(responseMessage1.getProtocolOp());
        throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
    // Make sure that the bind response has the "SASL bind in progress" result
    // code.
    // Make sure that the bind response has the "SASL bind in progress" result code.
    BindResponseProtocolOp bindResponse1 =
         responseMessage1.getBindResponseProtocolOp();
    int resultCode1 = bindResponse1.getResultCode();
@@ -885,157 +601,75 @@
      throw new LDAPException(ReturnCode.PROTOCOL_ERROR.get(), message);
    }
    // Use the provided password and credentials to generate the CRAM-MD5 response.
    String salsCredentials = authID + ' ' + generateCRAMMD5Digest(bindPassword, serverChallenge);
    sendSecondBindRequest(SASL_MECHANISM_CRAM_MD5, bindDN, salsCredentials, requestControls);
    // Use the provided password and credentials to generate the CRAM-MD5
    // response.
    StringBuilder buffer = new StringBuilder();
    buffer.append(authID);
    buffer.append(' ');
    buffer.append(generateCRAMMD5Digest(bindPassword, serverChallenge));
    LDAPMessage responseMessage2 =
        readBindResponse(ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE, SASL_MECHANISM_CRAM_MD5);
    responseControls.addAll(responseMessage2.getControls());
    checkConnected(responseMessage2);
    checkSuccessfulBind(responseMessage2, SASL_MECHANISM_CRAM_MD5);
    return null;
  }
    // Create and send the second bind request to the server.
    BindRequestProtocolOp bindRequest2 =
         new BindRequestProtocolOp(bindDN.toByteString(),
             SASL_MECHANISM_CRAM_MD5, ByteString.valueOfUtf8(buffer.toString()));
    LDAPMessage requestMessage2 =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest2,
                         requestControls);
  /**
   * Construct the initial bind request to send to the server. We'll simply indicate the SASL
   * mechanism we want to use so the server will send us the challenge.
   */
  private void sendInitialBindRequest(String saslMechanism, ByteSequence bindDN) throws ClientException
  {
    // FIXME -- Should we include request controls in both stages or just the second stage?
    BindRequestProtocolOp bindRequest = new BindRequestProtocolOp(bindDN.toByteString(), saslMechanism, null);
    LDAPMessage requestMessage = new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest);
    try
    {
      writer.writeMessage(requestMessage2);
      writer.writeMessage(requestMessage);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SECOND_SASL_BIND.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_SEND_INITIAL_SASL_BIND.get(saslMechanism, getExceptionMessage(ioe));
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SECOND_SASL_BIND.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_SEND_INITIAL_SASL_BIND.get(saslMechanism, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR, message, e);
    }
    }
    // Read the response from the server.
    LDAPMessage responseMessage2;
  private LDAPMessage readBindResponse(Arg2<Object, Object> errCannotReadBindResponse, String saslMechanism)
      throws ClientException
  {
    try
    {
      responseMessage2 = reader.readMessage();
      if (responseMessage2 == null)
      LDAPMessage responseMessage = reader.readMessage();
      if (responseMessage != null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
        return responseMessage;
      }
      LocalizableMessage message = ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message);
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE.get(
              SASL_MECHANISM_CRAM_MD5, getExceptionMessage(e));
      LocalizableMessage message = errCannotReadBindResponse.get(saslMechanism, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
      LocalizableMessage message = errCannotReadBindResponse.get(saslMechanism, getExceptionMessage(ioe));
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE.get(
          SASL_MECHANISM_CRAM_MD5, getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // See if there are any controls in the response.  If so, then add them to
    // the response controls list.
    List<Control> respControls = responseMessage2.getControls();
    if (respControls != null && ! respControls.isEmpty())
    {
      responseControls.addAll(respControls);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    switch (responseMessage2.getProtocolOpType())
    {
      case OP_TYPE_BIND_RESPONSE:
        // We'll deal with this later.
        break;
      case OP_TYPE_EXTENDED_RESPONSE:
        ExtendedResponseProtocolOp extendedResponse =
             responseMessage2.getExtendedResponseProtocolOp();
        String responseOID = extendedResponse.getOID();
        if (responseOID != null &&
            responseOID.equals(OID_NOTICE_OF_DISCONNECTION))
        {
          LocalizableMessage message = ERR_LDAPAUTH_SERVER_DISCONNECT.
              get(extendedResponse.getResultCode(),
                  extendedResponse.getErrorMessage());
          throw new LDAPException(extendedResponse.getResultCode(), message);
        }
        else
        {
          LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_EXTENDED_RESPONSE.get(extendedResponse);
          throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
        }
      default:
        LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_RESPONSE.get(responseMessage2.getProtocolOp());
        throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
    BindResponseProtocolOp bindResponse2 =
         responseMessage2.getBindResponseProtocolOp();
    int resultCode2 = bindResponse2.getResultCode();
    if (resultCode2 == ReturnCode.SUCCESS.get())
    {
      // FIXME -- Need to look for things like password expiration warning,
      // reset notice, etc.
      return null;
    }
    // FIXME -- Add support for referrals.
    LocalizableMessage message =
        ERR_LDAPAUTH_SASL_BIND_FAILED.get(SASL_MECHANISM_CRAM_MD5);
    throw new LDAPException(resultCode2, bindResponse2.getErrorMessage(),
                            message, bindResponse2.getMatchedDN(), null);
  }
  private String getAuthID(Map<String, List<String>> saslProperties, String authID, String name) throws ClientException
  {
    List<String> values = saslProperties.get(name);
    Iterator<String> iterator = values.iterator();
    if (iterator.hasNext())
    {
      authID = iterator.next();
      if (iterator.hasNext())
      {
        LocalizableMessage message = ERR_LDAPAUTH_AUTHID_SINGLE_VALUED.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
      LocalizableMessage message = errCannotReadBindResponse.get(saslMechanism, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
      }
    }
    return authID;
  }
  /**
   * Generates the appropriate HMAC-MD5 digest for a CRAM-MD5 authentication
@@ -1141,7 +775,7 @@
   *          SASL CRAM-MD5 bind, mapped from the property names to their
   *          corresponding descriptions.
   */
  public static LinkedHashMap<String,LocalizableMessage> getSASLCRAMMD5Properties()
  private static LinkedHashMap<String, LocalizableMessage> getSASLCRAMMD5Properties()
  {
    LinkedHashMap<String,LocalizableMessage> properties = new LinkedHashMap<>(1);
@@ -1178,7 +812,7 @@
   * @throws  LDAPException  If the bind fails or some other server-side problem
   *                         occurs during processing.
   */
  public String doSASLDigestMD5(ByteSequence bindDN,
  private String doSASLDigestMD5(ByteSequence bindDN,
                     ByteSequence bindPassword,
                     Map<String,List<String>> saslProperties,
                     List<Control> requestControls,
@@ -1202,17 +836,18 @@
      throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
    }
    for (String name : saslProperties.keySet())
    for (Entry<String, List<String>> entry : saslProperties.entrySet())
    {
      String name = entry.getKey();
      List<String> values = entry.getValue();
      String lowerName = toLowerCase(name);
      if (lowerName.equals(SASL_PROPERTY_AUTHID))
      {
        authID = getAuthID(saslProperties, authID, name);
        authID = getSingleValue(values, ERR_LDAPAUTH_AUTHID_SINGLE_VALUED);
      }
      else if (lowerName.equals(SASL_PROPERTY_REALM))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
@@ -1229,7 +864,6 @@
      }
      else if (lowerName.equals(SASL_PROPERTY_QOP))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
@@ -1264,42 +898,17 @@
      }
      else if (lowerName.equals(SASL_PROPERTY_DIGEST_URI))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
          digestURI = toLowerCase(iterator.next());
          if (iterator.hasNext())
          {
            LocalizableMessage message = ERR_LDAPAUTH_DIGEST_URI_SINGLE_VALUED.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                                      message);
          }
        }
        digestURI = toLowerCase(getSingleValue(values, ERR_LDAPAUTH_DIGEST_URI_SINGLE_VALUED));
      }
      else if (lowerName.equals(SASL_PROPERTY_AUTHZID))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
          authzID = toLowerCase(iterator.next());
          if (iterator.hasNext())
          {
            LocalizableMessage message = ERR_LDAPAUTH_AUTHZID_SINGLE_VALUED.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                                      message);
          }
        }
        authzID = toLowerCase(getSingleValue(values, ERR_LDAPAUTH_AUTHZID_SINGLE_VALUED));
      }
      else
      {
        LocalizableMessage message = ERR_LDAPAUTH_INVALID_SASL_PROPERTY.get(
            name, SASL_MECHANISM_DIGEST_MD5);
        throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                message);
        throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
      }
    }
@@ -1321,108 +930,13 @@
    }
    // Construct the initial bind request to send to the server.  In this case,
    // we'll simply indicate that we want to use DIGEST-MD5 so the server will
    // send us the challenge.
    BindRequestProtocolOp bindRequest1 =
         new BindRequestProtocolOp(bindDN.toByteString(),
             SASL_MECHANISM_DIGEST_MD5, null);
    // FIXME -- Should we include request controls in both stages or just the
    // second stage?
    LDAPMessage requestMessage1 =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest1);
    sendInitialBindRequest(SASL_MECHANISM_DIGEST_MD5, bindDN);
    try
    {
      writer.writeMessage(requestMessage1);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_INITIAL_SASL_BIND.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_INITIAL_SASL_BIND.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR,
                                message, e);
    }
    LDAPMessage responseMessage1 =
        readBindResponse(ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE, SASL_MECHANISM_DIGEST_MD5);
    checkConnected(responseMessage1);
    // Read the response from the server.
    LDAPMessage responseMessage1;
    try
    {
      responseMessage1 = reader.readMessage();
      if (responseMessage1 == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE.get(
              SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_INITIAL_BIND_RESPONSE.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    switch (responseMessage1.getProtocolOpType())
    {
      case OP_TYPE_BIND_RESPONSE:
        // We'll deal with this later.
        break;
      case OP_TYPE_EXTENDED_RESPONSE:
        ExtendedResponseProtocolOp extendedResponse =
             responseMessage1.getExtendedResponseProtocolOp();
        String responseOID = extendedResponse.getOID();
        if (responseOID != null &&
            responseOID.equals(OID_NOTICE_OF_DISCONNECTION))
        {
          LocalizableMessage message = ERR_LDAPAUTH_SERVER_DISCONNECT.
              get(extendedResponse.getResultCode(),
                  extendedResponse.getErrorMessage());
          throw new LDAPException(extendedResponse.getResultCode(), message);
        }
        else
        {
          LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_EXTENDED_RESPONSE.get(extendedResponse);
          throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
        }
      default:
        LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_RESPONSE.get(responseMessage1.getProtocolOp());
        throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
    // Make sure that the bind response has the "SASL bind in progress" result
    // code.
    // Make sure that the bind response has the "SASL bind in progress" result code.
    BindResponseProtocolOp bindResponse1 =
         responseMessage1.getBindResponseProtocolOp();
    int resultCode1 = bindResponse1.getResultCode();
@@ -1591,163 +1105,33 @@
    // Generate the SASL credentials for the second bind request.
    StringBuilder credBuffer = new StringBuilder();
    credBuffer.append("username=\"");
    credBuffer.append(authID);
    credBuffer.append("\"");
    credBuffer.append("username=\"").append(authID).append("\"");
    if (realm != null)
    {
      credBuffer.append(",realm=\"");
      credBuffer.append(realm);
      credBuffer.append("\"");
      credBuffer.append(",realm=\"").append(realm).append("\"");
    }
    credBuffer.append(",nonce=\"");
    credBuffer.append(nonce);
    credBuffer.append("\",cnonce=\"");
    credBuffer.append(cnonce);
    credBuffer.append("\",nc=");
    credBuffer.append(nonceCount);
    credBuffer.append(",qop=");
    credBuffer.append(qop);
    credBuffer.append(",digest-uri=\"");
    credBuffer.append(digestURI);
    credBuffer.append("\",response=");
    credBuffer.append(responseDigest);
    credBuffer.append(",nonce=\"").append(nonce);
    credBuffer.append("\",cnonce=\"").append(cnonce);
    credBuffer.append("\",nc=").append(nonceCount);
    credBuffer.append(",qop=").append(qop);
    credBuffer.append(",digest-uri=\"").append(digestURI);
    credBuffer.append("\",response=").append(responseDigest);
    if (useUTF8)
    {
      credBuffer.append(",charset=utf-8");
    }
    if (authzID != null)
    {
      credBuffer.append(",authzid=\"");
      credBuffer.append(authzID);
      credBuffer.append("\"");
      credBuffer.append(",authzid=\"").append(authzID).append("\"");
    }
    sendSecondBindRequest(SASL_MECHANISM_DIGEST_MD5, bindDN, credBuffer.toString(), requestControls);
    // Generate and send the second bind request.
    BindRequestProtocolOp bindRequest2 =
         new BindRequestProtocolOp(bindDN.toByteString(),
             SASL_MECHANISM_DIGEST_MD5,
             ByteString.valueOfUtf8(credBuffer.toString()));
    LDAPMessage requestMessage2 =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest2,
                         requestControls);
    try
    {
      writer.writeMessage(requestMessage2);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SECOND_SASL_BIND.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SECOND_SASL_BIND.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR,
                                message, e);
    }
    // Read the response from the server.
    LDAPMessage responseMessage2;
    try
    {
      responseMessage2 = reader.readMessage();
      if (responseMessage2 == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE.get(
              SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE.get(
          SASL_MECHANISM_DIGEST_MD5, getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // See if there are any controls in the response.  If so, then add them to
    // the response controls list.
    List<Control> respControls = responseMessage2.getControls();
    if (respControls != null && ! respControls.isEmpty())
    {
      responseControls.addAll(respControls);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    switch (responseMessage2.getProtocolOpType())
    {
      case OP_TYPE_BIND_RESPONSE:
        // We'll deal with this later.
        break;
      case OP_TYPE_EXTENDED_RESPONSE:
        ExtendedResponseProtocolOp extendedResponse =
             responseMessage2.getExtendedResponseProtocolOp();
        String responseOID = extendedResponse.getOID();
        if (responseOID != null &&
            responseOID.equals(OID_NOTICE_OF_DISCONNECTION))
        {
          LocalizableMessage message = ERR_LDAPAUTH_SERVER_DISCONNECT.
              get(extendedResponse.getResultCode(),
                  extendedResponse.getErrorMessage());
          throw new LDAPException(extendedResponse.getResultCode(), message);
        }
        else
        {
          LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_EXTENDED_RESPONSE.get(extendedResponse);
          throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
        }
      default:
        LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_RESPONSE.get(responseMessage2.getProtocolOp());
        throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
    BindResponseProtocolOp bindResponse2 =
         responseMessage2.getBindResponseProtocolOp();
    int resultCode2 = bindResponse2.getResultCode();
    if (resultCode2 != ReturnCode.SUCCESS.get())
    {
      // FIXME -- Add support for referrals.
      LocalizableMessage message =
          ERR_LDAPAUTH_SASL_BIND_FAILED.get(SASL_MECHANISM_DIGEST_MD5);
      throw new LDAPException(resultCode2, bindResponse2.getErrorMessage(),
                              message, bindResponse2.getMatchedDN(),
                              null);
    }
    LDAPMessage responseMessage2 =
        readBindResponse(ERR_LDAPAUTH_CANNOT_READ_SECOND_BIND_RESPONSE, SASL_MECHANISM_DIGEST_MD5);
    responseControls.addAll(responseMessage2.getControls());
    checkConnected(responseMessage2);
    BindResponseProtocolOp bindResponse2 = checkSuccessfulBind(responseMessage2, SASL_MECHANISM_DIGEST_MD5);
    // Make sure that the bind response included server SASL credentials with
@@ -1802,12 +1186,34 @@
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
    // FIXME -- Need to look for things like password expiration warning,
    // reset notice, etc.
    // FIXME -- Need to look for things like password expiration warning, reset notice, etc.
    return null;
  }
  private void sendSecondBindRequest(String saslMechanism, ByteSequence bindDN, String saslCredentials,
      List<Control> requestControls) throws ClientException
  {
    // Generate and send the second bind request.
    BindRequestProtocolOp bindRequest2 =
        new BindRequestProtocolOp(bindDN.toByteString(), saslMechanism, ByteString.valueOfUtf8(saslCredentials));
    LDAPMessage requestMessage2 = new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest2, requestControls);
    try
    {
      writer.writeMessage(requestMessage2);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_SEND_SECOND_SASL_BIND.get(saslMechanism, getExceptionMessage(ioe));
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SECOND_SASL_BIND.get(saslMechanism, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR, message, e);
    }
  }
  /**
   * Reads the next token from the provided credentials string using the
@@ -1891,14 +1297,11 @@
      {
        // If this is a quoted string, then this comma is part of the token.
        // Otherwise, it's the end of the token.
        if (isQuoted)
        {
          token.append(c);
        }
        else
        if (!isQuoted)
        {
          break;
        }
        token.append(c);
      }
      else if (c == '"')
      {
@@ -1911,8 +1314,6 @@
            // We have hit the end of the string, so this is fine.
            break;
          }
          else
          {
            char c2 = credentials.charAt(pos++);
            if (c2 == ',')
            {
@@ -1921,13 +1322,9 @@
            }
            else
            {
              // We found the closing quote before the end of the token.  This
              // is not fine.
              LocalizableMessage message =
                  ERR_LDAPAUTH_DIGESTMD5_INVALID_CLOSING_QUOTE_POS.get(pos-2);
              throw new LDAPException(ReturnCode.INVALID_CREDENTIALS.get(),
                                      message);
            }
            // We found the closing quote before the end of the token. This is not fine.
            LocalizableMessage message = ERR_LDAPAUTH_DIGESTMD5_INVALID_CLOSING_QUOTE_POS.get(pos - 2);
            throw new LDAPException(ReturnCode.INVALID_CREDENTIALS.get(), message);
          }
        }
        else
@@ -2028,21 +1425,14 @@
      }
    }
    // Get a hash of "username:realm:password".
    StringBuilder a1String1 = new StringBuilder();
    a1String1.append(authID);
    a1String1.append(':');
    a1String1.append((realm == null) ? "" : realm);
    a1String1.append(':');
    byte[] a1Bytes1a = a1String1.toString().getBytes(charset);
    String a1String1 = authID + ':' + ((realm == null) ? "" : realm) + ':';
    byte[] a1Bytes1a = a1String1.getBytes(charset);
    byte[] a1Bytes1  = new byte[a1Bytes1a.length + password.length()];
    System.arraycopy(a1Bytes1a, 0, a1Bytes1, 0, a1Bytes1a.length);
    password.copyTo(a1Bytes1, a1Bytes1a.length);
    byte[] urpHash = md5Digest.digest(a1Bytes1);
    // Next, get a hash of "urpHash:nonce:cnonce[:authzid]".
    StringBuilder a1String2 = new StringBuilder();
    a1String2.append(':');
@@ -2060,37 +1450,20 @@
    System.arraycopy(a1Bytes2a, 0, a1Bytes2, urpHash.length, a1Bytes2a.length);
    byte[] a1Hash = md5Digest.digest(a1Bytes2);
    // Next, get a hash of "AUTHENTICATE:digesturi".
    byte[] a2Bytes = ("AUTHENTICATE:" + digestURI).getBytes(charset);
    byte[] a2Hash  = md5Digest.digest(a2Bytes);
    // Get hex string representations of the last two hashes.
    String a1HashHex = getHexString(a1Hash);
    String a2HashHex = getHexString(a2Hash);
    // Put together the final string to hash, consisting of
    // "a1HashHex:nonce:nonceCount:cnonce:qop:a2HashHex" and get its digest.
    StringBuilder kdStr = new StringBuilder();
    kdStr.append(a1HashHex);
    kdStr.append(':');
    kdStr.append(nonce);
    kdStr.append(':');
    kdStr.append(nonceCount);
    kdStr.append(':');
    kdStr.append(cnonce);
    kdStr.append(':');
    kdStr.append(qop);
    kdStr.append(':');
    kdStr.append(a2HashHex);
    return getHexString(md5Digest.digest(kdStr.toString().getBytes(charset)));
    String kdStr = a1HashHex + ':' + nonce + ':' + nonceCount + ':' + cnonce + ':' + qop + ':' + a2HashHex;
    return getHexString(md5Digest.digest(kdStr.getBytes(charset)));
  }
  /**
   * Generates the appropriate DIGEST-MD5 rspauth digest using the provided
   * information.
@@ -2118,7 +1491,7 @@
   * @throws  UnsupportedEncodingException  If the specified character set is
   *                                        invalid for some reason.
   */
  public byte[] generateDigestMD5RspAuth(String authID, String authzID,
  private byte[] generateDigestMD5RspAuth(String authID, String authzID,
                                         ByteSequence password, String realm,
                                         String nonce, String cnonce,
                                         String nonceCount, String digestURI,
@@ -2126,13 +1499,9 @@
         throws UnsupportedEncodingException
  {
    // First, get a hash of "username:realm:password".
    StringBuilder a1String1 = new StringBuilder();
    a1String1.append(authID);
    a1String1.append(':');
    a1String1.append(realm);
    a1String1.append(':');
    String a1String1 = authID + ':' + realm + ':';
    byte[] a1Bytes1a = a1String1.toString().getBytes(charset);
    byte[] a1Bytes1a = a1String1.getBytes(charset);
    byte[] a1Bytes1  = new byte[a1Bytes1a.length + password.length()];
    System.arraycopy(a1Bytes1a, 0, a1Bytes1, 0, a1Bytes1a.length);
    password.copyTo(a1Bytes1, a1Bytes1a.length);
@@ -2172,26 +1541,12 @@
    String a1HashHex = getHexString(a1Hash);
    String a2HashHex = getHexString(a2Hash);
    // Put together the final string to hash, consisting of
    // "a1HashHex:nonce:nonceCount:cnonce:qop:a2HashHex" and get its digest.
    StringBuilder kdStr = new StringBuilder();
    kdStr.append(a1HashHex);
    kdStr.append(':');
    kdStr.append(nonce);
    kdStr.append(':');
    kdStr.append(nonceCount);
    kdStr.append(':');
    kdStr.append(cnonce);
    kdStr.append(':');
    kdStr.append(qop);
    kdStr.append(':');
    kdStr.append(a2HashHex);
    return md5Digest.digest(kdStr.toString().getBytes(charset));
    String kdStr = a1HashHex + ':' + nonce + ':' + nonceCount + ':' + cnonce + ':' + qop + ':' + a2HashHex;
    return md5Digest.digest(kdStr.getBytes(charset));
  }
  /**
   * Retrieves a hexadecimal string representation of the contents of the
   * provided byte array.
@@ -2224,7 +1579,7 @@
   *          SASL DIGEST-MD5 bind, mapped from the property names to their
   *          corresponding descriptions.
   */
  public static LinkedHashMap<String,LocalizableMessage> getSASLDigestMD5Properties()
  private static LinkedHashMap<String, LocalizableMessage> getSASLDigestMD5Properties()
  {
    LinkedHashMap<String,LocalizableMessage> properties = new LinkedHashMap<>(5);
@@ -2285,111 +1640,11 @@
    }
    // Construct the bind request and send it to the server.
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(bindDN.toByteString(),
             SASL_MECHANISM_EXTERNAL, null);
    LDAPMessage requestMessage =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest,
                         requestControls);
    sendBindRequest(SASL_MECHANISM_EXTERNAL, bindDN, null, requestControls);
    try
    {
      writer.writeMessage(requestMessage);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
          SASL_MECHANISM_EXTERNAL, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
          SASL_MECHANISM_EXTERNAL, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR,
                                message, e);
    }
    // Read the response from the server.
    LDAPMessage responseMessage;
    try
    {
      responseMessage = reader.readMessage();
      if (responseMessage == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // See if there are any controls in the response.  If so, then add them to
    // the response controls list.
    List<Control> respControls = responseMessage.getControls();
    if (respControls != null && ! respControls.isEmpty())
    {
      responseControls.addAll(respControls);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    switch (responseMessage.getProtocolOpType())
    {
      case OP_TYPE_BIND_RESPONSE:
        // We'll deal with this later.
        break;
      case OP_TYPE_EXTENDED_RESPONSE:
        ExtendedResponseProtocolOp extendedResponse =
             responseMessage.getExtendedResponseProtocolOp();
        String responseOID = extendedResponse.getOID();
        if (responseOID != null &&
            responseOID.equals(OID_NOTICE_OF_DISCONNECTION))
        {
          LocalizableMessage message = ERR_LDAPAUTH_SERVER_DISCONNECT.
              get(extendedResponse.getResultCode(),
                  extendedResponse.getErrorMessage());
          throw new LDAPException(extendedResponse.getResultCode(), message);
        }
        else
        {
          LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_EXTENDED_RESPONSE.get(extendedResponse);
          throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
        }
      default:
        LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_RESPONSE.get(responseMessage.getProtocolOp());
        throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
    LDAPMessage responseMessage = readBindResponse(ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE);
    responseControls.addAll(responseMessage.getControls());
    checkConnected(responseMessage);
    BindResponseProtocolOp bindResponse =
         responseMessage.getBindResponseProtocolOp();
@@ -2409,7 +1664,57 @@
                            message, bindResponse.getMatchedDN(), null);
  }
  private void sendBindRequest(String saslMechanism, ByteSequence bindDN, ByteString saslCredentials,
      List<Control> requestControls) throws ClientException
  {
    BindRequestProtocolOp bindRequest =
        new BindRequestProtocolOp(bindDN.toByteString(), saslMechanism, saslCredentials);
    LDAPMessage requestMessage = new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest, requestControls);
    try
    {
      writer.writeMessage(requestMessage);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(saslMechanism, getExceptionMessage(ioe));
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(saslMechanism, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR, message, e);
    }
  }
  private LDAPMessage readBindResponse(Arg1<Object> errCannotReadBindResponse) throws ClientException
  {
    try
    {
      LDAPMessage responseMessage = reader.readMessage();
      if (responseMessage != null)
      {
        return responseMessage;
      }
      LocalizableMessage message = ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message);
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message = errCannotReadBindResponse.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = errCannotReadBindResponse.get(getExceptionMessage(ioe));
      throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = errCannotReadBindResponse.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
  }
  /**
   * Retrieves the set of properties that a client may provide when performing a
@@ -2420,7 +1725,7 @@
   *          SASL EXTERNAL bind, mapped from the property names to their
   *          corresponding descriptions.
   */
  public static LinkedHashMap<String,LocalizableMessage> getSASLExternalProperties()
  private static LinkedHashMap<String, LocalizableMessage> getSASLExternalProperties()
  {
    // There are no properties for the SASL EXTERNAL mechanism.
    return new LinkedHashMap<>(0);
@@ -2455,7 +1760,7 @@
   * @throws  LDAPException  If the bind fails or some other server-side problem
   *                         occurs during processing.
   */
  public String doSASLGSSAPI(ByteSequence bindDN,
  private String doSASLGSSAPI(ByteSequence bindDN,
                     ByteSequence bindPassword,
                     Map<String,List<String>> saslProperties,
                     List<Control> requestControls,
@@ -2469,16 +1774,7 @@
    gssapiAuthID  = null;
    gssapiAuthzID = null;
    gssapiQoP     = "auth";
    if (bindPassword == null)
    {
      gssapiAuthPW = null;
    }
    else
    {
      gssapiAuthPW = bindPassword.toString().toCharArray();
    }
    gssapiAuthPW = bindPassword != null ? bindPassword.toString().toCharArray() : null;
    // Evaluate the properties provided.  The authID is required.  The authzID,
    // KDC, QoP, and realm are optional.
@@ -2490,60 +1786,26 @@
              ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
    }
    for (String name : saslProperties.keySet())
    for (Entry<String, List<String>> entry : saslProperties.entrySet())
    {
      String name = entry.getKey();
      String lowerName = toLowerCase(name);
      List<String> values = entry.getValue();
      if (lowerName.equals(SASL_PROPERTY_AUTHID))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
          gssapiAuthID = iterator.next();
          if (iterator.hasNext())
          {
            LocalizableMessage message = ERR_LDAPAUTH_AUTHID_SINGLE_VALUED.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
          }
        }
        gssapiAuthID = getSingleValue(values, ERR_LDAPAUTH_AUTHID_SINGLE_VALUED);
      }
      else if (lowerName.equals(SASL_PROPERTY_AUTHZID))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
          gssapiAuthzID = iterator.next();
          if (iterator.hasNext())
          {
            LocalizableMessage message = ERR_LDAPAUTH_AUTHZID_SINGLE_VALUED.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                                      message);
          }
        }
        gssapiAuthzID = getSingleValue(values, ERR_LDAPAUTH_AUTHZID_SINGLE_VALUED);
      }
      else if (lowerName.equals(SASL_PROPERTY_KDC))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
          kdc = iterator.next();
          if (iterator.hasNext())
          {
            LocalizableMessage message = ERR_LDAPAUTH_KDC_SINGLE_VALUED.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                                      message);
          }
        }
        kdc = getSingleValue(values, ERR_LDAPAUTH_KDC_SINGLE_VALUED);
      }
      else if (lowerName.equals(SASL_PROPERTY_QOP))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
@@ -2580,19 +1842,7 @@
      }
      else if (lowerName.equals(SASL_PROPERTY_REALM))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
          realm = iterator.next();
          if (iterator.hasNext())
          {
            LocalizableMessage message = ERR_LDAPAUTH_REALM_SINGLE_VALUED.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                                      message);
          }
        }
        realm = getSingleValue(values, ERR_LDAPAUTH_REALM_SINGLE_VALUED);
      }
      else
      {
@@ -2645,8 +1895,7 @@
      File tempFile = File.createTempFile("login", "conf");
      configFileName = tempFile.getAbsolutePath();
      tempFile.deleteOnExit();
      BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false));
      try (BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false))) {
      w.write(getClass().getName() + " {");
      w.newLine();
@@ -2656,9 +1905,7 @@
      w.write("};");
      w.newLine();
      w.flush();
      w.close();
      }
    }
    catch (Exception e)
    {
@@ -2710,13 +1957,25 @@
    }
    // FIXME --  Need to make sure we handle request and response controls
    // properly, and also check for any possible message to send back to the
    // client.
    // FIXME -- Need to make sure we handle request and response controls properly,
    // and also check for any possible message to send back to the client.
    return null;
  }
  private String getSingleValue(List<String> values, Arg0 singleValuedErrMsg) throws ClientException
  {
    Iterator<String> it = values.iterator();
    if (it.hasNext())
    {
      String result = it.next();
      if (it.hasNext())
      {
        throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, singleValuedErrMsg.get());
      }
      return result;
    }
    return null;
  }
  /**
   * Retrieves the set of properties that a client may provide when performing a
@@ -2727,7 +1986,7 @@
   *          SASL EXTERNAL bind, mapped from the property names to their
   *          corresponding descriptions.
   */
  public static LinkedHashMap<String,LocalizableMessage> getSASLGSSAPIProperties()
  private static LinkedHashMap<String, LocalizableMessage> getSASLGSSAPIProperties()
  {
    LinkedHashMap<String,LocalizableMessage> properties = new LinkedHashMap<>(4);
@@ -2791,29 +2050,19 @@
              ReturnCode.CLIENT_SIDE_PARAM_ERROR, message);
    }
    for (String name : saslProperties.keySet())
    for (Entry<String, List<String>> entry : saslProperties.entrySet())
    {
      String name = entry.getKey();
      List<String> values = entry.getValue();
      String lowerName = toLowerCase(name);
      if (lowerName.equals(SASL_PROPERTY_AUTHID))
      {
        authID = getAuthID(saslProperties, authID, name);
        authID = getSingleValue(values, ERR_LDAPAUTH_AUTHID_SINGLE_VALUED);
      }
      else if (lowerName.equals(SASL_PROPERTY_AUTHZID))
      {
        List<String> values = saslProperties.get(name);
        Iterator<String> iterator = values.iterator();
        if (iterator.hasNext())
        {
          authzID = iterator.next();
          if (iterator.hasNext())
          {
            LocalizableMessage message = ERR_LDAPAUTH_AUTHZID_SINGLE_VALUED.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR,
                                      message);
          }
        }
        authzID = getSingleValue(values, ERR_LDAPAUTH_AUTHZID_SINGLE_VALUED);
      }
      else
      {
@@ -2841,116 +2090,17 @@
        bindPassword = ByteString.empty();
    }
    // Construct the bind request and send it to the server.
    StringBuilder credBuffer = new StringBuilder();
    if (authzID != null)
    {
      credBuffer.append(authzID);
    }
    credBuffer.append('\u0000');
    credBuffer.append(authID);
    credBuffer.append('\u0000');
    credBuffer.append(bindPassword.toString());
    String saslCredentials = (authzID != null ? authzID : "") + '\u0000' + authID + '\u0000' + bindPassword;
    sendBindRequest(SASL_MECHANISM_PLAIN, bindDN, ByteString.valueOfUtf8(saslCredentials), requestControls);
    ByteString saslCredentials =
        ByteString.valueOfUtf8(credBuffer.toString());
    BindRequestProtocolOp bindRequest =
         new BindRequestProtocolOp(bindDN.toByteString(), SASL_MECHANISM_PLAIN,
                                saslCredentials);
    LDAPMessage requestMessage =
         new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest,
                         requestControls);
    try
    {
      writer.writeMessage(requestMessage);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
          SASL_MECHANISM_PLAIN, getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
          SASL_MECHANISM_PLAIN, getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR,
                                message, e);
    }
    // Read the response from the server.
    LDAPMessage responseMessage;
    try
    {
      responseMessage = reader.readMessage();
      if (responseMessage == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    // See if there are any controls in the response.  If so, then add them to
    // the response controls list.
    List<Control> respControls = responseMessage.getControls();
    if (respControls != null && !respControls.isEmpty())
    {
      responseControls.addAll(respControls);
    }
    // Look at the protocol op from the response.  If it's a bind response, then
    // continue.  If it's an extended response, then it could be a notice of
    // disconnection so check for that.  Otherwise, generate an error.
    generateError(responseMessage);
    BindResponseProtocolOp bindResponse =
         responseMessage.getBindResponseProtocolOp();
    int resultCode = bindResponse.getResultCode();
    if (resultCode == ReturnCode.SUCCESS.get())
    {
      // FIXME -- Need to look for things like password expiration warning,
      // reset notice, etc.
    LDAPMessage responseMessage = readBindResponse(ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE);
    responseControls.addAll(responseMessage.getControls());
    checkConnected(responseMessage);
    checkSuccessfulBind(responseMessage, SASL_MECHANISM_PLAIN);
      return null;
    }
    // FIXME -- Add support for referrals.
    LocalizableMessage message = ERR_LDAPAUTH_SASL_BIND_FAILED.get(SASL_MECHANISM_PLAIN);
    throw new LDAPException(resultCode, bindResponse.getErrorMessage(),
                            message, bindResponse.getMatchedDN(), null);
  }
  /**
   * Retrieves the set of properties that a client may provide when performing a
   * SASL PLAIN bind, mapped from the property names to their corresponding
@@ -2960,7 +2110,7 @@
   *          SASL PLAIN bind, mapped from the property names to their
   *          corresponding descriptions.
   */
  public static LinkedHashMap<String,LocalizableMessage> getSASLPlainProperties()
  private static LinkedHashMap<String, LocalizableMessage> getSASLPlainProperties()
  {
    LinkedHashMap<String,LocalizableMessage> properties = new LinkedHashMap<>(2);
@@ -2988,8 +2138,7 @@
   *                         processing.
   */
  @Override
  public Object run()
         throws ClientException, LDAPException
  public Object run() throws ClientException, LDAPException
  {
    if (saslMechanism == null)
    {
@@ -2999,8 +2148,21 @@
    }
    else if (saslMechanism.equals(SASL_MECHANISM_GSSAPI))
    {
      doSASLGSSAPI2();
      return null;
    }
    else
    {
      LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_RUN_INVOCATION.get(
          saslMechanism, getBacktrace());
      throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
  }
  private void doSASLGSSAPI2() throws ClientException, LDAPException
  {
      // Create the property map that will be used by the internal SASL handler.
      HashMap<String,String> saslProperties = new HashMap<>();
    Map<String, String> saslProperties = new HashMap<>();
      saslProperties.put(Sasl.QOP, gssapiQoP);
      saslProperties.put(Sasl.SERVER_AUTH, "true");
@@ -3023,101 +2185,13 @@
                ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
      }
      // Get the SASL credentials to include in the initial bind request.
      ByteString saslCredentials;
      if (saslClient.hasInitialResponse())
      {
        try
        {
          byte[] credBytes = saslClient.evaluateChallenge(new byte[0]);
          saslCredentials = ByteString.wrap(credBytes);
        }
        catch (Exception e)
        {
          LocalizableMessage message = ERR_LDAPAUTH_GSSAPI_CANNOT_CREATE_INITIAL_CHALLENGE.
              get(getExceptionMessage(e));
          throw new ClientException(
                  ReturnCode.CLIENT_SIDE_LOCAL_ERROR,
                                    message, e);
        }
      }
      else
      {
        saslCredentials = null;
      }
      BindRequestProtocolOp bindRequest =
           new BindRequestProtocolOp(gssapiBindDN.toByteString(),
               SASL_MECHANISM_GSSAPI, saslCredentials);
      // FIXME -- Add controls here?
      LDAPMessage requestMessage =
           new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest);
    ByteString saslCredentials = getSaslCredentialsForInitialBind(saslClient);
    sendBindRequest(SASL_MECHANISM_GSSAPI, gssapiBindDN, saslCredentials, null);
      try
      {
        writer.writeMessage(requestMessage);
      }
      catch (IOException ioe)
      {
        LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
            SASL_MECHANISM_GSSAPI, getExceptionMessage(ioe));
        throw new ClientException(
                ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
      }
      catch (Exception e)
      {
        LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
            SASL_MECHANISM_GSSAPI, getExceptionMessage(e));
        throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR,
                                  message, e);
      }
      // Read the response from the server.
      LDAPMessage responseMessage;
      try
      {
        responseMessage = reader.readMessage();
        if (responseMessage == null)
        {
          LocalizableMessage message =
              ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
          throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                    message);
        }
      }
      catch (DecodeException | LDAPException e)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
        throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
      }
      catch (IOException ioe)
      {
        LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(
            getExceptionMessage(ioe));
        throw new ClientException(
                ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
      }
      catch (Exception e)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
        throw new ClientException(
                ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
      }
    LDAPMessage responseMessage = readBindResponse(ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE);
      // FIXME -- Handle response controls.
      // Look at the protocol op from the response.  If it's a bind response,
      // then continue.  If it's an extended response, then it could be a notice
      // of disconnection so check for that.  Otherwise, generate an error.
      generateError(responseMessage);
    checkConnected(responseMessage);
      while (true)
      {
@@ -3126,137 +2200,18 @@
        int resultCode = bindResponse.getResultCode();
        if (resultCode == ReturnCode.SUCCESS.get())
        {
          // We should be done after this, but we still need to look for and
          // handle the server SASL credentials.
          ByteString serverSASLCredentials =
               bindResponse.getServerSASLCredentials();
          if (serverSASLCredentials != null)
          {
            try
            {
              saslClient.evaluateChallenge(serverSASLCredentials.toByteArray());
            }
            catch (Exception e)
            {
              LocalizableMessage message =
                  ERR_LDAPAUTH_GSSAPI_CANNOT_VALIDATE_SERVER_CREDS.
                    get(getExceptionMessage(e));
              throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR,
                                        message, e);
            }
          }
          // Just to be sure, check that the login really is complete.
          if (! saslClient.isComplete())
          {
            LocalizableMessage message =
                ERR_LDAPAUTH_GSSAPI_UNEXPECTED_SUCCESS_RESPONSE.get();
            throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR,
                                      message);
          }
        evaluateGSSAPIChallenge(saslClient, bindResponse);
          break;
        }
        else if (resultCode == ReturnCode.SASL_BIND_IN_PROGRESS.get())
        {
          // Read the response and process the server SASL credentials.
          ByteString serverSASLCredentials =
               bindResponse.getServerSASLCredentials();
          byte[] credBytes;
          try
          {
            if (serverSASLCredentials == null)
            {
              credBytes = saslClient.evaluateChallenge(new byte[0]);
            }
            else
            {
              credBytes = saslClient.evaluateChallenge(
                  serverSASLCredentials.toByteArray());
            }
          }
          catch (Exception e)
          {
            LocalizableMessage message = ERR_LDAPAUTH_GSSAPI_CANNOT_VALIDATE_SERVER_CREDS.
                get(getExceptionMessage(e));
            throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR,
                                      message, e);
          }
          // Send the next bind in the sequence to the server.
          bindRequest =
               new BindRequestProtocolOp(gssapiBindDN.toByteString(),
                   SASL_MECHANISM_GSSAPI, ByteString.wrap(credBytes));
          // FIXME -- Add controls here?
          requestMessage =
               new LDAPMessage(nextMessageID.getAndIncrement(), bindRequest);
        ByteString credBytes = evaluateSaslChallenge(saslClient, bindResponse);
        sendBindRequest(SASL_MECHANISM_GSSAPI, gssapiBindDN, credBytes, null);
          try
          {
            writer.writeMessage(requestMessage);
          }
          catch (IOException ioe)
          {
            LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
                SASL_MECHANISM_GSSAPI, getExceptionMessage(ioe));
            throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                      message, ioe);
          }
          catch (Exception e)
          {
            LocalizableMessage message = ERR_LDAPAUTH_CANNOT_SEND_SASL_BIND.get(
                SASL_MECHANISM_GSSAPI, getExceptionMessage(e));
            throw new ClientException(ReturnCode.CLIENT_SIDE_ENCODING_ERROR,
                                      message, e);
          }
          // Read the response from the server.
          try
          {
            responseMessage = reader.readMessage();
            if (responseMessage == null)
            {
              LocalizableMessage message =
                  ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
              throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                        message);
            }
          }
          catch (DecodeException | LDAPException e)
          {
            LocalizableMessage message =
                ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(getExceptionMessage(e));
            throw new ClientException(
                ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
          }
          catch (IOException ioe)
          {
            LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(
                getExceptionMessage(ioe));
            throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                      message, ioe);
          }
          catch (Exception e)
          {
            LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE.get(
                getExceptionMessage(e));
            throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR,
                                      message, e);
          }
        responseMessage = readBindResponse(ERR_LDAPAUTH_CANNOT_READ_BIND_RESPONSE);
          // FIXME -- Handle response controls.
          // Look at the protocol op from the response.  If it's a bind
          // response, then continue.  If it's an extended response, then it
          // could be a notice of disconnection so check for that.  Otherwise,
          // generate an error.
          generateError(responseMessage);
        checkConnected(responseMessage);
        }
        else
        {
@@ -3267,22 +2222,77 @@
                                  null);
        }
      }
    // FIXME -- Need to look for things like password expiration warning, reset notice, etc.
    }
    else
  private void evaluateGSSAPIChallenge(SaslClient saslClient, BindResponseProtocolOp bindResponse)
      throws ClientException
    {
      LocalizableMessage message = ERR_LDAPAUTH_UNEXPECTED_RUN_INVOCATION.get(
          saslMechanism, getBacktrace());
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    // We should be done after this, but we still need to look for and
    // handle the server SASL credentials.
    ByteString serverSASLCredentials = bindResponse.getServerSASLCredentials();
    if (serverSASLCredentials != null)
    {
      try
      {
        saslClient.evaluateChallenge(serverSASLCredentials.toByteArray());
      }
      catch (Exception e)
      {
        LocalizableMessage message = ERR_LDAPAUTH_GSSAPI_CANNOT_VALIDATE_SERVER_CREDS.get(getExceptionMessage(e));
        throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
      }
    }
    // Just to be sure, check that the login really is complete.
    if (!saslClient.isComplete())
    {
      LocalizableMessage message = ERR_LDAPAUTH_GSSAPI_UNEXPECTED_SUCCESS_RESPONSE.get();
      throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message);
    }
  }
    // FIXME -- Need to look for things like password expiration warning, reset
    // notice, etc.
  private ByteString evaluateSaslChallenge(SaslClient saslClient, BindResponseProtocolOp bindResponse)
      throws ClientException
  {
    try
    {
      ByteString saslCredentials = bindResponse.getServerSASLCredentials();
      byte[] bs = saslCredentials != null ? saslCredentials.toByteArray() : new byte[0];
      return ByteString.wrap(saslClient.evaluateChallenge(bs));
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_LDAPAUTH_GSSAPI_CANNOT_VALIDATE_SERVER_CREDS.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
  }
  private ByteString getSaslCredentialsForInitialBind(SaslClient saslClient) throws ClientException
  {
    if (saslClient.hasInitialResponse())
    {
      try
      {
        return ByteString.wrap(saslClient.evaluateChallenge(new byte[0]));
      }
      catch (Exception e)
      {
        LocalizableMessage message = ERR_LDAPAUTH_GSSAPI_CANNOT_CREATE_INITIAL_CHALLENGE.get(getExceptionMessage(e));
        throw new ClientException(ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
      }
    }
    return null;
  }
  private void generateError(LDAPMessage responseMessage) throws LDAPException, ClientException
  /**
   * Look at the protocol op from the response.
   * If it's a bind response, then continue.
   * If it's an extended response, then check it is not a notice of disconnection.
   * Otherwise, generate an error.
   */
  private void checkConnected(LDAPMessage responseMessage) throws LDAPException, ClientException
  {
    switch (responseMessage.getProtocolOpType())
    {
@@ -3417,40 +2427,7 @@
    }
    // Read the response from the server.
    LDAPMessage responseMessage;
    try
    {
      responseMessage = reader.readMessage();
      if (responseMessage == null)
      {
        LocalizableMessage message =
            ERR_LDAPAUTH_CONNECTION_CLOSED_WITHOUT_BIND_RESPONSE.get();
        throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN,
                                  message);
      }
    }
    catch (DecodeException | LDAPException e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_WHOAMI_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(ReturnCode.CLIENT_SIDE_DECODING_ERROR, message, e);
    }
    catch (IOException ioe)
    {
      LocalizableMessage message = ERR_LDAPAUTH_CANNOT_READ_WHOAMI_RESPONSE.get(
          getExceptionMessage(ioe));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_SERVER_DOWN, message, ioe);
    }
    catch (Exception e)
    {
      LocalizableMessage message =
          ERR_LDAPAUTH_CANNOT_READ_WHOAMI_RESPONSE.get(getExceptionMessage(e));
      throw new ClientException(
              ReturnCode.CLIENT_SIDE_LOCAL_ERROR, message, e);
    }
    LDAPMessage responseMessage = readBindResponse(ERR_LDAPAUTH_CANNOT_READ_WHOAMI_RESPONSE);
    // If the protocol op isn't an extended response, then that's a problem.
    if (responseMessage.getProtocolOpType() != OP_TYPE_EXTENDED_RESPONSE)
@@ -3493,13 +2470,10 @@
      return null;
    }
    String valueString = authzID.toString();
    if (valueString == null || valueString.length() == 0 ||
        valueString.equalsIgnoreCase("dn:"))
    if (!"dn:".equalsIgnoreCase(authzID.toString()))
    {
      return null;
    }
    return authzID;
  }
    return null;
  }
}
opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPPasswordModify.java
@@ -29,7 +29,6 @@
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.forgerock.i18n.LocalizableMessage;
@@ -654,10 +653,7 @@
    // See if the response included any controls that we recognize, and if so
    // then handle them.
    List<Control> responseControls = responseMessage.getControls();
    if (responseControls != null)
    {
      for (Control c : responseControls)
    for (Control c : responseMessage.getControls())
      {
        if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
        {
@@ -667,16 +663,14 @@
              PasswordPolicyResponseControl.DECODER
                .decode(c.isCritical(), ((LDAPControl) c).getValue());
            PasswordPolicyWarningType pwPolicyWarningType =
                 pwPolicyControl.getWarningType();
          PasswordPolicyWarningType pwPolicyWarningType = pwPolicyControl.getWarningType();
            if (pwPolicyWarningType != null)
            {
              printWrappedText(
                      out, INFO_LDAPPWMOD_PWPOLICY_WARNING.get(pwPolicyWarningType, pwPolicyControl.getWarningValue()));
            }
            PasswordPolicyErrorType pwPolicyErrorType =
                 pwPolicyControl.getErrorType();
          PasswordPolicyErrorType pwPolicyErrorType = pwPolicyControl.getErrorType();
            if (pwPolicyErrorType != null)
            {
              printWrappedText(out, INFO_LDAPPWMOD_PWPOLICY_ERROR.get(pwPolicyErrorType));
@@ -688,7 +682,6 @@
          }
        }
      }
    }
    // See if the response included a generated password.
    ByteString responseValue = extendedResponse.getValue();
opendj-server-legacy/src/test/java/org/opends/server/tools/LDAPAuthenticationHandlerTestCase.java
@@ -31,7 +31,6 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.SASLMechanismHandler;
@@ -46,9 +45,7 @@
import com.forgerock.opendj.cli.ClientException;
/**
 * A set of test cases for the LDAP authentication handler.
 */
/** A set of test cases for the LDAP authentication handler. */
public class LDAPAuthenticationHandlerTestCase
       extends ToolsTestCase
{
@@ -67,8 +64,6 @@
    getFQDN();
  }
  /**
   * Retrieves the names of the supported SASL mechanisms.
   *
@@ -88,8 +83,6 @@
    };
  }
  /**
   * Tests the <CODE>getSupportedSASLMechanisms</CODE> method.
   *
@@ -114,29 +107,16 @@
  @Test(dataProvider = "saslMechanisms")
  public void testGetSASLProperties(String saslMechanismName)
  {
    LinkedHashMap<String, LocalizableMessage> properties =
         LDAPAuthenticationHandler.getSASLProperties(saslMechanismName);
    assertNotNull(properties);
    assertNotNull(LDAPAuthenticationHandler.getSASLProperties(saslMechanismName));
  }
  /**
   * Tests the <CODE>getSASLProperties</CODE> method with an unsupported
   * mechanism name.
   */
  /** Tests the <CODE>getSASLProperties</CODE> method with an unsupported mechanism name. */
  @Test
  public void testGetSASLPropertiesInvlaid()
  public void testGetSASLPropertiesInvalid()
  {
    LinkedHashMap<String,LocalizableMessage> properties =
         LDAPAuthenticationHandler.getSASLProperties("unsupportedMechanism");
    assertNull(properties);
    assertNull(LDAPAuthenticationHandler.getSASLProperties("unsupportedMechanism"));
  }
  /**
   * Tests the <CODE>doSimpleBind</CODE> method with a valid DN and password and
   * with no request controls.
@@ -147,8 +127,8 @@
  public void testDoSimpleBindWithValidDNAndPWNoControls()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -157,8 +137,6 @@
    }
  }
  /**
   * Tests the <CODE>doSimpleBind</CODE> method with a null DN and password and
   * no request controls.
@@ -169,8 +147,8 @@
  public void testDoSimpleBindWithNullDNAndPWNoControls()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -178,8 +156,6 @@
    }
  }
  /**
   * Tests the <CODE>doSimpleBind</CODE> method with an empty DN and password
   * and no request controls.
@@ -190,8 +166,8 @@
  public void testDoSimpleBindWithEmptyDNAndPWNoControls()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -199,20 +175,18 @@
    }
  }
  /**
   * Tests the <CODE>doSimpleBind</CODE> method with an valid DN but no
   * password.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSimpleBindWithDNButNoPassword()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
@@ -223,20 +197,18 @@
    }
  }
  /**
   * Tests the <CODE>doSimpleBind</CODE> method with an valid DN but an invalid
   * password.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSimpleBindWithDNButInvalidPassword()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
@@ -247,8 +219,6 @@
    }
  }
  /**
   * Tests the <CODE>doSimpleBind</CODE> method with the password policy
   * request control.
@@ -259,8 +229,8 @@
  public void testDoSimpleBindWithPasswordPolicyControl()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    requestControls.add(new PasswordPolicyRequestControl());
    try (Socket s = newSocket())
    {
@@ -270,20 +240,18 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method with a null mechanism.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindNullMechanism()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    try (Socket s = newSocket())
    {
@@ -292,20 +260,18 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method with an empty mechanism.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindEmptyMechanism()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    try (Socket s = newSocket())
    {
@@ -314,20 +280,18 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method with an invalid mechanism.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindInvalidMechanism()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    try (Socket s = newSocket())
    {
@@ -337,19 +301,17 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which ANONYMOUS
   * authentication is disabled in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindAnonymousDisabled()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("trace", newArrayList("testDoSASLBindAnonymousDisabled"));
    try (Socket s = newSocket())
@@ -359,8 +321,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which ANONYMOUS
   * authentication is enabled in the server.
@@ -374,7 +334,7 @@
    AnonymousSASLMechanismHandler handler = new AnonymousSASLMechanismHandler();
    handler.initializeSASLMechanismHandler(null);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("trace", newArrayList("testDoSASLBindAnonymous"));
    try (Socket s = newSocket())
@@ -399,7 +359,7 @@
    AnonymousSASLMechanismHandler handler = new AnonymousSASLMechanismHandler();
    handler.initializeSASLMechanismHandler(null);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    try (Socket s = newSocket())
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -408,8 +368,6 @@
    handler.finalizeSASLMechanismHandler();
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which ANONYMOUS
   * authentication is enabled in the server and multiple trace values are
@@ -417,14 +375,14 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindAnonymousMultivaluedTrace()
         throws Exception
  {
    AnonymousSASLMechanismHandler handler = new AnonymousSASLMechanismHandler();
    handler.initializeSASLMechanismHandler(null);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("trace",
        newArrayList("testDoSASLBindAnonymousMultivaluedTrace", "aSecondTraceStringWhichIsInvalid"));
@@ -439,8 +397,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which ANONYMOUS
   * authentication is enabled in the server and an invalid SASL property is
@@ -448,14 +404,14 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindAnonymousInvalidProperty()
         throws Exception
  {
    AnonymousSASLMechanismHandler handler = new AnonymousSASLMechanismHandler();
    handler.initializeSASLMechanismHandler(null);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("invalid", newArrayList("testDoSASLBindAnonymousInvalidProperty"));
    try (Socket s = newSocket())
@@ -469,8 +425,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which ANONYMOUS
   * authentication is enabled in the server and the request includes the
@@ -485,10 +439,10 @@
    AnonymousSASLMechanismHandler handler = new AnonymousSASLMechanismHandler();
    handler.initializeSASLMechanismHandler(null);
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    requestControls.add(new PasswordPolicyRequestControl());
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("trace", newArrayList("testDoSASLBindAnonymous"));
    try (Socket s = newSocket())
    {
@@ -499,15 +453,13 @@
    handler.finalizeSASLMechanismHandler();
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which CRAM-MD5
   * authentication is disabled in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindCRAMMD5Disabled()
         throws Exception
  {
@@ -530,8 +482,7 @@
         DirectoryServer.getSASLMechanismHandler("CRAM-MD5");
    DirectoryServer.deregisterSASLMechanismHandler("CRAM-MD5");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try
@@ -544,8 +495,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which CRAM-MD5
   * authentication is enabled in the server.
@@ -571,61 +520,54 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    cramMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method using CRAM-MD5 for the case in
   * which an authID was provided that doesn't map to any user.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindCRAMMD5InvalidAuthID()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    cramMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method using CRAM-MD5 for the case in
   * which an empty authID was provided.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindCRAMMD5EmptyAuthID()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList(""));
    cramMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method using CRAM-MD5 for the case in
   * which the provided password was incorrect.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindCRAMMD5InvalidPassword()
         throws Exception
  {
@@ -644,8 +586,7 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
@@ -655,15 +596,13 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method using CRAM-MD5 for the case in
   * which the specified user doesn't have a reversible password.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindCRAMMD5NoReversiblePassword()
         throws Exception
  {
@@ -680,61 +619,55 @@
         "cn: Test User",
         "userPassword: password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    cramMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method using CRAM-MD5 for the case in
   * which the provided SASL properties were null.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindCRAMMD5NullProperties()
         throws Exception
  {
    LinkedHashMap<String,List<String>> saslProperties = null;
    Map<String, List<String>> saslProperties = null;
    cramMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method using CRAM-MD5 for the case in
   * which the provided SASL properties were empty.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindCRAMMD5EmptyProperties()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    cramMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method using CRAM-MD5 for the case in
   * which multiple authID values were provided
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindCRAMMD5MultipleAuthIDs()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test", "u:test.user"));
    cramMd5SaslBind(saslProperties);
@@ -746,21 +679,19 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindCRAMMD5InvalidSASLProperty()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("invalid", newArrayList("foo"));
    cramMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which CRAM-MD5
   * authentication is enabled in the server and the password policy request
@@ -787,11 +718,10 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    requestControls.add(new PasswordPolicyRequestControl());
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
    {
@@ -802,15 +732,13 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which DIGEST-MD5
   * authentication is disabled in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindDigestMD5Disabled()
         throws Exception
  {
@@ -829,13 +757,11 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    SASLMechanismHandler<?> digestMD5Handler =
         DirectoryServer.getSASLMechanismHandler("DIGEST-MD5");
    DirectoryServer.deregisterSASLMechanismHandler("DIGEST-MD5");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test"));
@@ -850,8 +776,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which DIGEST-MD5
   * authentication is enabled in the server.
@@ -877,8 +801,7 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
@@ -888,8 +811,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which DIGEST-MD5
   * authentication is enabled in the server and an authz ID was provided.
@@ -915,8 +836,7 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
@@ -926,119 +846,104 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties are <CODE>null</CODE>.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5NullProperties()
         throws Exception
  {
    LinkedHashMap<String,List<String>> saslProperties = null;
    Map<String, List<String>> saslProperties = null;
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties are empty.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5EmptyProperties()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain an invalid property.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5InvalidProperty()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("invalid", newArrayList("foo"));
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain multiple values for the authID property.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5MultipleAuthIDs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    ArrayList<String> propList = newArrayList("dn:uid=test.user,o=test");
    List<String> propList = newArrayList("dn:uid=test.user,o=test");
    propList.add("u:test.user");
    saslProperties.put("authid", propList);
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain an empty authID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5MEmptyAuthID()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList(""));
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain multiple values for the realm property.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5MultipleRealms()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test", "dc=example,dc=com"));
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain a valid quality of protection.
@@ -1064,8 +969,7 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("qop", newArrayList("auth"));
@@ -1083,11 +987,11 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5UnsupportedQoPAuthInt()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test"));
    saslProperties.put("qop", newArrayList("auth-int"));
@@ -1095,8 +999,6 @@
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain the unsupported confidentiality quality
@@ -1104,11 +1006,11 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5UnsupportedQoPAuthConf()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test"));
    saslProperties.put("qop", newArrayList("auth-conf"));
@@ -1116,19 +1018,17 @@
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain an invalid quality of protection.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5InvalidQoP()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test"));
    saslProperties.put("qop", newArrayList("invalid"));
@@ -1136,18 +1036,17 @@
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain multiple quality of protection values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5MultipleQoPs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test"));
    saslProperties.put("qop", newArrayList("auth", "auth-int", "auth-conf"));
@@ -1155,19 +1054,17 @@
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain multiple digest URIs.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5MultipleDigestURIs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test"));
    saslProperties.put("digest-uri", newArrayList("ldap/value1", "ldap/value2"));
@@ -1175,18 +1072,17 @@
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain multiple authorization IDs.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindDigestMD5MultipleAuthzIDs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("realm", newArrayList("o=test"));
    saslProperties.put("authzid", newArrayList("dn:uid=test.user,o=test", "u:test.user"));
@@ -1194,55 +1090,49 @@
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain an invalid auth ID in the DN form.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindDigestMD5InvalidAuthDN()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:invalid"));
    saslProperties.put("realm", newArrayList("o=test"));
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the
   * DIGEST-MD5 SASL properties contain an auth ID that doesn't map to any user.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindDigestMD5NonExistentAuthID()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:nosuchuser"));
    saslProperties.put("realm", newArrayList("o=test"));
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which an invalid
   * password was provided.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindDigestMD5InvalidPassword()
         throws Exception
  {
@@ -1261,8 +1151,7 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:nosuchuser"));
    saslProperties.put("realm", newArrayList("o=test"));
@@ -1273,15 +1162,13 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the target
   * user does not have a reversible password.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindDigestMD5NoReversiblePassword()
         throws Exception
  {
@@ -1298,16 +1185,13 @@
         "cn: Test User",
         "userPassword: password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:nosuchuser"));
    saslProperties.put("realm", newArrayList("o=test"));
    digestMd5SaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which DIGEST-MD5
   * authentication is enabled in the server and the password policy request
@@ -1334,11 +1218,10 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    requestControls.add(new PasswordPolicyRequestControl());
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
@@ -1349,15 +1232,13 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which EXTERNAL
   * authentication is not enabled in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindExternalDisabled()
         throws Exception
  {
@@ -1373,24 +1254,20 @@
         "sn: User",
         "cn: Test User");
    SASLMechanismHandler<?> externalHandler =
         DirectoryServer.getSASLMechanismHandler("EXTERNAL");
    DirectoryServer.deregisterSASLMechanismHandler("EXTERNAL");
    String keyStorePath   = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
    String trustStorePath = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.truststore";
    SSLConnectionFactory factory = new SSLConnectionFactory();
    factory.init(false, keyStorePath, "password", "client-cert",
                 trustStorePath, "password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    try (Socket s = factory.createSocket("127.0.0.1", TestCaseUtils.getServerLdapsPort()))
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -1402,8 +1279,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which EXTERNAL
   * authentication is enabled in the server.
@@ -1426,35 +1301,30 @@
         "sn: User",
         "cn: Test User");
    String keyStorePath   = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
    String trustStorePath = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.truststore";
    SSLConnectionFactory factory = new SSLConnectionFactory();
    factory.init(false, keyStorePath, "password", "client-cert", trustStorePath,
                 "password");
    try (Socket s = factory.createSocket("127.0.0.1", TestCaseUtils.getServerLdapsPort()))
    {
      LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
      Map<String, List<String>> saslProperties = new LinkedHashMap<>();
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
      doSASLBind("EXTERNAL", null, authHandler, saslProperties);
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in the EXTERNAL SASL
   * properties were not empty.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindExternalInvalidProperties()
         throws Exception
  {
@@ -1470,27 +1340,23 @@
         "sn: User",
         "cn: Test User");
    SASLMechanismHandler<?> externalHandler =
         DirectoryServer.getSASLMechanismHandler("EXTERNAL");
    DirectoryServer.deregisterSASLMechanismHandler("EXTERNAL");
    String keyStorePath   = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
    String trustStorePath = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.truststore";
    SSLConnectionFactory factory = new SSLConnectionFactory();
    factory.init(false, keyStorePath, "password", "client-cert", trustStorePath,
                 "password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("invalid", newArrayList("foo"));
    try (Socket s = factory.createSocket("127.0.0.1", TestCaseUtils.getServerLdapsPort());)
    try (Socket s = factory.createSocket("127.0.0.1", TestCaseUtils.getServerLdapsPort()))
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
      doSASLBind("EXTERNAL", null, authHandler, saslProperties);
@@ -1524,22 +1390,19 @@
         "sn: User",
         "cn: Test User");
    String keyStorePath   = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
    String trustStorePath = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.truststore";
    SSLConnectionFactory factory = new SSLConnectionFactory();
    factory.init(false, keyStorePath, "password", "client-cert", trustStorePath,
                 "password");
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    requestControls.add(new PasswordPolicyRequestControl());
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    try (Socket s = factory.createSocket("127.0.0.1", TestCaseUtils.getServerLdapsPort()))
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -1548,36 +1411,32 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties list was null.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPINullProperties()
         throws Exception
  {
    LinkedHashMap<String,List<String>> saslProperties = null;
    Map<String, List<String>> saslProperties = null;
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties list was empty.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIEmptyProperties()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    gssapiSaslBind(saslProperties);
  }
@@ -1641,7 +1500,7 @@
    }
  }
  private void cramMd5SaslBind(LinkedHashMap<String, List<String>> saslProperties) throws Exception
  private void cramMd5SaslBind(Map<String, List<String>> saslProperties) throws Exception
  {
    try (Socket s = newSocket())
    {
@@ -1649,7 +1508,7 @@
    }
  }
  private void digestMd5SaslBind(LinkedHashMap<String, List<String>> saslProperties) throws Exception
  private void digestMd5SaslBind(Map<String, List<String>> saslProperties) throws Exception
  {
    try (Socket s = newSocket())
    {
@@ -1657,7 +1516,7 @@
    }
  }
  private void gssapiSaslBind(LinkedHashMap<String, List<String>> saslProperties) throws Exception
  private void gssapiSaslBind(Map<String, List<String>> saslProperties) throws Exception
  {
    try (Socket s = newSocket())
    {
@@ -1671,93 +1530,83 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIEmptyAuthID()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList(""));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has multiple authID values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIMultipleAuthIDs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user", "dn:uid=test.user,o=test"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has multiple authzID values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIMultipleAuthzIDs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("authzid", newArrayList("u:test.user", "dn:uid=test.user,o=test"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has multiple KDC values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIMultipleKDCs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("kdc", newArrayList("kdc1", "kdc2"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has multiple quality of protection values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIMultipleQoPs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("qop", newArrayList("auth", "auth-int", "auth-conf"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has an unsupported quality of protection value of
@@ -1765,19 +1614,17 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIUnsupportedQoPAuthInt()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("qop", newArrayList("auth-int"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has an unsupported quality of protection value of
@@ -1785,100 +1632,91 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIUnsupportedQoPAuthConf()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("qop", newArrayList("auth-conf"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has an invalid quality of protection value.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIInvalidQoP()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("qop", newArrayList("invalid"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has multiple realm values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIMultipleRealms()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("realm", newArrayList("realm1", "realm2"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties has an invalid property.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPIInvalidProperty()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("u:test.user"));
    saslProperties.put("invalid", newArrayList("foo"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for GSSAPI authentication when the
   * provided properties isn't empty but doesn't contain an auth ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindGSSAPINoAuthID()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("qop", newArrayList("auth"));
    gssapiSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which PLAIN
   * authentication is disabled in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindPlainDisabled()
         throws Exception
  {
@@ -1895,13 +1733,11 @@
         "cn: Test User",
         "userPassword: password");
    SASLMechanismHandler<?> plainHandler =
         DirectoryServer.getSASLMechanismHandler("PLAIN");
    DirectoryServer.deregisterSASLMechanismHandler("PLAIN");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
@@ -1915,8 +1751,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which PLAIN
   * authentication is enabled in the server.
@@ -1940,8 +1774,7 @@
         "cn: Test User",
         "userPassword: password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
    {
@@ -1950,53 +1783,47 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the PLAIN
   * SASL properties are null.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindPlainNullProperties()
         throws Exception
  {
    LinkedHashMap<String,List<String>> saslProperties = null;
    Map<String, List<String>> saslProperties = null;
    plainSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the PLAIN
   * SASL properties are empty.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindPlainEmptyProperties()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    plainSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the PLAIN
   * SASL properties have multiple auth ID values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindPlainMultipleAuthIDs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test", "u:test.user"));
    plainSaslBind(saslProperties);
@@ -2008,90 +1835,81 @@
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindPlainZeroLengthAuthID()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList(""));
    plainSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the PLAIN
   * SASL properties have multiple authzID values.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindPlainMultipleAuthzIDs()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("authzid", newArrayList("dn:uid=test.user,o=test", "u:test.user"));
    plainSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the PLAIN
   * SASL properties contains an invalid property.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindPlainInvalidProperty()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    saslProperties.put("invalid", newArrayList("foo"));
    plainSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which the PLAIN
   * SASL properties does not contain an auth ID.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { ClientException.class })
  @Test(expectedExceptions = ClientException.class)
  public void testDoSASLBindPlainNoAuthID()
         throws Exception
  {
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authzid", newArrayList("dn:uid=test.user,o=test"));
    plainSaslBind(saslProperties);
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for PLAIN authentication in which
   * the target user does not exist in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindPlainNonExistentUser()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    try (Socket s = newSocket())
    {
      LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
      Map<String, List<String>> saslProperties = new LinkedHashMap<>();
      saslProperties.put("authid", newArrayList("dn:uid=does.not.exist,o=test"));
      plain(newLDAPAuthenticationHandler(s, "localhost"), saslProperties);
@@ -2105,7 +1923,7 @@
   * @throws Exception
   *           If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { LDAPException.class })
  @Test(expectedExceptions = LDAPException.class)
  public void testDoSASLBindPlainWrongPassword()
         throws Exception
  {
@@ -2122,8 +1940,7 @@
         "cn: Test User",
         "userPassword: password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=does.not.exist,o=test"));
    try (Socket s = newSocket())
    {
@@ -2132,8 +1949,6 @@
    }
  }
  /**
   * Tests the <CODE>doSASLBind</CODE> method for the case in which PLAIN
   * authentication is enabled in the server and the password policy request
@@ -2158,11 +1973,10 @@
         "cn: Test User",
         "userPassword: password");
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    requestControls.add(new PasswordPolicyRequestControl());
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
    {
@@ -2172,8 +1986,6 @@
    }
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for an
   * unauthenticated client connection.
@@ -2191,8 +2003,6 @@
    }
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for a a client
   * connection after a simple anonymous bind.
@@ -2203,8 +2013,8 @@
  public void testRequestAuthorizationIdentitySimpleAnonymous()
         throws Exception
  {
    ArrayList<Control> requestControls = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
@@ -2224,8 +2034,8 @@
  public void testRequestAuthorizationIdentitySimpleRootUser()
         throws Exception
  {
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -2235,8 +2045,6 @@
    }
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for a a client
   * connection after a simple bind as a normal user.
@@ -2260,9 +2068,8 @@
         "cn: Test User",
         "userPassword: password");
    ArrayList<Control> requestControls  = new ArrayList<>();
    ArrayList<Control> responseControls = new ArrayList<>();
    List<Control> requestControls = new ArrayList<>();
    List<Control> responseControls = new ArrayList<>();
    try (Socket s = newSocket())
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -2272,8 +2079,6 @@
    }
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for a a client
   * connection after a SASL ANONYMOUS bind.
@@ -2287,7 +2092,7 @@
    AnonymousSASLMechanismHandler handler = new AnonymousSASLMechanismHandler();
    handler.initializeSASLMechanismHandler(null);
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("trace", newArrayList("testDoSASLBindAnonymous"));
    try (Socket s = newSocket())
    {
@@ -2298,8 +2103,6 @@
    handler.finalizeSASLMechanismHandler();
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for a a client
   * connection after a CRAM-MD5 bind.
@@ -2325,8 +2128,7 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
    {
@@ -2336,8 +2138,6 @@
    }
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for a a client
   * connection after a DIGEST-MD5 bind.
@@ -2363,8 +2163,7 @@
         "ds-pwp-password-policy-dn: cn=Clear UserPassword Policy," +
              "cn=Password Policies,cn=config");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
@@ -2375,8 +2174,6 @@
    }
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for a a client
   * connection after an EXTERNAL bind.
@@ -2399,19 +2196,16 @@
         "sn: User",
         "cn: Test User");
    String keyStorePath   = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.keystore";
    String trustStorePath = DirectoryServer.getInstanceRoot() + File.separator +
                            "config" + File.separator + "client.truststore";
    SSLConnectionFactory factory = new SSLConnectionFactory();
    factory.init(false, keyStorePath, "password", "client-cert", trustStorePath,
                 "password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    try (Socket s = factory.createSocket("127.0.0.1", TestCaseUtils.getServerLdapsPort()))
    {
      LDAPAuthenticationHandler authHandler = newLDAPAuthenticationHandler(s, "localhost");
@@ -2420,8 +2214,6 @@
    }
  }
  /**
   * Tests the <CODE>requestAuthorizationIdentity</CODE> method for a a client
   * connection after a PLAIN bind.
@@ -2445,8 +2237,7 @@
         "cn: Test User",
         "userPassword: password");
    LinkedHashMap<String, List<String>> saslProperties = new LinkedHashMap<>();
    Map<String, List<String>> saslProperties = new LinkedHashMap<>();
    saslProperties.put("authid", newArrayList("dn:uid=test.user,o=test"));
    try (Socket s = newSocket())
    {