From 4a584b3c73a9dc2546b3673008e2321e0fdf8474 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 09 Feb 2012 09:28:16 +0000
Subject: [PATCH] Preparation work for OPENDJ-420: Rare SSLExceptions while handling LDAPS connections and big LDAP searches

---
 opends/src/server/org/opends/server/extensions/SASLContext.java | 2009 +++++++++++++++++++++++++++++++++-------------------------
 1 files changed, 1,136 insertions(+), 873 deletions(-)

diff --git a/opends/src/server/org/opends/server/extensions/SASLContext.java b/opends/src/server/org/opends/server/extensions/SASLContext.java
index 7d68665..78a6c5b 100644
--- a/opends/src/server/org/opends/server/extensions/SASLContext.java
+++ b/opends/src/server/org/opends/server/extensions/SASLContext.java
@@ -28,26 +28,29 @@
 
 package org.opends.server.extensions;
 
+
+
+import static org.opends.messages.ExtensionMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.ServerConstants.SASL_DEFAULT_PROTOCOL;
+import static org.opends.server.util.ServerConstants.SASL_MECHANISM_DIGEST_MD5;
+import static org.opends.server.util.ServerConstants.SASL_MECHANISM_GSSAPI;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.HashMap;
 import java.util.List;
 import java.util.concurrent.locks.Lock;
+
 import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.callback.*;
 import javax.security.auth.login.LoginContext;
-import javax.security.sasl.AuthorizeCallback;
-import javax.security.sasl.RealmCallback;
-import javax.security.sasl.Sasl;
-import javax.security.sasl.SaslException;
-import javax.security.sasl.SaslServer;
+import javax.security.sasl.*;
 
 import org.ietf.jgss.GSSException;
-import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.messages.Message;
 import org.opends.server.api.AuthenticationPolicyState;
 import org.opends.server.api.ClientConnection;
@@ -56,908 +59,1168 @@
 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.internal.InternalClientConnection;
 import org.opends.server.protocols.ldap.LDAPClientConnection;
 import org.opends.server.types.*;
 
-import static org.opends.messages.ExtensionMessages.*;
-import static org.opends.server.loggers.debug.DebugLogger.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
+
 
 /**
  * This class defines the SASL context needed to process GSSAPI and DIGEST-MD5
  * bind requests from clients.
- *
  */
