From 139c40de1bc595ccd4b8ca952da9e2a37bc8a18e Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Wed, 05 Nov 2008 13:22:43 +0000
Subject: [PATCH] These fixes add confidentiality/integrity to the SASL GSSAPI and DIGEST-MD5 mechanisms. The issue links:

---
 opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java | 1631 ++++++---------------------------------------------------
 1 files changed, 189 insertions(+), 1,442 deletions(-)

diff --git a/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java b/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
index c1e1b9e..56106d5 100644
--- a/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
+++ b/opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
@@ -26,95 +26,65 @@
  */
 package org.opends.server.extensions;
 
-
-
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.text.ParseException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.concurrent.locks.Lock;
-
+import javax.security.sasl.*;
 import org.opends.messages.Message;
 import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.meta.DigestMD5SASLMechanismHandlerCfgDefn.*;
 import org.opends.server.admin.std.server.DigestMD5SASLMechanismHandlerCfg;
 import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
-import org.opends.server.api.Backend;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.IdentityMapper;
 import org.opends.server.api.SASLMechanismHandler;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.BindOperation;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.PasswordPolicyState;
-import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.protocols.internal.InternalClientConnection;
-import org.opends.server.types.AuthenticationInfo;
-import org.opends.server.types.ByteString;
+import org.opends.server.loggers.debug.*;
 import org.opends.server.types.ConfigChangeResult;
-import org.opends.server.types.DebugLogLevel;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DisconnectReason;
 import org.opends.server.types.DN;
-import org.opends.server.types.Entry;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.InitializationException;
-import org.opends.server.types.LockManager;
-import org.opends.server.types.Privilege;
 import org.opends.server.types.ResultCode;
-import org.opends.server.util.Base64;
-
-import static org.opends.messages.ExtensionMessages.*;
-import static org.opends.server.loggers.ErrorLogger.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.messages.ExtensionMessages.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
 
 
-
 /**
- * This class provides an implementation of a SASL mechanism that uses digest
- * authentication via DIGEST-MD5.  This is a password-based mechanism that does
- * not expose the password itself over the wire but rather uses an MD5 hash that
- * proves the client knows the password.  This is similar to the CRAM-MD5
- * mechanism, and the primary differences are that CRAM-MD5 only obtains random
- * data from the server whereas DIGEST-MD5 uses random data from both the
- * server and the client, CRAM-MD5 does not allow for an authorization ID in
- * addition to the authentication ID where DIGEST-MD5 does, and CRAM-MD5 does
- * not define any integrity and confidentiality mechanisms where DIGEST-MD5
- * does.  This implementation is based on the specification in RFC 2831 and
- * updates from draft-ietf-sasl-rfc2831bis-06.
+ * This class provides an implementation of a SASL mechanism that authenticates
+ * clients through DIGEST-MD5.
  */
 public class DigestMD5SASLMechanismHandler
