| | |
| | | |
| | | 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; |
| | |
| | | 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; |
| | | } |
| | | } |