-public class
-SASLContext implements CallbackHandler, PrivilegedExceptionAction<Boolean> {
+public class SASLContext implements CallbackHandler,
+    PrivilegedExceptionAction<Boolean>
+{
+
+  // The tracer object for the debug logger.
+  private static final DebugTracer TRACER = getTracer();
 
 
-    // The tracer object for the debug logger.
-    private static final DebugTracer TRACER = getTracer();
 
-    // The SASL server to use in the authentication.
-    private SaslServer saslServer=null;
-
-    // The identity mapper to use when mapping identities.
-    private final IdentityMapper<?> identityMapper;
-
-    //The  property set to use when creating the SASL server.
-    private HashMap<String, String>saslProps;
-
-    //The fully qualified domain name to use when creating the SASL server.
-    private String serverFQDN;
-
-    //The SASL mechanism name.
-    private final String mechanism;
-
-    //The authorization entry used in the authentication.
-    private Entry authEntry=null;
-
-    //The authorization entry used in the authentication.
-    private Entry authzEntry=null;
-
-    //The user name used in the authentication taken from the name callback.
-    private String userName;
-
-    //Error message used by callbacks.
-    private Message cbMsg;
-
-    //Error code used by callbacks.
-    private ResultCode cbResultCode;
-
-    //The current bind operation used by the callbacks.
-    private BindOperation bindOp;
-
-    //Used to check if negotiated QOP is confidentiality or integrity.
-    private static final String confidentiality = "auth-conf";
-    private static final String integrity = "auth-int";
+  /**
+   * Instantiate a GSSAPI/DIGEST-MD5 SASL context using the specified
+   * parameters.
+   *
+   * @param saslProps
+   *          The properties to use in creating the SASL server.
+   * @param serverFQDN
+   *          The fully qualified domain name to use in creating the SASL
+   *          server.
+   * @param mechanism
+   *          The SASL mechanism name.
+   * @param identityMapper
+   *          The identity mapper to use in mapping identities.
+   * @return A fully instantiated SASL context to use in processing a SASL bind
+   *         for the GSSAPI or DIGEST-MD5 mechanisms.
+   * @throws SaslException
+   *           If the SASL server can not be instantiated.
+   */
+  public static SASLContext createSASLContext(
+      final HashMap<String, String> saslProps, final String serverFQDN,
+      final String mechanism, final IdentityMapper<?> identityMapper)
+      throws SaslException
+  {
+    return (new SASLContext(saslProps, serverFQDN, mechanism, identityMapper));
+  }
 
 
-    /**
-     * Create a SASL context using the specified parameters. A SASL server will
-     * be instantiated only for the DIGEST-MD5 mechanism. The GSSAPI mechanism
-     * must instantiate the SASL server as the login context in a separate step.
-     *
-     * @param saslProps The properties to use in creating the SASL server.
-     * @param serverFQDN The fully qualified domain name to use in creating the
-     *                   SASL server.
-     * @param mechanism The SASL mechanism name.
-     * @param identityMapper The identity mapper to use in mapping identities.
-     *
-     * @throws SaslException If the SASL server can not be instantiated.
-     */
-    private SASLContext(HashMap<String, String>saslProps, String serverFQDN,
-                          String mechanism, IdentityMapper<?> identityMapper)
-                          throws SaslException {
-        this.identityMapper = identityMapper;
-        this.mechanism = mechanism;
-        this.saslProps = saslProps;
-        this.serverFQDN = serverFQDN;
-        if(mechanism.equals(SASL_MECHANISM_DIGEST_MD5)) {
-            initSASLServer();
+
+  // The SASL server to use in the authentication.
+  private SaslServer saslServer = null;
+
+  // The identity mapper to use when mapping identities.
+  private final IdentityMapper<?> identityMapper;
+
+  // The property set to use when creating the SASL server.
+  private final HashMap<String, String> saslProps;
+
+  // The fully qualified domain name to use when creating the SASL server.
+  private final String serverFQDN;
+
+  // The SASL mechanism name.
+  private final String mechanism;
+
+  // The authorization entry used in the authentication.
+  private Entry authEntry = null;
+
+  // The authorization entry used in the authentication.
+  private Entry authzEntry = null;
+
+  // The user name used in the authentication taken from the name callback.
+  private String userName;
+
+  // Error message used by callbacks.
+  private Message cbMsg;
+
+  // Error code used by callbacks.
+  private ResultCode cbResultCode;
+
+  // The current bind operation used by the callbacks.
+  private BindOperation bindOp;
+
+  // Used to check if negotiated QOP is confidentiality or integrity.
+  private static final String confidentiality = "auth-conf";
+  private static final String integrity = "auth-int";
+
+
+
+  /**
+   * Create a SASL context using the specified parameters. A SASL server will be
+   * instantiated only for the DIGEST-MD5 mechanism. The GSSAPI mechanism must
+   * instantiate the SASL server as the login context in a separate step.
+   *
+   * @param saslProps
+   *          The properties to use in creating the SASL server.
+   * @param serverFQDN
+   *          The fully qualified domain name to use in creating the SASL
+   *          server.
+   * @param mechanism
+   *          The SASL mechanism name.
+   * @param identityMapper
+   *          The identity mapper to use in mapping identities.
+   * @throws SaslException
+   *           If the SASL server can not be instantiated.
+   */
+  private SASLContext(final HashMap<String, String> saslProps,
+      final String serverFQDN, final String mechanism,
+      final IdentityMapper<?> identityMapper) throws SaslException
+  {
+    this.identityMapper = identityMapper;
+    this.mechanism = mechanism;
+    this.saslProps = saslProps;
+    this.serverFQDN = serverFQDN;
+
+    if (mechanism.equals(SASL_MECHANISM_DIGEST_MD5))
+    {
+      initSASLServer();
+    }
+  }
+
+
+
+  /**
+   * Process the specified callback array.
+   *
+   * @param callbacks
+   *          An array of callbacks that need processing.
+   * @throws UnsupportedCallbackException
+   *           If a callback is not supported.
+   */
+  public void handle(final Callback[] callbacks)
+      throws UnsupportedCallbackException
+  {
+    for (final Callback callback : callbacks)
+    {
+      if (callback instanceof NameCallback)
+      {
+        nameCallback((NameCallback) callback);
+      }
+      else if (callback instanceof PasswordCallback)
+      {
+        passwordCallback((PasswordCallback) callback);
+      }
+      else if (callback instanceof RealmCallback)
+      {
+        realmCallback((RealmCallback) callback);
+      }
+      else if (callback instanceof AuthorizeCallback)
+      {
+        authorizeCallback((AuthorizeCallback) callback);
+      }
+      else
+      {
+        final Message message = INFO_SASL_UNSUPPORTED_CALLBACK.get(mechanism,
+            String.valueOf(callback));
+        throw new UnsupportedCallbackException(callback, message.toString());
+      }
+    }
+  }
+
+
+
+  /**
+   * The method performs all GSSAPI processing. It is run as the context of the
+   * login context performed by the GSSAPI mechanism handler. See comments for
+   * processing overview.
+   *
+   * @return {@code true} if the authentication processing was successful.
+   */
+  public Boolean run()
+  {
+    final ClientConnection clientConn = bindOp.getClientConnection();
+
+    // If the SASL server is null then this is the first handshake and the
+    // server needs to be initialized before any processing can be performed.
+    // If the SASL server cannot be created then all processing is abandoned
+    // and INVALID_CREDENTIALS is returned to the client.
+    if (saslServer == null)
+    {
+      try
+      {
+        initSASLServer();
+      }
+      catch (final SaslException ex)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, ex);
         }
-    }
+        final GSSException gex = (GSSException) ex.getCause();
 
-
-    /**
-     * Instantiate a GSSAPI/DIGEST-MD5 SASL context using the specified
-     * parameters.
-     *
-     * @param saslProps The properties to use in creating the SASL server.
-     * @param serverFQDN The fully qualified domain name to use in creating the
-     *                   SASL server.
-     * @param mechanism The SASL mechanism name.
-     * @param identityMapper The identity mapper to use in mapping identities.
-     * @return A fully instantiated SASL context to use in processing a SASL
-     *         bind for the GSSAPI or DIGEST-MD5 mechanisms.
-     *
-     * @throws SaslException If the SASL server can not be instantiated.
-     */
-    public static
-    SASLContext createSASLContext(HashMap<String,String>saslProps,
-                        String serverFQDN, String mechanism,
-                        IdentityMapper<?> identityMapper) throws SaslException {
-      return (new SASLContext(saslProps,serverFQDN, mechanism, identityMapper));
-    }
-
-
-    /**
-     * Initialize the SASL server using parameters specified in the
-     * constructor.
-     */
-    private void initSASLServer() throws SaslException {
-       saslServer = Sasl.createSaslServer(mechanism, SASL_DEFAULT_PROTOCOL,
-                                               serverFQDN, saslProps, this);
-       if(saslServer == null) {
-         Message msg =
-                 ERR_SASL_CREATE_SASL_SERVER_FAILED.get(mechanism, serverFQDN);
-         throw new SaslException(Message.toString(msg));
-       }
-    }
-
-
-    /**
-     * Wrap the specified clear byte array using the provided offset and length
-     * values. Used only when the SASL server has negotiated
-     * confidentiality/integrity  processing.
-     *
-     * @param clearBytes The clear byte array to wrap.
-     * @param offset The offset into the clear byte array..
-     * @param len The length from the offset of the number of bytes to wrap.
-     * @return A byte array containing the wrapped bytes.
-     *
-     * @throws SaslException If the clear bytes cannot be wrapped.
-     */
-    byte[] wrap(byte[] clearBytes, int offset, int len)
-    throws SaslException {
-        return saslServer.wrap(clearBytes, offset, len);
-    }
-
-
-    /**
-     * Unwrap the specified byte array using the provided offset and length
-     * values. Used only when the SASL server has negotiated
-     * confidentiality or integrity processing.
-     *
-     * @param bytes The byte array to unwrap.
-     * @param offset The offset in the array.
-     * @param len The length from the offset of the number of bytes to unwrap.
-     * @return A byte array containing the clear or unwrapped bytes.
-     *
-     * @throws SaslException If the bytes cannot be unwrapped.
-     */
-    byte[] unwrap(byte[] bytes, int offset, int len)
-    throws SaslException {
-        return saslServer.unwrap(bytes, offset, len);
-    }
-
-
-    /**
-     * Dispose of the SASL server instance.
-     */
-    void dispose() {
-        try {
-            saslServer.dispose();
-        } catch (SaslException e) {
-            if (debugEnabled()) {
-                TRACER.debugCaught(DebugLogLevel.ERROR, e);
-              }
+        final Message msg;
+        if (gex != null)
+        {
+          msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
+              GSSAPISASLMechanismHandler.getGSSExceptionMessage(gex));
         }
-    }
-
-
-    /**
-     * Return the negotiated buffer size.
-     *
-     * @param prop The buffer size property to return.
-     * @return The value of the negotiated buffer size.
-     */
-    int getBufSize(String prop) {
-          String sizeStr =
-              (String) saslServer.getNegotiatedProperty(prop);
-          return Integer.parseInt(sizeStr);
-    }
-
-    /**
-     * Return the Security Strength Factor of the cipher if the QOP property
-     * is confidentiality, or, 1 if it is integrity.
-     *
-     * @return The SSF of the cipher used during confidentiality or
-     *         integrity processing.
-     */
-    int getSSF() {
-        int ssf = 0;
-        String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
-        if(qop.equalsIgnoreCase(integrity)) {
-            ssf = 1;
-        } else {
-            String negStrength =
-                (String) saslServer.getNegotiatedProperty(Sasl.STRENGTH);
-            if(negStrength.equalsIgnoreCase("low"))
-                ssf = 40;
-            else if (negStrength.equalsIgnoreCase("medium"))
-                ssf = 56;
-            else
-                ssf = 128;
+        else
+        {
+          msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
+              getExceptionMessage(ex));
         }
-        return ssf;
-    }
 
-    /**
-     * Return {@code true} if the bind has been completed. If the context is
-     * supporting confidentiality or integrity, the security provider will need
-     * to check if the context has completed the handshake with the client
-     * and is ready to process confidentiality or integrity messages.
-     *
-     * @return {@code true} if the handshaking is complete.
-     */
-    boolean isBindComplete() {
-          return saslServer.isComplete();
-    }
-
-
-    /**
-     * Return true if the SASL server has negotiated with the client to support
-     * confidentiality or integrity.
-     *
-     * @return {@code true} if the context supports confidentiality or
-     *         integrity.
-     */
-    private boolean isConfidentialIntegrity() {
-      boolean ret = false;
-      String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
-      if(qop.equalsIgnoreCase(confidentiality) ||
-         qop.equalsIgnoreCase(integrity))
-           ret = true;
-      return ret;
-    }
-
-
-    /**
-     * Helper routine to call the SASL server evaluateResponse method with the
-     * specified byte array.
-     *
-     * @param bytes The byte array to pass to the SASL server.
-     * @return A byte array containing the result of the evaluation.
-     *
-     * @throws SaslException If the SASL server cannot evaluate the byte array.
-     */
-    private ByteString evaluateResponse(ByteString response)
-      throws SaslException  {
-      if (response == null)
-        response = ByteString.empty();
-      byte[] evalResponse = saslServer.evaluateResponse(response.toByteArray());
-      if(evalResponse == null)
-        return ByteString.empty();
-      else return ByteString.wrap(evalResponse);
-    }
-
-
-    /**
-     * This method is used to process an exception that is thrown during bind
-     * processing. It will try to determine if the exception is a result of
-     * callback processing, and if it is, will try to use a more informative
-     * failure message set by the callback.
-     *
-     * If the exception is a result of a error during the the SASL server
-     * processing, the callback message will be null, and
-     * the method will use the specified message parameter as the
-     * failure reason. This is a more cryptic exception message hard-coded
-     * in the SASL server internals.
-     *
-     * The method also disposes of the SASL server, clears the authentication
-     * state  and sets the result code to INVALID_CREDENTIALs
-     *
-     * @param msg The message to use if the callback message is not null.
-     */
-    private void handleError(Message msg) {
-        dispose();
-        ClientConnection clientConn = bindOp.getClientConnection();
         clientConn.setSASLAuthStateInfo(null);
+        bindOp.setAuthFailureReason(msg);
+        bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+        return false;
+      }
+    }
 
-        //Check if the callback message is null and use that message if not.
-        if (cbResultCode != null)
+    final ByteString clientCredentials = bindOp.getSASLCredentials();
+    clientConn.setSASLAuthStateInfo(null);
+    try
+    {
+      final ByteString responseAuthStr = evaluateResponse(clientCredentials);
+
+      // If the bind has not been completed,then
+      // more handshake is needed and SASL_BIND_IN_PROGRESS is returned back
+      // to the client.
+      if (isBindComplete())
+      {
+        bindOp.setResultCode(ResultCode.SUCCESS);
+        bindOp.setSASLAuthUserEntry(authEntry);
+        final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
+            authzEntry, mechanism, clientCredentials,
+            DirectoryServer.isRootDN(authEntry.getDN()));
+        bindOp.setAuthenticationInfo(authInfo);
+
+        // If confidentiality/integrity has been negotiated then
+        // create a SASL security provider and save it in the client
+        // connection. If confidentiality/integrity has not been
+        // negotiated, dispose of the SASL server.
+        if (isConfidentialIntegrity())
         {
-          bindOp.setResultCode(cbResultCode);
+          final SASLByteChannel saslByteChannel = SASLByteChannel
+              .getSASLByteChannel(clientConn, mechanism, this);
+          final LDAPClientConnection ldapConn =
+              (LDAPClientConnection) clientConn;
+          ldapConn.setSASLPendingProvider(saslByteChannel);
         }
         else
         {
-          bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+          dispose();
+          clientConn.setSASLAuthStateInfo(null);
         }
+      }
+      else
+      {
+        bindOp.setServerSASLCredentials(responseAuthStr);
+        clientConn.setSASLAuthStateInfo(this);
+        bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
+      }
+    }
+    catch (final SaslException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
 
-        if (cbMsg != null)
+      final Message msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism,
+          getExceptionMessage(e));
+      handleError(msg);
+      return false;
+    }
+
+    return true;
+  }
+
+
+
+  /**
+   * Dispose of the SASL server instance.
+   */
+  void dispose()
+  {
+    try
+    {
+      saslServer.dispose();
+    }
+    catch (final SaslException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+    }
+  }
+
+
+
+  /**
+   * Evaluate the final stage of a DIGEST-MD5 SASL bind using the specified bind
+   * operation.
+   *
+   * @param bindOp
+   *          The bind operation to use in processing.
+   */
+  void evaluateFinalStage(final BindOperation bindOp)
+  {
+    this.bindOp = bindOp;
+    final ByteString clientCredentials = bindOp.getSASLCredentials();
+
+    if ((clientCredentials == null) || (clientCredentials.length() == 0))
+    {
+      final Message msg = ERR_SASL_NO_CREDENTIALS.get(mechanism, mechanism);
+      handleError(msg);
+      return;
+    }
+
+    final ClientConnection clientConn = bindOp.getClientConnection();
+    clientConn.setSASLAuthStateInfo(null);
+
+    try
+    {
+      final ByteString responseAuthStr = evaluateResponse(clientCredentials);
+      bindOp.setResultCode(ResultCode.SUCCESS);
+      bindOp.setServerSASLCredentials(responseAuthStr);
+      bindOp.setSASLAuthUserEntry(authEntry);
+      final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
+          authzEntry, mechanism, clientCredentials,
+          DirectoryServer.isRootDN(authEntry.getDN()));
+      bindOp.setAuthenticationInfo(authInfo);
+
+      // If confidentiality/integrity has been negotiated, then create a
+      // SASL security provider and save it in the client connection for
+      // use in later processing.
+      if (isConfidentialIntegrity())
+      {
+        final SASLByteChannel saslByteChannel = SASLByteChannel
+            .getSASLByteChannel(clientConn, mechanism, this);
+        final LDAPClientConnection ldapConn = (LDAPClientConnection) clientConn;
+        ldapConn.setSASLPendingProvider(saslByteChannel);
+      }
+      else
+      {
+        dispose();
+        clientConn.setSASLAuthStateInfo(null);
+      }
+    }
+    catch (final SaslException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      final Message msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism,
+          getExceptionMessage(e));
+      handleError(msg);
+    }
+  }
+
+
+
+  /**
+   * Process the initial stage of a DIGEST-MD5 SASL bind using the specified
+   * bind operation.
+   *
+   * @param bindOp
+   *          The bind operation to use in processing.
+   */
+  void evaluateInitialStage(final BindOperation bindOp)
+  {
+    this.bindOp = bindOp;
+    final ClientConnection clientConn = bindOp.getClientConnection();
+
+    try
+    {
+      final ByteString challenge = evaluateResponse(ByteString.empty());
+      bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
+      bindOp.setServerSASLCredentials(challenge);
+      clientConn.setSASLAuthStateInfo(this);
+    }
+    catch (final SaslException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      final Message msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism,
+          getExceptionMessage(e));
+      handleError(msg);
+    }
+  }
+
+
+
+  /**
+   * Return the negotiated buffer size.
+   *
+   * @param prop
+   *          The buffer size property to return.
+   * @return The value of the negotiated buffer size.
+   */
+  int getBufSize(final String prop)
+  {
+    final String sizeStr = (String) saslServer.getNegotiatedProperty(prop);
+    return Integer.parseInt(sizeStr);
+  }
+
+
+
+  /**
+   * Return the Security Strength Factor of the cipher if the QOP property is
+   * confidentiality, or, 1 if it is integrity.
+   *
+   * @return The SSF of the cipher used during confidentiality or integrity
+   *         processing.
+   */
+  int getSSF()
+  {
+    int ssf = 0;
+    final String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
+    if (qop.equalsIgnoreCase(integrity))
+    {
+      ssf = 1;
+    }
+    else
+    {
+      final String negStrength = (String) saslServer
+          .getNegotiatedProperty(Sasl.STRENGTH);
+      if (negStrength.equalsIgnoreCase("low"))
+      {
+        ssf = 40;
+      }
+      else if (negStrength.equalsIgnoreCase("medium"))
+      {
+        ssf = 56;
+      }
+      else
+      {
+        ssf = 128;
+      }
+    }
+    return ssf;
+  }
+
+
+
+  /**
+   * Return {@code true} if the bind has been completed. If the context is
+   * supporting confidentiality or integrity, the security provider will need to
+   * check if the context has completed the handshake with the client and is
+   * ready to process confidentiality or integrity messages.
+   *
+   * @return {@code true} if the handshaking is complete.
+   */
+  boolean isBindComplete()
+  {
+    return saslServer.isComplete();
+  }
+
+
+
+  /**
+   * Perform the authentication as the specified login context. The specified
+   * bind operation needs to be saved so the callbacks have access to it. Only
+   * used by the GSSAPI mechanism.
+   *
+   * @param loginContext
+   *          The login context to perform the authentication as.
+   * @param bindOp
+   *          The bind operation needed by the callbacks to process the
+   *          authentication.
+   */
+  void performAuthentication(final LoginContext loginContext,
+      final BindOperation bindOp)
+  {
+    this.bindOp = bindOp;
+    try
+    {
+      Subject.doAs(loginContext.getSubject(), this);
+    }
+    catch (final PrivilegedActionException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      final Message msg = ERR_SASL_PROTOCOL_ERROR.get(mechanism,
+          getExceptionMessage(e));
+      handleError(msg);
+    }
+  }
+
+
+
+  /**
+   * Unwrap the specified byte array using the provided offset and length
+   * values. Used only when the SASL server has negotiated confidentiality or
+   * integrity processing.
+   *
+   * @param bytes
+   *          The byte array to unwrap.
+   * @param offset
+   *          The offset in the array.
+   * @param len
+   *          The length from the offset of the number of bytes to unwrap.
+   * @return A byte array containing the clear or unwrapped bytes.
+   * @throws SaslException
+   *           If the bytes cannot be unwrapped.
+   */
+  byte[] unwrap(final byte[] bytes, final int offset, final int len)
+      throws SaslException
+  {
+    return saslServer.unwrap(bytes, offset, len);
+  }
+
+
+
+  /**
+   * Wrap the specified clear byte array using the provided offset and length
+   * values. Used only when the SASL server has negotiated
+   * confidentiality/integrity processing.
+   *
+   * @param clearBytes
+   *          The clear byte array to wrap.
+   * @param offset
+   *          The offset into the clear byte array..
+   * @param len
+   *          The length from the offset of the number of bytes to wrap.
+   * @return A byte array containing the wrapped bytes.
+   * @throws SaslException
+   *           If the clear bytes cannot be wrapped.
+   */
+  byte[] wrap(final byte[] clearBytes, final int offset, final int len)
+      throws SaslException
+  {
+    return saslServer.wrap(clearBytes, offset, len);
+  }
+
+
+
+  /**
+   * This callback is used to process the authorize callback. It is used during
+   * both GSSAPI and DIGEST-MD5 processing. When processing the GSSAPI
+   * mechanism, this is the only callback invoked. When processing the
+   * DIGEST-MD5 mechanism, it is the last callback invoked after the name and
+   * password callbacks respectively.
+   *
+   * @param callback
+   *          The authorize callback instance to process.
+   */
+  private void authorizeCallback(final AuthorizeCallback callback)
+  {
+    final String responseAuthzID = callback.getAuthorizationID();
+
+    // If the authEntry is null, then we are processing a GSSAPI SASL bind,
+    // and first need to try to map the authentication ID to an user entry.
+    // The authEntry is never null, when processing a DIGEST-MD5 SASL bind.
+    if (authEntry == null)
+    {
+      final String authid = callback.getAuthenticationID();
+      try
+      {
+        authEntry = identityMapper.getEntryForID(authid);
+        if (authEntry == null)
         {
-          bindOp.setAuthFailureReason(cbMsg);
-        }
-        else
-        {
-          bindOp.setAuthFailureReason(msg);
-        }
-    }
-
-
-    /**
-     * Checks the specified authentication information parameter against the
-     * privilege subsystem to see if it has PROXIED_AUTH privileges.
-     *
-     * @param authInfo The authentication information to use in the check.
-     * @return {@code true} if the authentication information has
-     *         PROXIED_AUTH privileges.
-     */
-    private boolean
-    hasPrivilege(AuthenticationInfo authInfo) {
-        boolean ret = true;
-          InternalClientConnection tempConn =
-               new InternalClientConnection(authInfo);
-          if (! tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOp)) {
-              setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_PRIVILEGES.get(
-                             String.valueOf(authEntry.getDN())));
-              ret = false;
-          }
-          return ret;
-    }
-
-
-    /**
-     * Checks the specified authentication information parameter against the
-     * access control subsystem to see if it has the "proxy" right.
-     *
-     * @param authInfo The authentication information to check access on.
-     * @return {@code true} if the authentication information has
-     *         proxy access.
-     */
-    private boolean
-    hasPermission(AuthenticationInfo authInfo) {
-        boolean ret = true;
-        Entry e = authzEntry;
-        //If the authz entry is null, use the entry associated with the NULL DN.
-        if(e == null) {
-            try {
-                e = DirectoryServer.getEntry(DN.nullDN());
-            } catch (DirectoryException ex) {
-                return false;
-            }
-        }
-        if (AccessControlConfigManager.getInstance().getAccessControlHandler().
-               mayProxy(authInfo.getAuthenticationEntry(), e,
-                        bindOp) == false) {
-            setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_ACCESS.get(
-                    String.valueOf(authEntry.getDN())));
-            ret = false;
-        }
-        return ret;
-    }
-
-
-    /**
-     * Sets the callback message to the specified message.
-     *
-     * @param cbMsg The message to set the callback message to.
-     */
-    private void setCallbackMsg(Message cbMsg) {
-        setCallbackMsg(ResultCode.INVALID_CREDENTIALS, cbMsg);
-    }
-
-
-    /**
-     * Sets the callback message to the specified message.
-     *
-     * @param cbResultCode The result code.
-     * @param cbMsg The message.
-     */
-    private void setCallbackMsg(ResultCode cbResultCode, Message cbMsg) {
-        this.cbResultCode = cbResultCode;
-        this.cbMsg = cbMsg;
-    }
-
-
-   /**
-     * Process the specified callback array.
-     *
-     *@param callbacks An array of callbacks that need processing.
-     *@throws UnsupportedCallbackException If a callback is not supported.
-     */
-    public void handle(Callback[] callbacks)
-    throws UnsupportedCallbackException {
-        for (Callback callback : callbacks) {
-            if (callback instanceof NameCallback) {
-                nameCallback((NameCallback) callback);
-            } else if (callback instanceof PasswordCallback) {
-                passwordCallback((PasswordCallback) callback);
-            } else if (callback instanceof RealmCallback) {
-                realmCallback((RealmCallback) callback);
-            } else if (callback instanceof AuthorizeCallback)  {
-                authorizeCallback((AuthorizeCallback) callback);
-            } else {
-                Message message =
-                    INFO_SASL_UNSUPPORTED_CALLBACK.get(mechanism,
-                                                      String.valueOf(callback));
-                throw new UnsupportedCallbackException(callback,
-                        message.toString());
-            }
-        }
-    }
-
-
-    /**
-     * This callback is used to process realm information. It is not used.
-     *
-     * @param callback The realm callback instance to process.
-     */
-    private void realmCallback(RealmCallback callback) {
-    }
-
-
-    /**
-     * This callback is used to process the authorize callback. It is used
-     * during both GSSAPI and DIGEST-MD5 processing. When processing the GSSAPI
-     * mechanism, this is the only callback invoked. When processing the
-     * DIGEST-MD5 mechanism, it is the last callback invoked after the name
-     * and password callbacks respectively.
-     *
-     * @param callback The authorize callback instance to process.
-     */
-    private void authorizeCallback(AuthorizeCallback callback) {
-        String  responseAuthzID = callback.getAuthorizationID();
-        //If the authEntry is null, then we are processing a GSSAPI SASL bind,
-        //and first need to try to map the authentication ID to an user entry.
-        //The authEntry is never null, when processing a DIGEST-MD5 SASL bind.
-        if(authEntry == null) {
-            String  authid = callback.getAuthenticationID();
-            try {
-                authEntry = identityMapper.getEntryForID(authid);
-                if (authEntry == null) {
-                 setCallbackMsg(ERR_SASL_AUTHENTRY_NO_MAPPED_ENTRY.get(authid));
-                 callback.setAuthorized(false);
-                 return;
-                }
-            } catch (DirectoryException de) {
-                if (debugEnabled()) {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, de);
-                }
-                setCallbackMsg(ERR_SASL_CANNOT_MAP_AUTHENTRY.get(authid,
-                        de.getMessage()));
-                callback.setAuthorized(false);
-                return;
-            }
-            userName=authid;
-        }
-        if (responseAuthzID.length() == 0) {
-            setCallbackMsg(ERR_SASLDIGESTMD5_EMPTY_AUTHZID.get());
-            callback.setAuthorized(false);
-            return;
-        } else if (!responseAuthzID.equals(userName))  {
-            String lowerAuthzID = toLowerCase(responseAuthzID);
-            //Process the callback differently depending on if the authzid
-            //string begins with the string "dn:" or not.
-            if (lowerAuthzID.startsWith("dn:")) {
-                authzDNCheck(callback);
-            } else {
-                authzIDCheck(callback);
-            }
-        } else {
-            authzEntry = authEntry;
-            callback.setAuthorized(true);
-        }
-    }
-
-
-    /**
-     * Process the specified authorize callback. This method is called if the
-     * callback's authorization ID does not begin with the string "dn:".
-     *
-     * @param callback The authorize callback to process.
-     */
-    private void authzIDCheck(AuthorizeCallback callback) {
-        String  authzid = callback.getAuthorizationID();
-        String lowerAuthzID = toLowerCase(authzid);
-        String idStr;
-        callback.setAuthorized(true);
-        if (lowerAuthzID.startsWith("u:")) {
-            idStr = authzid.substring(2);
-        } else {
-            idStr = authzid;
-        }
-        if (idStr.length() == 0) {
-            authzEntry = null;
-        } else {
-            try {
-                authzEntry = identityMapper.getEntryForID(idStr);
-                if (authzEntry == null) {
-                  setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid));
-                  callback.setAuthorized(false);
-                  return;
-                }
-            } catch (DirectoryException e) {
-                if (debugEnabled()) {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                }
-                setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid));
-                callback.setAuthorized(false);
-                return;
-            }
-        }
-        if ((authzEntry == null) ||
-                (!authzEntry.getDN().equals(authEntry.getDN()))) {
-            //Create temporary authorization information and run it both
-            //through the privilege and then the access control subsystems.
-            AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
-                    DirectoryServer.isRootDN(authEntry.getDN()));
-            if(!hasPrivilege(authInfo)) {
-                callback.setAuthorized(false);
-            } else {
-                callback.setAuthorized(hasPermission(authInfo));
-            }
-        }
-    }
-
-    /**
-     * Process the specified authorize callback. This method is called if the
-     * callback's authorization ID begins with the string "dn:".
-     *
-     * @param callback The authorize callback to process.
-     */
-    private void authzDNCheck(AuthorizeCallback callback) {
-        String  responseAuthzID = callback.getAuthorizationID();
-        DN authzDN;
-        callback.setAuthorized(true);
-        try {
-            authzDN = DN.decode(responseAuthzID.substring(3));
-        } catch (DirectoryException e) {
-            if (debugEnabled()) {
-                TRACER.debugCaught(DebugLogLevel.ERROR, e);
-            }
-            setCallbackMsg(ERR_SASL_AUTHZID_INVALID_DN.get(responseAuthzID,
-                                                        e.getMessageObject()));
-            callback.setAuthorized(false);
-            return;
-        }
-        DN actualAuthzDN =
-            DirectoryServer.getActualRootBindDN(authzDN);
-        if (actualAuthzDN != null) {
-            authzDN = actualAuthzDN;
-        }
-        if (!authzDN.equals(authEntry.getDN())) {
-            if (authzDN.isNullDN()) {
-                authzEntry = null;
-            } else {
-                try {
-                  if((authzEntry = DirectoryServer.getEntry(authzDN)) == null) {
-                        setCallbackMsg(ERR_SASL_AUTHZID_NO_SUCH_ENTRY.get(
-                                                      String.valueOf(authzDN)));
-                        callback.setAuthorized(false);
-                        return;
-                    }
-                } catch (DirectoryException e) {
-                    if (debugEnabled()) {
-                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                    }
-                    setCallbackMsg(ERR_SASL_AUTHZID_CANNOT_GET_ENTRY
-                          .get(String.valueOf(authzDN), e.getMessageObject()));
-                    callback.setAuthorized(false);
-                    return;
-                }
-            }
-            AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
-                                  DirectoryServer.isRootDN(authEntry.getDN()));
-            if(!hasPrivilege(authInfo)) {
-                callback.setAuthorized(false);
-            } else
-                callback.setAuthorized(hasPermission(authInfo));
-        }
-    }
-
-    /**
-     * Process the specified password callback. Used only for the DIGEST-MD5
-     * SASL mechanism. The password callback is processed after the name
-     * callback.
-     *
-     * @param passwordCallback The password callback to process.
-     */
-    private void passwordCallback(PasswordCallback passwordCallback) {
-        //If there is no authEntry this is an error.
-        if(authEntry == null) {
-            setCallbackMsg(ERR_SASL_NO_MATCHING_ENTRIES.get(userName));
-            return;
-        }
-        //Try to get a clear password to use.
-        List<ByteString> clearPasswords;
-        try {
-          AuthenticationPolicyState authState =
-            AuthenticationPolicyState.forUser(authEntry, false);
-
-          if (!authState.isPasswordPolicy())
-          {
-            Message message = ERR_SASL_ACCOUNT_NOT_LOCAL.get(
-                mechanism, String.valueOf(authEntry.getDN()));
-            setCallbackMsg(ResultCode.INAPPROPRIATE_AUTHENTICATION, message);
-            return;
-          }
-
-          PasswordPolicyState pwPolicyState = (PasswordPolicyState) authState;
-
-          clearPasswords = pwPolicyState.getClearPasswords();
-          if ((clearPasswords == null) || clearPasswords.isEmpty()) {
-              setCallbackMsg(
-                 ERR_SASL_NO_REVERSIBLE_PASSWORDS.get(mechanism,
-                                            String.valueOf(authEntry.getDN())));
-            return;
-          }
-        }
-        catch (Exception e) {
-            if (debugEnabled()) {
-                TRACER.debugCaught(DebugLogLevel.ERROR, e);
-            }
-            setCallbackMsg(ERR_SASL_CANNOT_GET_REVERSIBLE_PASSWORDS.get(
-                    String.valueOf(authEntry.getDN()),mechanism,
-                    String.valueOf(e)));
+          setCallbackMsg(ERR_SASL_AUTHENTRY_NO_MAPPED_ENTRY.get(authid));
+          callback.setAuthorized(false);
           return;
         }
-        //Use the first password.
-        char[] password = clearPasswords.get(0).toString().toCharArray();
-        passwordCallback.setPassword(password);
+      }
+      catch (final DirectoryException de)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, de);
+        }
+        setCallbackMsg(ERR_SASL_CANNOT_MAP_AUTHENTRY.get(authid,
+            de.getMessage()));
+        callback.setAuthorized(false);
         return;