-       extends SASLMechanismHandler<DigestMD5SASLMechanismHandlerCfg>
-       implements ConfigurationChangeListener<
-                       DigestMD5SASLMechanismHandlerCfg>
-{
-  /**
-   * The tracer object for the debug logger.
-   */
+      extends SASLMechanismHandler<DigestMD5SASLMechanismHandlerCfg>
+      implements ConfigurationChangeListener<DigestMD5SASLMechanismHandlerCfg> {
+
+  //The tracer object for the debug logger.
   private static final DebugTracer TRACER = getTracer();
 
   // The current configuration for this SASL mechanism handler.
-  private DigestMD5SASLMechanismHandlerCfg currentConfig;
+  private DigestMD5SASLMechanismHandlerCfg configuration;
 
   // The identity mapper that will be used to map ID strings to user entries.
   private IdentityMapper<?> identityMapper;
 
-  // The message digest engine that will be used to create the MD5 digests.
-  private MessageDigest md5Digest;
+  //Properties to use when creating a SASL server to process the authentication.
+  private HashMap<String,String> saslProps;
 
-  // The lock that will be used to provide threadsafe access to the message
-  // digest.
-  private Object digestLock;
+//The fully qualified domain name used when creating the SASL server.
+  private String serverFQDN;
 
-  // The random number generator that we will use to create the nonce.
-  private SecureRandom randomGenerator;
+  // The DN of the configuration entry for this SASL mechanism handler.
+  private DN configEntryDN;
 
+  //Property used to set the realm in the environment.
+  private static final String REALM_PROPERTY =
+                                          "com.sun.security.sasl.digest.realm";
 
 
   /**
@@ -128,1409 +98,95 @@
   }
 
 
-
   /**
    * {@inheritDoc}
    */
   @Override()
   public void initializeSASLMechanismHandler(
-                   DigestMD5SASLMechanismHandlerCfg configuration)
-         throws ConfigException, InitializationException
-  {
-    configuration.addDigestMD5ChangeListener(this);
-    currentConfig = configuration;
-
-
-    // Initialize the variables needed for the MD5 digest creation.
-    digestLock      = new Object();
-    randomGenerator = new SecureRandom();
-
-    try
-    {
-      md5Digest = MessageDigest.getInstance("MD5");
-    }
-    catch (Exception e)
-    {
-      if (debugEnabled())
-      {
-        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          DigestMD5SASLMechanismHandlerCfg configuration)
+  throws ConfigException, InitializationException {
+      configuration.addDigestMD5ChangeListener(this);
+      configEntryDN = configuration.dn();
+      try {
+         DN identityMapperDN = configuration.getIdentityMapperDN();
+         identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
+         serverFQDN = getFQDN(configuration);
+         Message msg= INFO_DIGEST_MD5_SERVER_FQDN.get(serverFQDN);
+         logError(msg);
+         String QOP = getQOP(configuration);
+         saslProps = new HashMap<String,String>();
+         saslProps.put(Sasl.QOP, QOP);
+         if(QOP.equalsIgnoreCase(SASL_MECHANISM_CONFIDENTIALITY)) {
+             saslProps.put(Sasl.STRENGTH, getStrength(configuration));
+         }
+         String realm=getRealm(configuration);
+         if(realm != null) {
+           msg = INFO_DIGEST_MD5_REALM.get(realm);
+           logError(msg);
+           saslProps.put(REALM_PROPERTY, getRealm(configuration));
+         }
+         this.configuration = configuration;
+         DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5,
+                  this);
+      } catch (UnknownHostException unhe) {
+          if (debugEnabled()) {
+              TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
+          }
+          Message message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(
+                  String.valueOf(configEntryDN), getExceptionMessage(unhe));
+          throw new InitializationException(message, unhe);
       }
-
-      Message message = ERR_SASLDIGESTMD5_CANNOT_GET_MESSAGE_DIGEST.get(
-          getExceptionMessage(e));
-      throw new InitializationException(message, e);
-    }
-
-
-    // Get the identity mapper that should be used to find users.
-    DN identityMapperDN = configuration.getIdentityMapperDN();
-    identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
-
-
-    DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5,
-                                                 this);
   }
 
 
-
   /**
    * {@inheritDoc}
    */
   @Override()
-  public void finalizeSASLMechanismHandler()
-  {
-    currentConfig.removeDigestMD5ChangeListener(this);
+  public void finalizeSASLMechanismHandler() {
+    configuration.removeDigestMD5ChangeListener(this);
     DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5);
   }
 
 
-
-
   /**
    * {@inheritDoc}
    */
   @Override()
-  public void processSASLBind(BindOperation bindOperation)
-  {
-    DigestMD5SASLMechanismHandlerCfg config = currentConfig;
-    IdentityMapper<?> identityMapper = this.identityMapper;
-    String realm = config.getRealm();
-
-
-    // The DIGEST-MD5 bind process uses two stages.  See if we have any state
-    // information from the first stage to determine whether this is a
-    // continuation of an existing bind or an initial authentication.  Note that
-    // this implementation does not support subsequent authentication, so even
-    // if the client provided credentials for the bind, it will be treated as an
-    // initial authentication if there is no existing state.
-    boolean initialAuth = true;
-    ClientConnection clientConnection  = bindOperation.getClientConnection();
-    Object saslStateInfo = clientConnection.getSASLAuthStateInfo();
-    if ((saslStateInfo != null) &&
-        (saslStateInfo instanceof DigestMD5StateInfo))
-    {
-      initialAuth = false;
-    }
-
-    if (initialAuth)
-    {
-      // Create a buffer to hold the challenge.
-      StringBuilder challengeBuffer = new StringBuilder();
-
-
-      // Add the realm to the challenge.  If we have a configured realm, then
-      // use it.  Otherwise, add a realm for each suffix defined in the server.
-      if (realm == null)
-      {
-        Map<DN,Backend> suffixes = DirectoryServer.getPublicNamingContexts();
-        if (! suffixes.isEmpty())
-        {
-          Iterator<DN> iterator = suffixes.keySet().iterator();
-          challengeBuffer.append("realm=\"");
-          challengeBuffer.append(iterator.next().toNormalizedString());
-          challengeBuffer.append("\"");
-
-          while (iterator.hasNext())
-          {
-            challengeBuffer.append(",realm=\"");
-            challengeBuffer.append(iterator.next().toNormalizedString());
-            challengeBuffer.append("\"");
-          }
-        }
-      }
-      else
-      {
-        challengeBuffer.append("realm=\"");
-        challengeBuffer.append(realm);
-        challengeBuffer.append("\"");
-      }
-
-
-      // Generate the nonce.  Add it to the challenge and remember it for future
-      // use.
-      String nonce = generateNonce();
-      if (challengeBuffer.length() > 0)
-      {
-        challengeBuffer.append(",");
-      }
-      challengeBuffer.append("nonce=\"");
-      challengeBuffer.append(nonce);
-      challengeBuffer.append("\"");
-
-
-      // Generate the qop-list and add it to the challenge.
-      // FIXME -- Add support for integrity and confidentiality.  Once we do,
-      //          we'll also want to add the maxbuf and cipher options.
-      challengeBuffer.append(",qop=\"auth\"");
-
-
-      // Add the charset option to indicate that we support UTF-8 values.
-      challengeBuffer.append(",charset=utf-8");
-
-
-      // Add the algorithm, which will always be "md5-sess".
-      challengeBuffer.append(",algorithm=md5-sess");
-
-
-      // Encode the challenge as an ASN.1 element.  The total length of the
-      // encoded value must be less than 2048 bytes, which should not be a
-      // problem, but we'll add a safety check just in case....  In the event
-      // that it does happen, we'll also log an error so it is more noticeable.
-      ASN1OctetString challenge =
-           new ASN1OctetString(challengeBuffer.toString());
-      if (challenge.value().length >= 2048)
-      {
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = WARN_SASLDIGESTMD5_CHALLENGE_TOO_LONG.get(
-                challenge.value().length);
-        bindOperation.setAuthFailureReason(message);
-
-        logError(message);
-        return;
-      }
-
-
-      // Store the state information with the client connection so we can use it
-      // for later validation.
-      DigestMD5StateInfo stateInfo = new DigestMD5StateInfo(nonce, "00000000");
-      clientConnection.setSASLAuthStateInfo(stateInfo);
-
-
-      // Prepare the response and return so it will be sent to the client.
-      bindOperation.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
-      bindOperation.setServerSASLCredentials(challenge);
-      return;
-    }
-
-
-    // If we've gotten here, then we have existing SASL state information for
-    // this client.  Make sure that the client also provided credentials.
-    ASN1OctetString clientCredentials = bindOperation.getSASLCredentials();
-    if ((clientCredentials == null) || (clientCredentials.value().length == 0))
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_NO_CREDENTIALS.get();
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-
-
-    // Parse the SASL state information.  Also, since there are only ever two
-    // stages of a DIGEST-MD5 bind, clear the SASL state information stored in
-    // the client connection because it shouldn't be used anymore regardless of
-    // whether the bind succeeds or fails.  Note that if we do add support for
-    // subsequent authentication in the future, then we will probably need to
-    // keep state information in the client connection, but even then it will
-    // be different from what's already there.
-    DigestMD5StateInfo stateInfo = (DigestMD5StateInfo) saslStateInfo;
-    clientConnection.setSASLAuthStateInfo(null);
-
-
-    // Create variables to hold values stored in the client's response.  We'll
-    // also store the base DN because we might need to override it later.
-    String responseUserName      = null;
-    String responseRealm         = null;
-    String responseNonce         = null;
-    String responseCNonce        = null;
-    int    responseNonceCount    = -1;
-    String responseNonceCountStr = null;
-    String responseQoP           = "auth";
-    String responseDigestURI     = null;
-    byte[] responseDigest        = null;
-    String responseCharset       = "ISO-8859-1";
-    String responseAuthzID       = null;
-
-
-    // Get a temporary string representation of the SASL credentials using the
-    // ISO-8859-1 encoding and see if it contains "charset=utf-8".  If so, then
-    // re-parse the credentials using that character set.
-    byte[] credBytes  = clientCredentials.value();
-    String credString = null;
-    String lowerCreds = null;
-    try
-    {
-      credString = new String(credBytes, responseCharset);
-      lowerCreds = toLowerCase(credString);
-    }
-    catch (Exception e)
-    {
-      if (debugEnabled())
-      {
-        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-      }
-
-      // This isn't necessarily fatal because we're going to retry using UTF-8,
-      // but we want to log it anyway.
-      logError(WARN_SASLDIGESTMD5_CANNOT_PARSE_ISO_CREDENTIALS.get(
-          responseCharset, getExceptionMessage(e)));
-    }
-
-    if ((credString == null) ||
-        (lowerCreds.indexOf("charset=utf-8") >= 0))
-    {
-      try
-      {
-        credString = new String(credBytes, "UTF-8");
-        lowerCreds = toLowerCase(credString);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        // This is fatal because either we can't parse the credentials as a
-        // string at all, or we know we need to do so using UTF-8 and can't.
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = WARN_SASLDIGESTMD5_CANNOT_PARSE_UTF8_CREDENTIALS.get(
-                getExceptionMessage(e));
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-    }
-
-
-    // Iterate through the credentials string, parsing the property names and
-    // their corresponding values.
-    int pos    = 0;
-    int length = credString.length();
-    while (pos < length)
-    {
-      int equalPos = credString.indexOf('=', pos+1);
-      if (equalPos < 0)
-      {
-        // This is bad because we're not at the end of the string but we don't
-        // have a name/value delimiter.
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_INVALID_TOKEN_IN_CREDENTIALS.get(
-                credString, pos);
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-
-
-      String tokenName  = lowerCreds.substring(pos, equalPos);
-
-      String tokenValue;
-      try
-      {
-        StringBuilder valueBuffer = new StringBuilder();
-        pos = readToken(credString, equalPos+1, length, valueBuffer);
-        tokenValue = valueBuffer.toString();
-      }
-      catch (DirectoryException de)
-      {
-        // We couldn't parse the token value, so it must be malformed.
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-        bindOperation.setAuthFailureReason(
-                de.getMessageObject());
-        return;
-      }
-
-      if (tokenName.equals("charset"))
-      {
-        // The value must be the string "utf-8".  If not, that's an error.
-        if (! tokenValue.equalsIgnoreCase("utf-8"))
-        {
-          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-          Message message = ERR_SASLDIGESTMD5_INVALID_CHARSET.get(tokenValue);
-          bindOperation.setAuthFailureReason(message);
+  public void processSASLBind(BindOperation bindOp) {
+      ClientConnection clientConnection = bindOp.getClientConnection();
+      if (clientConnection == null) {
+          Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
+          bindOp.setAuthFailureReason(message);
+          bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
           return;
-        }
       }
-      else if (tokenName.equals("username"))
-      {
-        responseUserName = tokenValue;
-      }
-      else if (tokenName.equals("realm"))
-      {
-        responseRealm = tokenValue;
-        if (realm != null)
-        {
-          if (! responseRealm.equals(realm))
-          {
-            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-            Message message =
-                    ERR_SASLDIGESTMD5_INVALID_REALM.get(responseRealm);
-            bindOperation.setAuthFailureReason(message);
-            return;
-          }
-        }
-      }
-      else if (tokenName.equals("nonce"))
-      {
-        responseNonce = tokenValue;
-        String requestNonce = stateInfo.getNonce();
-        if (! responseNonce.equals(requestNonce))
-        {
-          // The nonce provided by the client is incorrect.  This could be an
-          // attempt at a replay or chosen plaintext attack, so we'll close the
-          // connection.  We will put a message in the log but will not send it
-          // to the client.
-          Message message = ERR_SASLDIGESTMD5_INVALID_NONCE.get();
-          clientConnection.disconnect(DisconnectReason.SECURITY_PROBLEM, false,
-                  message);
-          return;
-        }
-      }
-      else if (tokenName.equals("cnonce"))
-      {
-        responseCNonce = tokenValue;
-      }
-      else if (tokenName.equals("nc"))
-      {
-        try
-        {
-          responseNonceCountStr = tokenValue;
-          responseNonceCount    = Integer.parseInt(responseNonceCountStr, 16);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-          Message message = ERR_SASLDIGESTMD5_CANNOT_DECODE_NONCE_COUNT.get(
-                  tokenValue);
-          bindOperation.setAuthFailureReason(message);
-          return;
-        }
-
-        int storedNonce;
-        try
-        {
-          storedNonce = Integer.parseInt(stateInfo.getNonceCount(), 16);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-          Message message =
-                  ERR_SASLDIGESTMD5_CANNOT_DECODE_STORED_NONCE_COUNT.get(
-                          getExceptionMessage(e));
-          bindOperation.setAuthFailureReason(message);
-          return;
-        }
-
-        if (responseNonceCount != (storedNonce + 1))
-        {
-          // The nonce count provided by the client is incorrect.  This
-          // indicates a replay attack, so we'll close the connection.  We will
-          // put a message in the log but we will not send it to the client.
-          Message message = ERR_SASLDIGESTMD5_INVALID_NONCE_COUNT.get();
-          clientConnection.disconnect(DisconnectReason.SECURITY_PROBLEM, false,
-                  message);
-          return;
-        }
-      }
-      else if (tokenName.equals("qop"))
-      {
-        responseQoP = tokenValue;
-
-        if (responseQoP.equals("auth"))
-        {
-          // No action necessary.
-        }
-        else if (responseQoP.equals("auth-int"))
-        {
-          // FIXME -- Add support for integrity protection.
-          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-          Message message = ERR_SASLDIGESTMD5_INTEGRITY_NOT_SUPPORTED.get();
-          bindOperation.setAuthFailureReason(message);
-          return;
-        }
-        else if (responseQoP.equals("auth-conf"))
-        {
-          // FIXME -- Add support for confidentiality protection.
-          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-          Message message =
-                  ERR_SASLDIGESTMD5_CONFIDENTIALITY_NOT_SUPPORTED.get();
-          bindOperation.setAuthFailureReason(message);
-          return;
-        }
-        else
-        {
-          // This is an invalid QoP value.
-          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-          Message message = ERR_SASLDIGESTMD5_INVALID_QOP.get(responseQoP);
-          bindOperation.setAuthFailureReason(message);
-          return;
-        }
-      }
-      else if (tokenName.equals("digest-uri"))
-      {
-        responseDigestURI = tokenValue;
-
-        String serverFQDN = config.getServerFqdn();
-        if ((serverFQDN != null) && (serverFQDN.length() > 0))
-        {
-          // If a server FQDN is populated, then we'll use it to validate the
-          // digest-uri, which should be in the form "ldap/serverfqdn".
-          String expectedDigestURI = "ldap/" + serverFQDN;
-          if (! expectedDigestURI.equalsIgnoreCase(responseDigestURI))
-          {
-            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-            Message message = ERR_SASLDIGESTMD5_INVALID_DIGEST_URI.get(
-                    responseDigestURI, expectedDigestURI);
-            bindOperation.setAuthFailureReason(message);
-            return;
-          }
-        }
-      }
-      else if (tokenName.equals("response"))
-      {
-        try
-        {
-          responseDigest = hexStringToByteArray(tokenValue);
-        }
-        catch (ParseException pe)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, pe);
-          }
-
-          Message message =
-                  ERR_SASLDIGESTMD5_CANNOT_PARSE_RESPONSE_DIGEST.get(
-                          getExceptionMessage(pe));
-          bindOperation.setAuthFailureReason(message);
-          return;
-        }
-      }
-      else if (tokenName.equals("authzid"))
-      {
-        responseAuthzID = tokenValue;
-
-        // FIXME -- This must always be parsed in UTF-8 even if the charset for
-        // other elements is ISO 8859-1.
-      }
-      else if (tokenName.equals("maxbuf") || tokenName.equals("cipher"))
-      {
-        // FIXME -- Add support for confidentiality and integrity protection.
-      }
-      else
-      {
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_INVALID_RESPONSE_TOKEN.get(
-                tokenName);
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-    }
-
-
-    // Make sure that all required properties have been specified.
-    if ((responseUserName == null) || (responseUserName.length() == 0))
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_NO_USERNAME_IN_RESPONSE.get();
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-    else if (responseNonce == null)
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_NO_NONCE_IN_RESPONSE.get();
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-    else if (responseCNonce == null)
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_NO_CNONCE_IN_RESPONSE.get();
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-    else if (responseNonceCount < 0)
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_NO_NONCE_COUNT_IN_RESPONSE.get();
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-    else if (responseDigest == null)
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_NO_DIGEST_IN_RESPONSE.get();
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-
-
-    // Slight departure from draft-ietf-sasl-rfc2831bis-06 in order to
-    // support legacy/broken client implementations, such as Solaris
-    // Native LDAP Client, which omit digest-uri directive. the presence
-    // of digest-uri directive erroneously read "may" in the RFC and has
-    // been fixed later in the DRAFT to read "must". if the client does
-    // not include digest-uri directive use the empty string instead.
-    if (responseDigestURI == null)
-    {
-      responseDigestURI = "";
-    }
-
-
-    // If a realm has not been specified, then use the empty string.
-    // FIXME -- Should we reject this if a specific realm is defined?
-    if (responseRealm == null)
-    {
-      responseRealm = "";
-    }
-
-
-    // Get the user entry for the authentication ID.  Allow for an
-    // authentication ID that is just a username (as per the DIGEST-MD5 spec),
-    // but also allow a value in the authzid form specified in RFC 2829.
-    Entry  userEntry    = null;
-    String lowerUserName = toLowerCase(responseUserName);
-    if (lowerUserName.startsWith("dn:"))
-    {
-      // Try to decode the user DN and retrieve the corresponding entry.
-      DN userDN;
-      try
-      {
-        userDN = DN.decode(responseUserName.substring(3));
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_CANNOT_DECODE_USERNAME_AS_DN.get(
-                responseUserName, de.getMessageObject());
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-
-      if (userDN.isNullDN())
-      {
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_USERNAME_IS_NULL_DN.get();
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-
-      DN rootDN = DirectoryServer.getActualRootBindDN(userDN);
-      if (rootDN != null)
-      {
-        userDN = rootDN;
-      }
-
-      // Acquire a read lock on the user entry.  If this fails, then so will the
-      // authentication.
-      Lock readLock = null;
-      for (int i=0; i < 3; i++)
-      {
-        readLock = LockManager.lockRead(userDN);
-        if (readLock != null)
-        {
-          break;
-        }
-      }
-
-      if (readLock == null)
-      {
-        bindOperation.setResultCode(DirectoryServer.getServerErrorResultCode());
-
-        Message message = INFO_SASLDIGESTMD5_CANNOT_LOCK_ENTRY.get(
-                String.valueOf(userDN));
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-
-      try
-      {
-        userEntry = DirectoryServer.getEntry(userDN);
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_CANNOT_GET_ENTRY_BY_DN.get(
-                String.valueOf(userDN), de.getMessageObject());
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-      finally
-      {
-        LockManager.unlock(userDN, readLock);
-      }
-    }
-    else
-    {
-      // Use the identity mapper to resolve the username to an entry.
-      String userName = responseUserName;
-      if (lowerUserName.startsWith("u:"))
-      {
-        if (lowerUserName.equals("u:"))
-        {
-          bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-          Message message = ERR_SASLDIGESTMD5_ZERO_LENGTH_USERNAME.get();
-          bindOperation.setAuthFailureReason(message);
-          return;
-        }
-
-        userName = responseUserName.substring(2);
-      }
-
-
-      try
-      {
-        userEntry = identityMapper.getEntryForID(userName);
-      }
-      catch (DirectoryException de)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, de);
-        }
-
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_CANNOT_MAP_USERNAME.get(
-                String.valueOf(responseUserName), de.getMessageObject());
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-    }
-
-
-    // At this point, we should have a user entry.  If we don't then fail.
-    if (userEntry == null)
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message =
-              ERR_SASLDIGESTMD5_NO_MATCHING_ENTRIES.get(responseUserName);
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-    else
-    {
-      bindOperation.setSASLAuthUserEntry(userEntry);
-    }
-
-
-    Entry authZEntry = userEntry;
-    if (responseAuthzID != null)
-    {
-      if (responseAuthzID.length() == 0)
-      {
-        // The authorization ID must not be an empty string.
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_EMPTY_AUTHZID.get();
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-      else if (! responseAuthzID.equals(responseUserName))
-      {
-        String lowerAuthzID = toLowerCase(responseAuthzID);
-
-        if (lowerAuthzID.startsWith("dn:"))
-        {
-          DN authzDN;
-          try
-          {
-            authzDN = DN.decode(responseAuthzID.substring(3));
-          }
-          catch (DirectoryException de)
-          {
-            if (debugEnabled())
-            {
-              TRACER.debugCaught(DebugLogLevel.ERROR, de);
-            }
-
-            bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-            Message message = ERR_SASLDIGESTMD5_AUTHZID_INVALID_DN.get(
-                    responseAuthzID, de.getMessageObject());
-            bindOperation.setAuthFailureReason(message);
-            return;
-          }
-
-          DN actualAuthzDN = DirectoryServer.getActualRootBindDN(authzDN);
-          if (actualAuthzDN != null)
-          {
-            authzDN = actualAuthzDN;
-          }
-
-          if (! authzDN.equals(userEntry.getDN()))
-          {
-            AuthenticationInfo tempAuthInfo =
-              new AuthenticationInfo(userEntry,
-                       DirectoryServer.isRootDN(userEntry.getDN()));
-            InternalClientConnection tempConn =
-                 new InternalClientConnection(tempAuthInfo);
-            if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
-            {
-              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              Message message =
-                      ERR_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES.get(
-                              String.valueOf(userEntry.getDN()));
-              bindOperation.setAuthFailureReason(message);
+      ClientConnection clientConn  = bindOp.getClientConnection();
+      SASLContext saslContext =
+         (SASLContext) clientConn.getSASLAuthStateInfo();
+      if(saslContext == null) {
+          try {
+              saslContext = SASLContext.createSASLContext(saslProps, serverFQDN,
+                            SASL_MECHANISM_DIGEST_MD5, identityMapper);
+          } catch (SaslException ex) {
+              if (debugEnabled()) {
+                  TRACER.debugCaught(DebugLogLevel.ERROR, ex);
+              }
+              Message msg =
+                  ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_DIGEST_MD5,
+                                                    getExceptionMessage(ex));
+              clientConn.setSASLAuthStateInfo(null);
+              bindOp.setAuthFailureReason(msg);
+              bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
               return;
-            }
-
-            if (authzDN.isNullDN())
-            {
-              authZEntry = null;
-            }
-            else
-            {
-              try
-              {
-                authZEntry = DirectoryServer.getEntry(authzDN);
-                if (authZEntry == null)
-                {
-                  bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-                  Message message = ERR_SASLDIGESTMD5_AUTHZID_NO_SUCH_ENTRY.get(
-                          String.valueOf(authzDN));
-                  bindOperation.setAuthFailureReason(message);
-                  return;
-                }
-              }
-              catch (DirectoryException de)
-              {
-                if (debugEnabled())
-                {
-                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-
-                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-                Message message = ERR_SASLDIGESTMD5_AUTHZID_CANNOT_GET_ENTRY
-                        .get(String.valueOf(authzDN), de.getMessageObject());
-                bindOperation.setAuthFailureReason(message);
-                return;
-              }
-            }
           }
-        }
-        else
-        {
-          String idStr;
-          if (lowerAuthzID.startsWith("u:"))
-          {
-            idStr = responseAuthzID.substring(2);
-          }
-          else
-          {
-            idStr = responseAuthzID;
-          }
-
-          if (idStr.length() == 0)
-          {
-            authZEntry = null;
-          }
-          else
-          {
-            try
-            {
-              authZEntry = identityMapper.getEntryForID(idStr);
-              if (authZEntry == null)
-              {
-                bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-                Message message = ERR_SASLDIGESTMD5_AUTHZID_NO_MAPPED_ENTRY.get(
-                        responseAuthzID);
-                bindOperation.setAuthFailureReason(message);
-                return;
-              }
-            }
-            catch (DirectoryException de)
-            {
-              if (debugEnabled())
-              {
-                TRACER.debugCaught(DebugLogLevel.ERROR, de);
-              }
-
-              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              Message message = ERR_SASLDIGESTMD5_CANNOT_MAP_AUTHZID.get(
-                      responseAuthzID, de.getMessageObject());
-              bindOperation.setAuthFailureReason(message);
-              return;
-            }
-          }
-
-          if ((authZEntry == null) ||
-              (! authZEntry.getDN().equals(userEntry.getDN())))
-          {
-            AuthenticationInfo tempAuthInfo =
-              new AuthenticationInfo(userEntry,
-                       DirectoryServer.isRootDN(userEntry.getDN()));
-            InternalClientConnection tempConn =
-                 new InternalClientConnection(tempAuthInfo);
-            if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOperation))
-            {
-              bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-              Message message =
-                      ERR_SASLDIGESTMD5_AUTHZID_INSUFFICIENT_PRIVILEGES.get(
-                              String.valueOf(userEntry.getDN()));
-              bindOperation.setAuthFailureReason(message);
-              return;
-            }
-          }
-        }
+          saslContext.evaluateInitialStage(bindOp);
+      } else {
+          saslContext.evaluateFinalStage(bindOp);
       }
-    }
-
-
-    // Get the clear-text passwords from the user entry, if there are any.
-    List<ByteString> clearPasswords;
-    try
-    {
-      PasswordPolicyState pwPolicyState =
-           new PasswordPolicyState(userEntry, false);
-      clearPasswords = pwPolicyState.getClearPasswords();
-      if ((clearPasswords == null) || clearPasswords.isEmpty())
-      {
-        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-        Message message = ERR_SASLDIGESTMD5_NO_REVERSIBLE_PASSWORDS.get(
-                String.valueOf(userEntry.getDN()));
-        bindOperation.setAuthFailureReason(message);
-        return;
-      }
-    }
-    catch (Exception e)
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_CANNOT_GET_REVERSIBLE_PASSWORDS.get(
-              String.valueOf(userEntry.getDN()),
-              String.valueOf(e));
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-
-
-    // Iterate through the clear-text values and see if any of them can be used
-    // in conjunction with the challenge to construct the provided digest.
-    boolean matchFound    = false;
-    byte[]  passwordBytes = null;
-    for (ByteString clearPassword : clearPasswords)
-    {
-      byte[] generatedDigest;
-      try
-      {
-        generatedDigest =
-             generateResponseDigest(responseUserName, responseAuthzID,
-                                    clearPassword.value(), responseRealm,
-                                    responseNonce, responseCNonce,
-                                    responseNonceCountStr, responseDigestURI,
-                                    responseQoP, responseCharset);
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          TRACER.debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        logError(WARN_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_DIGEST.get(
-            getExceptionMessage(e)));
-        continue;
-      }
-
-      if (Arrays.equals(responseDigest, generatedDigest))
-      {
-        matchFound    = true;
-        passwordBytes = clearPassword.value();
-        break;
-      }
-    }
-
-    if (! matchFound)
-    {
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message = ERR_SASLDIGESTMD5_INVALID_CREDENTIALS.get();
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-
-
-    // Generate the response auth element to include in the response to the
-    // client.
-    byte[] responseAuth;
-    try
-    {
-      responseAuth =
-           generateResponseAuthDigest(responseUserName, responseAuthzID,
-                                      passwordBytes, responseRealm,
-                                      responseNonce, responseCNonce,
-                                      responseNonceCountStr, responseDigestURI,
-                                      responseQoP, responseCharset);
-    }
-    catch (Exception e)
-    {
-      if (debugEnabled())
-      {
-        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-      }
-
-      bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
-      Message message =
-              ERR_SASLDIGESTMD5_CANNOT_GENERATE_RESPONSE_AUTH_DIGEST.get(
-                      getExceptionMessage(e));
-      bindOperation.setAuthFailureReason(message);
-      return;
-    }
-
-    ASN1OctetString responseAuthStr =
-         new ASN1OctetString("rspauth=" + getHexString(responseAuth));
-
-
-    // Make sure to store the updated nonce count with the client connection to
-    // allow for correct subsequent authentication.
-    stateInfo.setNonceCount(responseNonceCountStr);
-
-
-    // If we've gotten here, then the authentication was successful.  We'll also
-    // need to include the response auth string in the server SASL credentials.
-    bindOperation.setResultCode(ResultCode.SUCCESS);
-    bindOperation.setServerSASLCredentials(responseAuthStr);
-
-
-    AuthenticationInfo authInfo =
-         new AuthenticationInfo(userEntry, authZEntry,
-                                SASL_MECHANISM_DIGEST_MD5,
-                                DirectoryServer.isRootDN(userEntry.getDN()));
-    bindOperation.setAuthenticationInfo(authInfo);
-    return;
   }
 
 
-
-  /**
-   * Generates a new nonce value to use during the DIGEST-MD5 authentication
-   * process.
-   *
-   * @return  The nonce that should be used for DIGEST-MD5 authentication.
-   */
-  private String generateNonce()
-  {
-    byte[] nonceBytes = new byte[16];
-    randomGenerator.nextBytes(nonceBytes);
-    return Base64.encode(nonceBytes);
-  }
-
-
-
-  /**
-   * Reads the next token from the provided credentials string using the
-   * provided information.  If the token is surrounded by quotation marks, then
-   * the token returned will not include those quotation marks.
-   *
-   * @param  credentials  The credentials string from which to read the token.
-   * @param  startPos     The position of the first character of the token to
-   *                      read.
-   * @param  length       The total number of characters in the credentials
-   *                      string.
-   * @param  token        The buffer into which the token is to be placed.
-   *
-   * @return  The position at which the next token should start, or a value
-   *          greater than or equal to the length of the string if there are no
-   *          more tokens.
-   *
-   * @throws  DirectoryException  If a problem occurs while attempting to read
-   *                              the token.
-   */
-  private int readToken(String credentials, int startPos, int length,
-                        StringBuilder token)
-          throws DirectoryException
-  {
-    // If the position is greater than or equal to the length, then we shouldn't
-    // do anything.
-    if (startPos >= length)
-    {
-      return startPos;
-    }
-
-
-    // Look at the first character to see if it's an empty string or the string
-    // is quoted.
-    boolean isEscaped = false;
-    boolean isQuoted  = false;
-    int     pos       = startPos;
-    char    c         = credentials.charAt(pos++);
-
-    if (c == ',')
-    {
-      // This must be a zero-length token, so we'll just return the next
-      // position.
-      return pos;
-    }
-    else if (c == '"')
-    {
-      // The string is quoted, so we'll ignore this character, and we'll keep
-      // reading until we find the unescaped closing quote followed by a comma
-      // or the end of the string.
-      isQuoted = true;
-    }
-    else if (c == '\\')
-    {
-      // The next character is escaped, so we'll take it no matter what.
-      isEscaped = true;
-    }
-    else
-    {
-      // The string is not quoted, and this is the first character.  Store this
-      // character and keep reading until we find a comma or the end of the
-      // string.
-      token.append(c);
-    }
-
-
-    // Enter a loop, reading until we find the appropriate criteria for the end
-    // of the token.
-    while (pos < length)
-    {
-      c = credentials.charAt(pos++);
-
-      if (isEscaped)
-      {
-        // The previous character was an escape, so we'll take this no matter
-        // what.
-        token.append(c);
-        isEscaped = false;
-      }
-      else if (c == ',')
-      {
-        // 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
-        {
-          break;
-        }
-      }
-      else if (c == '"')
-      {
-        if (isQuoted)
-        {
-          // This should be the end of the token, but in order for it to be
-          // valid it must be followed by a comma or the end of the string.
-          if (pos >= length)
-          {
-            // We have hit the end of the string, so this is fine.
-            break;
-          }
-          else
-          {
-            char c2 = credentials.charAt(pos++);
-            if (c2 == ',')
-            {
-              // We have hit the end of the token, so this is fine.
-              break;
-            }
-            else
-            {
-              // We found the closing quote before the end of the token.  This
-              // is not fine.
-              Message message =
-                  ERR_SASLDIGESTMD5_INVALID_CLOSING_QUOTE_POS.get((pos-2));
-              throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
-                                           message);
-            }
-          }
-        }
-        else
-        {
-          // This must be part of the value, so we'll take it.
-          token.append(c);
-        }
-      }
-      else if (c == '\\')
-      {
-        // The next character is escaped.  We'll set a flag so we know to
-        // accept it, but will not include the backspace itself.
-        isEscaped = true;
-      }
-      else
-      {
-        token.append(c);
-      }
-    }
-
-
-    return pos;
-  }
-
-
-
-  /**
-   * Generates the appropriate DIGEST-MD5 response for the provided set of
-   * information.
-   *
-   * @param  userName    The username from the authentication request.
-   * @param  authzID     The authorization ID from the request, or
-   *                     <CODE>null</CODE> if there is none.
-   * @param  password    The clear-text password for the user.
-   * @param  realm       The realm for which the authentication is to be
-   *                     performed.
-   * @param  nonce       The random data generated by the server for use in the
-   *                     digest.
-   * @param  cnonce      The random data generated by the client for use in the
-   *                     digest.
-   * @param  nonceCount  The 8-digit hex string indicating the number of times
-   *                     the provided nonce has been used by the client.
-   * @param  digestURI   The digest URI that specifies the service and host for
-   *                     which the authentication is being performed.
-   * @param  qop         The quality of protection string for the
-   *                     authentication.
-   * @param  charset     The character set used to encode the information.
-   *
-   * @return  The DIGEST-MD5 response for the provided set of information.
-   *
-   * @throws  UnsupportedEncodingException  If the specified character set is
-   *                                        invalid for some reason.
-   */
-  public byte[] generateResponseDigest(String userName, String authzID,
-                                       byte[] password, String realm,
-                                       String nonce, String cnonce,
-                                       String nonceCount, String digestURI,
-                                       String qop, String charset)
-         throws UnsupportedEncodingException
-  {
-    synchronized (digestLock)
-    {
-      // First, get a hash of "username:realm:password".
-      StringBuilder a1String1 = new StringBuilder();
-      a1String1.append(userName);
-      a1String1.append(':');
-      a1String1.append(realm);
-      a1String1.append(':');
-
-      byte[] a1Bytes1a = a1String1.toString().getBytes(charset);
-      byte[] a1Bytes1  = new byte[a1Bytes1a.length + password.length];
-      System.arraycopy(a1Bytes1a, 0, a1Bytes1, 0, a1Bytes1a.length);
-      System.arraycopy(password, 0, a1Bytes1, a1Bytes1a.length,
-                       password.length);
-      byte[] urpHash = md5Digest.digest(a1Bytes1);
-
-
-      // Next, get a hash of "urpHash:nonce:cnonce[:authzid]".
-      StringBuilder a1String2 = new StringBuilder();
-      a1String2.append(':');
-      a1String2.append(nonce);
-      a1String2.append(':');
-      a1String2.append(cnonce);
-      if (authzID != null)
-      {
-        a1String2.append(':');
-        a1String2.append(authzID);
-      }
-      byte[] a1Bytes2a = a1String2.toString().getBytes(charset);
-      byte[] a1Bytes2  = new byte[urpHash.length + a1Bytes2a.length];
-      System.arraycopy(urpHash, 0, a1Bytes2, 0, urpHash.length);
-      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 kdString = new StringBuilder();
-      kdString.append(a1HashHex);
-      kdString.append(':');
-      kdString.append(nonce);
-      kdString.append(':');
-      kdString.append(nonceCount);
-      kdString.append(':');
-      kdString.append(cnonce);
-      kdString.append(':');
-      kdString.append(qop);
-      kdString.append(':');
-      kdString.append(a2HashHex);
-      return md5Digest.digest(kdString.toString().getBytes(charset));
-    }
-  }
-
-
-
-  /**
-   * Generates the appropriate DIGEST-MD5 rspauth digest using the provided
-   * information.
-   *
-   * @param  userName    The username from the authentication request.
-   * @param  authzID     The authorization ID from the request, or
-   *                     <CODE>null</CODE> if there is none.
-   * @param  password    The clear-text password for the user.
-   * @param  realm       The realm for which the authentication is to be
-   *                     performed.
-   * @param  nonce       The random data generated by the server for use in the
-   *                     digest.
-   * @param  cnonce      The random data generated by the client for use in the
-   *                     digest.
-   * @param  nonceCount  The 8-digit hex string indicating the number of times
-   *                     the provided nonce has been used by the client.
-   * @param  digestURI   The digest URI that specifies the service and host for
-   *                     which the authentication is being performed.
-   * @param  qop         The quality of protection string for the
-   *                     authentication.
-   * @param  charset     The character set used to encode the information.
-   *
-   * @return  The DIGEST-MD5 response for the provided set of information.
-   *
-   * @throws  UnsupportedEncodingException  If the specified character set is
-   *                                        invalid for some reason.
-   */
-  public byte[] generateResponseAuthDigest(String userName, String authzID,
-                                           byte[] password, String realm,
-                                           String nonce, String cnonce,
-                                           String nonceCount, String digestURI,
-                                           String qop, String charset)
-         throws UnsupportedEncodingException
-  {
-    synchronized (digestLock)
-    {
-      // First, get a hash of "username:realm:password".
-      StringBuilder a1String1 = new StringBuilder();
-      a1String1.append(userName);
-      a1String1.append(':');
-      a1String1.append(realm);
-      a1String1.append(':');
-
-      byte[] a1Bytes1a = a1String1.toString().getBytes(charset);
-      byte[] a1Bytes1  = new byte[a1Bytes1a.length + password.length];
-      System.arraycopy(a1Bytes1a, 0, a1Bytes1, 0, a1Bytes1a.length);
-      System.arraycopy(password, 0, a1Bytes1, a1Bytes1a.length,
-                       password.length);
-      byte[] urpHash = md5Digest.digest(a1Bytes1);
-
-
-      // Next, get a hash of "urpHash:nonce:cnonce[:authzid]".
-      StringBuilder a1String2 = new StringBuilder();
-      a1String2.append(':');
-      a1String2.append(nonce);
-      a1String2.append(':');
-      a1String2.append(cnonce);
-      if (authzID != null)
-      {
-        a1String2.append(':');
-        a1String2.append(authzID);
-      }
-      byte[] a1Bytes2a = a1String2.toString().getBytes(charset);
-      byte[] a1Bytes2  = new byte[urpHash.length + a1Bytes2a.length];
-      System.arraycopy(urpHash, 0, a1Bytes2, 0, urpHash.length);
-      System.arraycopy(a1Bytes2a, 0, a1Bytes2, urpHash.length,
-                       a1Bytes2a.length);
-      byte[] a1Hash = md5Digest.digest(a1Bytes2);
-
-
-      // Next, get a hash of "AUTHENTICATE:digesturi".
-      String a2String = ":" + digestURI;
-      if (qop.equals("auth-int") || qop.equals("auth-conf"))
-      {
-        a2String += ":00000000000000000000000000000000";
-      }
-      byte[] a2Bytes = a2String.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 kdString = new StringBuilder();
-      kdString.append(a1HashHex);
-      kdString.append(':');
-      kdString.append(nonce);
-      kdString.append(':');
-      kdString.append(nonceCount);
-      kdString.append(':');
-      kdString.append(cnonce);
-      kdString.append(':');
-      kdString.append(qop);
-      kdString.append(':');
-      kdString.append(a2HashHex);
-      return md5Digest.digest(kdString.toString().getBytes(charset));
-    }
-  }
-
-
-
-  /**
-   * Retrieves a hexadecimal string representation of the contents of the
-   * provided byte array.
-   *
-   * @param  byteArray  The byte array for which to obtain the hexadecimal
-   *                    string representation.
-   *
-   * @return  The hexadecimal string representation of the contents of the
-   *          provided byte array.
-   */
-  private String getHexString(byte[] byteArray)
-  {
-    StringBuilder buffer = new StringBuilder(2*byteArray.length);
-    for (byte b : byteArray)
-    {
-      buffer.append(byteToLowerHex(b));
-    }
-
-    return buffer.toString();
-  }
-
-
-
   /**
    * {@inheritDoc}
    */
@@ -1554,7 +210,6 @@
   }
 
 
-
   /**
    * {@inheritDoc}
    */
@@ -1569,7 +224,6 @@
   }
 
 
-
   /**
    * {@inheritDoc}
    */
@@ -1581,23 +235,116 @@
   }
 
 
-
   /**
    * {@inheritDoc}
    */
   public ConfigChangeResult applyConfigurationChange(
-              DigestMD5SASLMechanismHandlerCfg configuration)
+          DigestMD5SASLMechanismHandlerCfg configuration)
   {
-    ResultCode        resultCode          = ResultCode.SUCCESS;
-    boolean           adminActionRequired = false;
-    ArrayList<Message> messages            = new ArrayList<Message>();
+      ResultCode        resultCode          = ResultCode.SUCCESS;
+      boolean           adminActionRequired = false;
 
-    // Get the identity mapper that should be used to find users.
-    DN identityMapperDN = configuration.getIdentityMapperDN();
-    identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
-    currentConfig  = configuration;
+      ArrayList<Message> messages            = new ArrayList<Message>();
+      try {
+          DN identityMapperDN = configuration.getIdentityMapperDN();
+          identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
+          serverFQDN = getFQDN(configuration);
+          Message msg = INFO_DIGEST_MD5_SERVER_FQDN.get(serverFQDN);
+          logError(msg);
+          String QOP = getQOP(configuration);
+          saslProps = new HashMap<String,String>();
+          saslProps.put(Sasl.QOP, QOP);
+          if(QOP.equalsIgnoreCase(SASL_MECHANISM_CONFIDENTIALITY)) {
+              saslProps.put(Sasl.STRENGTH, getStrength(configuration));
+          }
+          String realm=getRealm(configuration);
+          if(realm != null) {
+               msg = INFO_DIGEST_MD5_REALM.get(realm);
+              logError(msg);
+             saslProps.put(REALM_PROPERTY, getRealm(configuration));
+          }
+          this.configuration  = configuration;
+      } catch (UnknownHostException unhe) {
+          if (debugEnabled()) {
+              TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
+          }
+          resultCode = ResultCode.OPERATIONS_ERROR;
+          messages.add(ERR_SASL_CANNOT_GET_SERVER_FQDN.get(
+                  String.valueOf(configEntryDN), getExceptionMessage(unhe)));
+          return new ConfigChangeResult(resultCode,adminActionRequired,
+                  messages);
+      }
+      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+  }
 
-    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
+
+  /**
+   * Retrieves the cipher strength string to use if confidentiality is enforce.
+   * This determination is the lowest value that the server can use.
+   *
+   * @param configuration The configuration to examine.
+   * @return The cipher strength string.
+   */
+  private String
+  getStrength(DigestMD5SASLMechanismHandlerCfg configuration) {
+      CipherStrength strength = configuration.getCipherStrength();
+      if(strength.equals(CipherStrength.HIGH)) {
+          return "high";
+      } else if(strength.equals(CipherStrength.MEDIUM)) {
+          return "high,medium";
+      } else {
+          return "high,medium,low";
+      }
+  }
+
+
+  /**
+   * Retrieves the QOP (quality-of-protection) from the specified
+   * configuration.
+   *
+   * @param configuration The new configuration to use.
+   * @return A string representing the quality-of-protection.
+   */
+  private String
+  getQOP(DigestMD5SASLMechanismHandlerCfg configuration) {
+      QualityOfProtection QOP = configuration.getQualityOfProtection();
+      if(QOP.equals(QualityOfProtection.CONFIDENTIALITY))
+          return "auth-conf";
+      else if(QOP.equals(QualityOfProtection.INTEGRITY))
+          return "auth-int";
+      else
+          return "auth";
+  }
+
+
+  /**
+   * Returns the fully qualified name either defined in the configuration, or,
+   * determined by examining the system configuration.
+   *
+   * @param configuration The configuration to check.
+   * @return The fully qualified hostname of the server.
+   *
+   * @throws UnknownHostException If the name cannot be determined from the
+   *                              system configuration.
+   */
+  private String getFQDN(DigestMD5SASLMechanismHandlerCfg configuration)
+  throws UnknownHostException {
+      String serverName = configuration.getServerFqdn();
+      if (serverName == null) {
+              serverName = InetAddress.getLocalHost().getCanonicalHostName();
+      }
+      return serverName;
+  }
+
+
+  /**
+   * Retrieve the realm either defined in the specified configuration. If this
+   * isn't defined, the SaslServer internal code uses the server name.
+   *
+   * @param configuration The configuration to check.
+   * @return A string representing the realm.
+   */
+  private String getRealm(DigestMD5SASLMechanismHandlerCfg configuration) {
+    return configuration.getRealm();
   }
 }
-

--
Gitblit v1.10.0