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