+      }
+      userName = authid;
     }
 
-    /**
-     * Process the specified name callback. Used only for DIGEST-MD5 SASL
-     * mechanism.
-     *
-     * @param nameCallback The name callback to process.
-     */
-    private void nameCallback(NameCallback nameCallback) {
-        userName= nameCallback.getDefaultName();
-        String lowerUserName = toLowerCase(userName);
-        //Process the user name differently if it starts with the string "dn:".
-        if (lowerUserName.startsWith("dn:")) {
-            DN userDN;
-            try {
-                userDN = DN.decode(userName.substring(3));
-            } catch (DirectoryException e) {
-                if (debugEnabled()) {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                }
-                 setCallbackMsg(ERR_SASL_CANNOT_DECODE_USERNAME_AS_DN.get(
-                                      mechanism,
-                                      userName, e.getMessageObject()));
-                return;
-            }
-            if (userDN.isNullDN()) {
-              setCallbackMsg(ERR_SASL_USERNAME_IS_NULL_DN.get(
-                                                      mechanism));
-              return;
-            }
-            DN rootDN = DirectoryServer.getActualRootBindDN(userDN);
-            if (rootDN != null) {
-                userDN = rootDN;
-            }
-            getAuthEntry(userDN);
-        } else {
-            //The entry name is not a DN, try to map it using the identity
-            //mapper.
-            String entryID = userName;
-            if (lowerUserName.startsWith("u:")) {
-                if (lowerUserName.equals("u:")) {
-                    setCallbackMsg(ERR_SASL_ZERO_LENGTH_USERNAME.get(
-                            mechanism,mechanism));
-                    return;
-                }
-                entryID = userName.substring(2);
-            }
-            try {
-                authEntry = identityMapper.getEntryForID(entryID);
-            } catch (DirectoryException e) {
-                if (debugEnabled()) {
-                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
-                }
-               setCallbackMsg(ERR_SASLDIGESTMD5_CANNOT_MAP_USERNAME.get(
-                      String.valueOf(userName), e.getMessageObject()));
-                return;
-            }
-        }
-        if (authEntry == null) {
-            //The authEntry is null, this is an error. The password callback
-            //will catch this error. There is no way to stop the processing
-            //from the name callback.
+    if (responseAuthzID.length() == 0)
+    {
+      setCallbackMsg(ERR_SASLDIGESTMD5_EMPTY_AUTHZID.get());
+      callback.setAuthorized(false);
+      return;
+    }
+    else if (!responseAuthzID.equals(userName))
+    {
+      final String lowerAuthzID = toLowerCase(responseAuthzID);
+
+      // Process the callback differently depending on if the authzid
+      // string begins with the string "dn:" or not.
+      if (lowerAuthzID.startsWith("dn:"))
+      {
+        authzDNCheck(callback);
+      }
+      else
+      {
+        authzIDCheck(callback);
+      }
+    }
+    else
+    {
+      authzEntry = authEntry;
+      callback.setAuthorized(true);
+    }
+  }
+
+
+
+  /**
+   * Process the specified authorize callback. This method is called if the
+   * callback's authorization ID begins with the string "dn:".
+   *
+   * @param callback
+   *          The authorize callback to process.
+   */
+  private void authzDNCheck(final AuthorizeCallback callback)
+  {
+    final String responseAuthzID = callback.getAuthorizationID();
+    DN authzDN;
+    callback.setAuthorized(true);
+
+    try
+    {
+      authzDN = DN.decode(responseAuthzID.substring(3));
+    }
+    catch (final DirectoryException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      setCallbackMsg(ERR_SASL_AUTHZID_INVALID_DN.get(responseAuthzID,
+          e.getMessageObject()));
+      callback.setAuthorized(false);
+      return;
+    }
+
+    final DN actualAuthzDN = DirectoryServer.getActualRootBindDN(authzDN);
+    if (actualAuthzDN != null)
+    {
+      authzDN = actualAuthzDN;
+    }
+
+    if (!authzDN.equals(authEntry.getDN()))
+    {
+      if (authzDN.isNullDN())
+      {
+        authzEntry = null;
+      }
+      else
+      {
+        try
+        {
+          if ((authzEntry = DirectoryServer.getEntry(authzDN)) == null)
+          {
+            setCallbackMsg(ERR_SASL_AUTHZID_NO_SUCH_ENTRY.get(String
+                .valueOf(authzDN)));
+            callback.setAuthorized(false);
             return;
+          }
         }
+        catch (final DirectoryException e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+          setCallbackMsg(ERR_SASL_AUTHZID_CANNOT_GET_ENTRY.get(
+              String.valueOf(authzDN), e.getMessageObject()));
+          callback.setAuthorized(false);
+          return;
+        }
+      }
+      final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
+          DirectoryServer.isRootDN(authEntry.getDN()));
+      if (!hasPrivilege(authInfo))
+      {
+        callback.setAuthorized(false);
+      }
+      else
+      {
+        callback.setAuthorized(hasPermission(authInfo));
+      }
+    }
+  }
+
+
+
+  /**
+   * Process the specified authorize callback. This method is called if the
+   * callback's authorization ID does not begin with the string "dn:".
+   *
+   * @param callback
+   *          The authorize callback to process.
+   */
+  private void authzIDCheck(final AuthorizeCallback callback)
+  {
+    final String authzid = callback.getAuthorizationID();
+    final String lowerAuthzID = toLowerCase(authzid);
+    String idStr;
+    callback.setAuthorized(true);
+
+    if (lowerAuthzID.startsWith("u:"))
+    {
+      idStr = authzid.substring(2);
+    }
+    else
+    {
+      idStr = authzid;
     }
 
-    /**
-     * Try to get a entry from the directory using the specified DN. Used only
-     * for DIGEST-MD5 SASL mechanism.
-     *
-     * @param userDN The DN of the entry to retrieve from the server.
-     */
-   private void getAuthEntry(DN userDN) {
-       Lock readLock = null;
-       for (int i=0; i < 3; i++) {
-           readLock = LockManager.lockRead(userDN);
-           if (readLock != null) {
-               break;
-           }
-       }
-       if (readLock == null) {
-           setCallbackMsg(INFO_SASL_CANNOT_LOCK_ENTRY.get(
-                                                       String.valueOf(userDN)));
-           return;
-       } try {
-           authEntry = DirectoryServer.getEntry(userDN);
-       } catch (DirectoryException e) {
-           if (debugEnabled()) {
-               TRACER.debugCaught(DebugLogLevel.ERROR, e);
-           }
-           setCallbackMsg(ERR_SASL_CANNOT_GET_ENTRY_BY_DN.get(
-                   String.valueOf(userDN), SASL_MECHANISM_DIGEST_MD5,
-                   e.getMessageObject()));
-           return;
-       } finally {
-           LockManager.unlock(userDN, readLock);
-       }
-   }
+    if (idStr.length() == 0)
+    {
+      authzEntry = null;
+    }
+    else
+    {
+      try
+      {
+        authzEntry = identityMapper.getEntryForID(idStr);
+        if (authzEntry == null)
+        {
+          setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid));
+          callback.setAuthorized(false);
+          return;
+        }
+      }
+      catch (final DirectoryException e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+        setCallbackMsg(ERR_SASL_AUTHZID_NO_MAPPED_ENTRY.get(authzid));
+        callback.setAuthorized(false);
+        return;
+      }
+    }
 
-   /**
-    * The method performs all GSSAPI processing. It is run as the context of
-    * the login context performed by the GSSAPI mechanism handler. See comments
-    * for processing overview.
-    * @return {@code true} if the authentication processing was successful.
-    */
-   public Boolean run() {
-       ClientConnection clientConn = bindOp.getClientConnection();
-       //If the SASL server is null then this is the first handshake and the
-       //server needs to be initialized before any processing can be performed.
-       //If the SASL server cannot be created then all processing is abandoned
-       //and INVALID_CREDENTIALS is returned to the client.
-       if(saslServer == null) {
-           try {
-               initSASLServer();
-           } catch (SaslException ex) {
-               if (debugEnabled())
-                   TRACER.debugCaught(DebugLogLevel.ERROR, ex);
-               Message msg;
-               GSSException gex = (GSSException) ex.getCause();
-               if(gex != null)
-                 msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
-                     GSSAPISASLMechanismHandler.getGSSExceptionMessage(gex));
-               else
-                  msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
-                                                     getExceptionMessage(ex));
-               clientConn.setSASLAuthStateInfo(null);
-               bindOp.setAuthFailureReason(msg);
-               bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
-               return false;
-           }
-       }
-
-       ByteString clientCredentials = bindOp.getSASLCredentials();
-       clientConn.setSASLAuthStateInfo(null);
-       try {
-           ByteString responseAuthStr = evaluateResponse(clientCredentials);
-           //If the bind has not been completed,then
-           //more handshake is needed and SASL_BIND_IN_PROGRESS is returned back
-           //to the client.
-           if (isBindComplete()) {
-               bindOp.setResultCode(ResultCode.SUCCESS);
-               bindOp.setSASLAuthUserEntry(authEntry);
-               AuthenticationInfo authInfo =
-                    new AuthenticationInfo(authEntry, authzEntry,
-                                    mechanism, clientCredentials,
-                                   DirectoryServer.isRootDN(authEntry.getDN()));
-               bindOp.setAuthenticationInfo(authInfo);
-               //If confidentiality/integrity has been negotiated then
-               //create a SASL security provider and save it in the client
-               //connection. If confidentiality/integrity has not been
-               //negotiated, dispose of the SASL server.
-               if(isConfidentialIntegrity()) {
-                   SASLByteChannel saslByteChannel =
-                        SASLByteChannel.getSASLByteChannel(clientConn,
-                                                           mechanism, this);
-                   LDAPClientConnection ldapConn =
-                       (LDAPClientConnection) clientConn;
-                       ldapConn.setSASLPendingProvider(saslByteChannel);
-               } else {
-                   dispose();
-                   clientConn.setSASLAuthStateInfo(null);
-               }
-           } else {
-               bindOp.setServerSASLCredentials(responseAuthStr);
-               clientConn.setSASLAuthStateInfo(this);
-               bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
-           }
-       } catch (SaslException e) {
-           if (debugEnabled()) {
-               TRACER.debugCaught(DebugLogLevel.ERROR, e);
-           }
-           Message msg =
-               ERR_SASL_PROTOCOL_ERROR.get(mechanism, getExceptionMessage(e));
-           handleError(msg);
-           return false;
-       }
-       return true;
-   }
+    if ((authzEntry == null) || (!authzEntry.getDN().equals(authEntry.getDN())))
+    {
+      // Create temporary authorization information and run it both
+      // through the privilege and then the access control subsystems.
+      final AuthenticationInfo authInfo = new AuthenticationInfo(authEntry,
+          DirectoryServer.isRootDN(authEntry.getDN()));
+      if (!hasPrivilege(authInfo))
+      {
+        callback.setAuthorized(false);
+      }
+      else
+      {
+        callback.setAuthorized(hasPermission(authInfo));
+      }
+    }
+  }
 
 
-   /**
-    * Perform the authentication as the specified login context. The specified
-    * bind operation needs to be saved so the callbacks have access to it.
-    * Only used by the GSSAPI mechanism.
-    *
-    * @param loginContext The login context to perform the authentication
-    *                     as.
-    * @param bindOp The bind operation needed by the callbacks to process the
-    *               authentication.
-    */
-   void
-   performAuthentication(LoginContext loginContext, BindOperation bindOp) {
-       this.bindOp = bindOp;
-       try {
-           Subject.doAs(loginContext.getSubject(), this);
-       } catch (PrivilegedActionException e) {
-           if (debugEnabled()) {
-               TRACER.debugCaught(DebugLogLevel.ERROR, e);
-           }
-           Message msg =
-               ERR_SASL_PROTOCOL_ERROR.get(mechanism, getExceptionMessage(e));
-           handleError(msg);
-       }
-   }
 
-   /**
-    * Process the initial stage of a DIGEST-MD5 SASL bind using the specified
-    * bind operation.
-    *
-    * @param bindOp The bind operation to use in processing.
-    */
-   void
-   evaluateInitialStage(BindOperation bindOp) {
-       this.bindOp = bindOp;
-       ClientConnection clientConn = bindOp.getClientConnection();
-       try {
-           ByteString challenge = evaluateResponse(ByteString.empty());
-           bindOp.setResultCode(ResultCode.SASL_BIND_IN_PROGRESS);
-           bindOp.setServerSASLCredentials(challenge);
-           clientConn.setSASLAuthStateInfo(this);
-       } catch (SaslException e) {
-           if (debugEnabled()) {
-               TRACER.debugCaught(DebugLogLevel.ERROR, e);
-           }
-           Message msg =
-               ERR_SASL_PROTOCOL_ERROR.get(mechanism,getExceptionMessage(e));
-           handleError(msg);
-       }
-   }
+  /**
+   * Helper routine to call the SASL server evaluateResponse method with the
+   * specified byte array.
+   *
+   * @param bytes
+   *          The byte array to pass to the SASL server.
+   * @return A byte array containing the result of the evaluation.
+   * @throws SaslException
+   *           If the SASL server cannot evaluate the byte array.
+   */
+  private ByteString evaluateResponse(ByteString response) throws SaslException
+  {
+    if (response == null)
+    {
+      response = ByteString.empty();
+    }
 
-   /**
-    * Evaluate the final stage of a DIGEST-MD5 SASL bind using the specified
-    * bind operation.
-    *
-    * @param bindOp The bind operation to use in processing.
-    */
-   void
-   evaluateFinalStage(BindOperation bindOp) {
-      this.bindOp = bindOp;
-       ByteString clientCredentials = bindOp.getSASLCredentials();
-       if ((clientCredentials == null) ||
-               (clientCredentials.length() == 0)) {
-           Message msg =
-               ERR_SASL_NO_CREDENTIALS.get(mechanism, mechanism);
-           handleError(msg);
-           return;
-       }
-       ClientConnection clientConn = bindOp.getClientConnection();
-       clientConn.setSASLAuthStateInfo(null);
-       try {
-           ByteString responseAuthStr = evaluateResponse(clientCredentials);
-           bindOp.setResultCode(ResultCode.SUCCESS);
-           bindOp.setServerSASLCredentials(responseAuthStr);
-           bindOp.setSASLAuthUserEntry(authEntry);
-           AuthenticationInfo authInfo =
-                new AuthenticationInfo(authEntry, authzEntry,
-                                       mechanism, clientCredentials,
-                                  DirectoryServer.isRootDN(authEntry.getDN()));
-           bindOp.setAuthenticationInfo(authInfo);
-           //If confidentiality/integrity has been negotiated, then create a
-           //SASL security provider and save it in the client connection for
-           //use in later processing.
-           if(isConfidentialIntegrity()) {
-               SASLByteChannel saslByteChannel =
-                SASLByteChannel.getSASLByteChannel(clientConn, mechanism, this);
-               LDAPClientConnection ldapConn =
-                   (LDAPClientConnection) clientConn;
-               ldapConn.setSASLPendingProvider(saslByteChannel);
-           } else {
-               dispose();
-               clientConn.setSASLAuthStateInfo(null);
-           }
-       } catch (SaslException e) {
-           if (debugEnabled()) {
-               TRACER.debugCaught(DebugLogLevel.ERROR, e);
-           }
-           Message msg =
-               ERR_SASL_PROTOCOL_ERROR.get(mechanism, getExceptionMessage(e));
-           handleError(msg);
-       }
-   }
+    final byte[] evalResponse = saslServer.evaluateResponse(response
+        .toByteArray());
+    if (evalResponse == null)
+    {
+      return ByteString.empty();
+    }
+    else
+    {
+      return ByteString.wrap(evalResponse);
+    }
+  }
+
+
+
+  /**
+   * Try to get a entry from the directory using the specified DN. Used only for
+   * DIGEST-MD5 SASL mechanism.
+   *
+   * @param userDN
+   *          The DN of the entry to retrieve from the server.
+   */
+  private void getAuthEntry(final DN userDN)
+  {
+    Lock readLock = null;
+    for (int i = 0; i < 3; i++)
+    {
+      readLock = LockManager.lockRead(userDN);
+      if (readLock != null)
+      {
+        break;
+      }
+    }
+
+    if (readLock == null)
+    {
+      setCallbackMsg(INFO_SASL_CANNOT_LOCK_ENTRY.get(String.valueOf(userDN)));
+      return;
+    }
+
+    try
+    {
+      authEntry = DirectoryServer.getEntry(userDN);
+    }
+    catch (final DirectoryException e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      setCallbackMsg(ERR_SASL_CANNOT_GET_ENTRY_BY_DN.get(
+          String.valueOf(userDN), SASL_MECHANISM_DIGEST_MD5,
+          e.getMessageObject()));
+      return;
+    }
+    finally
+    {
+      LockManager.unlock(userDN, readLock);
+    }
+  }
+
+
+
+  /**
+   * This method is used to process an exception that is thrown during bind
+   * processing. It will try to determine if the exception is a result of
+   * callback processing, and if it is, will try to use a more informative
+   * failure message set by the callback. If the exception is a result of a
+   * error during the the SASL server processing, the callback message will be
+   * null, and the method will use the specified message parameter as the
+   * failure reason. This is a more cryptic exception message hard-coded in the
+   * SASL server internals. The method also disposes of the SASL server, clears
+   * the authentication state and sets the result code to INVALID_CREDENTIALs
+   *
+   * @param msg
+   *          The message to use if the callback message is not null.
+   */
+  private void handleError(final Message msg)
+  {
+    dispose();
+    final ClientConnection clientConn = bindOp.getClientConnection();
+    clientConn.setSASLAuthStateInfo(null);
+
+    // Check if the callback message is null and use that message if not.
+    if (cbResultCode != null)
+    {
+      bindOp.setResultCode(cbResultCode);
+    }
+    else
+    {
+      bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+    }
+
+    if (cbMsg != null)
+    {
+      bindOp.setAuthFailureReason(cbMsg);
+    }
+    else
+    {
+      bindOp.setAuthFailureReason(msg);
+    }
+  }
+
+
+
+  /**
+   * Checks the specified authentication information parameter against the
+   * access control subsystem to see if it has the "proxy" right.
+   *
+   * @param authInfo
+   *          The authentication information to check access on.
+   * @return {@code true} if the authentication information has proxy access.
+   */
+  private boolean hasPermission(final AuthenticationInfo authInfo)
+  {
+    boolean ret = true;
+    Entry e = authzEntry;
+
+    // If the authz entry is null, use the entry associated with the NULL DN.
+    if (e == null)
+    {
+      try
+      {
+        e = DirectoryServer.getEntry(DN.nullDN());
+      }
+      catch (final DirectoryException ex)
+      {
+        return false;
+      }
+    }
+
+    if (AccessControlConfigManager.getInstance().getAccessControlHandler()
+        .mayProxy(authInfo.getAuthenticationEntry(), e, bindOp) == false)
+    {
+      setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_ACCESS.get(String
+          .valueOf(authEntry.getDN())));
+      ret = false;
+    }
+
+    return ret;
+  }
+
+
+
+  /**
+   * Checks the specified authentication information parameter against the
+   * privilege subsystem to see if it has PROXIED_AUTH privileges.
+   *
+   * @param authInfo
+   *          The authentication information to use in the check.
+   * @return {@code true} if the authentication information has PROXIED_AUTH
+   *         privileges.
+   */
+  private boolean hasPrivilege(final AuthenticationInfo authInfo)
+  {
+    boolean ret = true;
+    final InternalClientConnection tempConn = new InternalClientConnection(
+        authInfo);
+    if (!tempConn.hasPrivilege(Privilege.PROXIED_AUTH, bindOp))
+    {
+      setCallbackMsg(ERR_SASL_AUTHZID_INSUFFICIENT_PRIVILEGES.get(String
+          .valueOf(authEntry.getDN())));
+      ret = false;
+    }
+    return ret;
+  }
+
+
+
+  /**
+   * Initialize the SASL server using parameters specified in the constructor.
+   */
+  private void initSASLServer() throws SaslException
+  {
+    saslServer = Sasl.createSaslServer(mechanism, SASL_DEFAULT_PROTOCOL,
+        serverFQDN, saslProps, this);
+    if (saslServer == null)
+    {
+      final Message msg = ERR_SASL_CREATE_SASL_SERVER_FAILED.get(mechanism,
+          serverFQDN);
+      throw new SaslException(Message.toString(msg));
+    }
+  }
+
+
+
+  /**
+   * Return true if the SASL server has negotiated with the client to support
+   * confidentiality or integrity.
+   *
+   * @return {@code true} if the context supports confidentiality or integrity.
+   */
+  private boolean isConfidentialIntegrity()
+  {
+    boolean ret = false;
+    final String qop = (String) saslServer.getNegotiatedProperty(Sasl.QOP);
+    if (qop.equalsIgnoreCase(confidentiality)
+        || qop.equalsIgnoreCase(integrity))
+    {
+      ret = true;
+    }
+    return ret;
+  }
+
+
+
+  /**
+   * Process the specified name callback. Used only for DIGEST-MD5 SASL
+   * mechanism.
+   *
+   * @param nameCallback
+   *          The name callback to process.
+   */
+  private void nameCallback(final NameCallback nameCallback)
+  {
+    userName = nameCallback.getDefaultName();
+    final String lowerUserName = toLowerCase(userName);
+
+    // Process the user name differently if it starts with the string "dn:".
+    if (lowerUserName.startsWith("dn:"))
+    {
+      DN userDN;
+      try
+      {
+        userDN = DN.decode(userName.substring(3));
+      }
+      catch (final DirectoryException e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+        setCallbackMsg(ERR_SASL_CANNOT_DECODE_USERNAME_AS_DN.get(mechanism,
+            userName, e.getMessageObject()));
+        return;
+      }
+
+      if (userDN.isNullDN())
+      {
+        setCallbackMsg(ERR_SASL_USERNAME_IS_NULL_DN.get(mechanism));
+        return;
+      }
+
+      final DN rootDN = DirectoryServer.getActualRootBindDN(userDN);
+      if (rootDN != null)
+      {
+        userDN = rootDN;
+      }
+      getAuthEntry(userDN);
+    }
+    else
+    {
+      // The entry name is not a DN, try to map it using the identity
+      // mapper.
+      String entryID = userName;
+      if (lowerUserName.startsWith("u:"))
+      {
+        if (lowerUserName.equals("u:"))
+        {
+          setCallbackMsg(ERR_SASL_ZERO_LENGTH_USERNAME
+              .get(mechanism, mechanism));
+          return;
+        }
+        entryID = userName.substring(2);
+      }
+      try
+      {
+        authEntry = identityMapper.getEntryForID(entryID);
+      }
+      catch (final DirectoryException e)
+      {
+        if (debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, e);
+        }
+        setCallbackMsg(ERR_SASLDIGESTMD5_CANNOT_MAP_USERNAME.get(
+            String.valueOf(userName), e.getMessageObject()));
+        return;
+      }
+    }
+
+    if (authEntry == null)
+    {
+      // The authEntry is null, this is an error. The password callback
+      // will catch this error. There is no way to stop the processing
+      // from the name callback.
+      return;
+    }
+  }
+
+
+
+  /**
+   * Process the specified password callback. Used only for the DIGEST-MD5 SASL
+   * mechanism. The password callback is processed after the name callback.
+   *
+   * @param passwordCallback
+   *          The password callback to process.
+   */
+  private void passwordCallback(final PasswordCallback passwordCallback)
+  {
+    // If there is no authEntry this is an error.
+    if (authEntry == null)
+    {
+      setCallbackMsg(ERR_SASL_NO_MATCHING_ENTRIES.get(userName));
+      return;
+    }
+
+    // Try to get a clear password to use.
+    List<ByteString> clearPasswords;
+    try
+    {
+      final AuthenticationPolicyState authState = AuthenticationPolicyState
+          .forUser(authEntry, false);
+
+      if (!authState.isPasswordPolicy())
+      {
+        final Message message = ERR_SASL_ACCOUNT_NOT_LOCAL.get(mechanism,
+            String.valueOf(authEntry.getDN()));
+        setCallbackMsg(ResultCode.INAPPROPRIATE_AUTHENTICATION, message);
+        return;
+      }
+
+      final PasswordPolicyState pwPolicyState = (PasswordPolicyState) authState;
+
+      clearPasswords = pwPolicyState.getClearPasswords();
+      if ((clearPasswords == null) || clearPasswords.isEmpty())
+      {
+        setCallbackMsg(ERR_SASL_NO_REVERSIBLE_PASSWORDS.get(mechanism,
+            String.valueOf(authEntry.getDN())));
+        return;
+      }
+    }
+    catch (final Exception e)
+    {
+      if (debugEnabled())
+      {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+      setCallbackMsg(ERR_SASL_CANNOT_GET_REVERSIBLE_PASSWORDS.get(
+          String.valueOf(authEntry.getDN()), mechanism, String.valueOf(e)));
+      return;
+    }
+
+    // Use the first password.
+    final char[] password = clearPasswords.get(0).toString().toCharArray();
+    passwordCallback.setPassword(password);
+    return;
+  }
+
+
+
+  /**
+   * This callback is used to process realm information. It is not used.
+   *
+   * @param callback
+   *          The realm callback instance to process.
+   */
+  private void realmCallback(final RealmCallback callback)
+  {
+  }
+
+
+
+  /**
+   * Sets the callback message to the specified message.
+   *
+   * @param cbMsg
+   *          The message to set the callback message to.
+   */
+  private void setCallbackMsg(final Message cbMsg)
+  {
+    setCallbackMsg(ResultCode.INVALID_CREDENTIALS, cbMsg);
+  }
+
+
+
+  /**
+   * Sets the callback message to the specified message.
+   *
+   * @param cbResultCode
+   *          The result code.
+   * @param cbMsg
+   *          The message.
+   */
+  private void setCallbackMsg(final ResultCode cbResultCode,
+      final Message cbMsg)
+  {
+    this.cbResultCode = cbResultCode;
+    this.cbMsg = cbMsg;
+  }
 }

--
Gitblit v1.10.0