You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2007/10/20 00:32:26 UTC

svn commit: r586633 - in /directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support: DefaultBindHandler.java bind/BindHandlerChain.java bind/ConfigureChain.java

Author: elecharny
Date: Fri Oct 19 15:32:24 2007
New Revision: 586633

URL: http://svn.apache.org/viewvc?rev=586633&view=rev
Log:
Removed the SASL BindHandler chain completely

Removed:
    directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support/bind/BindHandlerChain.java
    directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support/bind/ConfigureChain.java
Modified:
    directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support/DefaultBindHandler.java

Modified: directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support/DefaultBindHandler.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support/DefaultBindHandler.java?rev=586633&r1=586632&r2=586633&view=diff
==============================================================================
--- directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support/DefaultBindHandler.java (original)
+++ directory/apacheds/branches/bigbang/protocol-ldap/src/main/java/org/apache/directory/server/ldap/support/DefaultBindHandler.java Fri Oct 19 15:32:24 2007
@@ -21,26 +21,49 @@
 
 
 import org.apache.directory.server.core.DirectoryService;
+import org.apache.directory.server.core.authn.LdapPrincipal;
 import org.apache.directory.server.core.jndi.ServerLdapContext;
+import org.apache.directory.server.core.partition.PartitionNexus;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
+import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
+import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry;
+import org.apache.directory.server.kerberos.shared.store.operations.GetPrincipal;
 import org.apache.directory.server.ldap.LdapServer;
-import org.apache.directory.server.ldap.support.bind.BindHandlerChain;
+import org.apache.directory.server.ldap.SessionRegistry;
+import org.apache.directory.server.ldap.support.bind.CramMd5MechanismHandler;
+import org.apache.directory.server.ldap.support.bind.DigestMd5MechanismHandler;
+import org.apache.directory.server.ldap.support.bind.GssapiMechanismHandler;
+import org.apache.directory.server.ldap.support.bind.MechanismHandler;
+import org.apache.directory.server.ldap.support.bind.SaslFilter;
+import org.apache.directory.server.protocol.shared.ServiceConfigurationException;
 import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
 import org.apache.directory.shared.ldap.constants.SupportedSASLMechanisms;
 import org.apache.directory.shared.ldap.exception.LdapException;
 import org.apache.directory.shared.ldap.message.*;
 import org.apache.directory.shared.ldap.name.LdapDN;
 import org.apache.directory.shared.ldap.util.ExceptionUtils;
+import org.apache.mina.common.IoFilterChain;
 import org.apache.mina.common.IoSession;
-import org.apache.mina.handler.chain.IoHandlerCommand;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.naming.Context;
 import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
 import javax.naming.ldap.InitialLdapContext;
 import javax.naming.ldap.LdapContext;
 import javax.naming.spi.InitialContextFactory;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosKey;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Map;
 import java.util.Set;
 
 
@@ -56,27 +79,37 @@
 {
     private static final Logger LOG = LoggerFactory.getLogger( BindHandler.class );
 
-    /** A class to handle SASL bind requests */
-    private IoHandlerCommand saslBindHandler;
-
     /** An empty Contol array used to get back the controls if any */
     private static final MutableControl[] EMPTY_CONTROL = new MutableControl[0];
 
+    private DirContext ctx;
+
+    /**
+     * A Hashed Adapter mapping SASL mechanisms to their handlers.
+     */
+    private final Map<String, MechanismHandler> handlers;
+
+    private final SessionRegistry registry;
 
     /**
      * Creates a new instance of BindHandler.
      */
-    public DefaultBindHandler()
+    public DefaultBindHandler( DirectoryService directoryService, SessionRegistry registry )
     {
+        Map<String, MechanismHandler> map = new HashMap<String, MechanismHandler>();
+        map.put( SupportedSASLMechanisms.CRAM_MD5, new CramMd5MechanismHandler( directoryService ) );
+        map.put( SupportedSASLMechanisms.DIGEST_MD5, new DigestMd5MechanismHandler( directoryService ) );
+        map.put( SupportedSASLMechanisms.GSSAPI, new GssapiMechanismHandler( directoryService ) );
+        handlers = Collections.unmodifiableMap( map );
+        
+        this.registry = registry;
     }
 
 
     public void setDirectoryService( DirectoryService directoryService )
     {
-        saslBindHandler = new BindHandlerChain( directoryService, getSessionRegistry() );
     }
 
-
     /**
      * Create an environment object and inject the Bond informations collected
      * from the BindRequest message :
@@ -256,6 +289,384 @@
     }
 
     
+    public void handleSaslAuth( IoSession session, Object message ) throws Exception
+    {
+        LdapServer ldapServer = ( LdapServer )
+                session.getAttribute( LdapServer.class.toString() );
+
+        Map<String, String> saslProps = new HashMap<String, String>();
+        saslProps.put( Sasl.QOP, ldapServer.getSaslQopString() );
+        saslProps.put( "com.sun.security.sasl.digest.realm", getActiveRealms( ldapServer ) );
+        session.setAttribute( "saslProps", saslProps );
+
+        session.setAttribute( "saslHost", ldapServer.getSaslHost() );
+        session.setAttribute( "baseDn", ldapServer.getSearchBaseDn() );
+
+        Set<String> activeMechanisms = ldapServer.getSupportedMechanisms();
+
+        if ( activeMechanisms.contains( SupportedSASLMechanisms.GSSAPI ) )
+        {
+            try
+            {
+                Subject saslSubject = getSubject( ldapServer );
+                session.setAttribute( "saslSubject", saslSubject );
+            }
+            catch ( ServiceConfigurationException sce )
+            {
+                activeMechanisms.remove( "GSSAPI" );
+                LOG.warn( sce.getMessage() );
+            }
+        }
+
+        BindRequest bindRequest = ( BindRequest ) message;
+
+        // Guard clause:  Reject unsupported SASL mechanisms.
+        if ( !ldapServer.getSupportedMechanisms().contains( bindRequest.getSaslMechanism() ) )
+        {
+            LOG.error( "Bind error : {} mechanism not supported. Please check the server.xml configuration file (supportedMechanisms field)", 
+                bindRequest.getSaslMechanism() );
+
+            LdapResult bindResult = bindRequest.getResultResponse().getLdapResult();
+            bindResult.setResultCode( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED );
+            bindResult.setErrorMessage( bindRequest.getSaslMechanism() + " is not a supported mechanism." );
+            session.write( bindRequest.getResultResponse() );
+            return;
+        }
+
+        handleSasl( session, bindRequest );
+    }
+
+    
+    /**
+     * Deal with a SASL bind request
+     */
+    public void handleSasl( IoSession session, BindRequest bindRequest ) throws Exception
+    {
+        String sessionMechanism = bindRequest.getSaslMechanism();
+
+        if ( sessionMechanism.equals( SupportedSASLMechanisms.SIMPLE ) )
+        {
+            /*
+             * This is the principal name that will be used to bind to the DIT.
+             */
+            session.setAttribute( Context.SECURITY_PRINCIPAL, bindRequest.getName() );
+
+            /*
+             * These are the credentials that will be used to bind to the DIT.
+             * For the simple mechanism, this will be a password, possibly one-way hashed.
+             */
+            session.setAttribute( Context.SECURITY_CREDENTIALS, bindRequest.getCredentials() );
+
+            getLdapContext( session, bindRequest );
+        }
+        else
+        {
+            MechanismHandler mechanismHandler = handlers.get( sessionMechanism );
+
+            if ( mechanismHandler == null )
+            {
+                LOG.error( "Handler unavailable for " + sessionMechanism );
+                throw new IllegalArgumentException( "Handler unavailable for " + sessionMechanism );
+            }
+
+            SaslServer ss = mechanismHandler.handleMechanism( session, bindRequest );
+            
+            LdapResult result = bindRequest.getResultResponse().getLdapResult();
+
+            if ( !ss.isComplete() )
+            {
+                try
+                {
+                    /*
+                     * SaslServer will throw an exception if the credentials are null.
+                     */
+                    if ( bindRequest.getCredentials() == null )
+                    {
+                        bindRequest.setCredentials( new byte[0] );
+                    }
+
+                    byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() );
+
+                    if ( ss.isComplete() )
+                    {
+                        /*
+                         * There may be a token to return to the client.  We set it here
+                         * so it will be returned in a SUCCESS message, after an LdapContext
+                         * has been initialized for the client.
+                         */
+                        session.setAttribute( "saslCreds", tokenBytes );
+
+                        /*
+                         * If we got here, we're ready to try getting an initial LDAP context.
+                         */
+                        getLdapContext( session, bindRequest );
+                    }
+                    else
+                    {
+                        LOG.info( "Continuation token had length " + tokenBytes.length );
+                        result.setResultCode( ResultCodeEnum.SASL_BIND_IN_PROGRESS );
+                        BindResponse resp = ( BindResponse ) bindRequest.getResultResponse();
+                        resp.setServerSaslCreds( tokenBytes );
+                        session.write( resp );
+                        LOG.debug( "Returning final authentication data to client to complete context." );
+                    }
+                }
+                catch ( SaslException se )
+                {
+                    LOG.error( se.getMessage() );
+                    result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
+                    result.setErrorMessage( se.getMessage() );
+                    session.write( bindRequest.getResultResponse() );
+                }
+            }
+        }
+    }
+
+    
+    /**
+     * Create a list of all the configured realms.
+     */
+    private String getActiveRealms( LdapServer ldapServer )
+    {
+        StringBuilder realms = new StringBuilder();
+        boolean isFirst = true;
+
+        for ( String realm:ldapServer.getSaslRealms() )
+        {
+            if ( isFirst )
+            {
+                isFirst = false;
+            }
+            else
+            {
+                realms.append( ' ' );
+            }
+            
+            realms.append( realm );
+        }
+
+        return realms.toString();
+    }
+
+
+    private Subject getSubject( LdapServer ldapServer ) throws ServiceConfigurationException
+    {
+        String servicePrincipalName = ldapServer.getSaslPrincipal();
+
+        KerberosPrincipal servicePrincipal = new KerberosPrincipal( servicePrincipalName );
+        GetPrincipal getPrincipal = new GetPrincipal( servicePrincipal );
+
+        PrincipalStoreEntry entry;
+
+        try
+        {
+            entry = findPrincipal( ldapServer, getPrincipal );
+        }
+        catch ( Exception e )
+        {
+            String message = "Service principal " + servicePrincipalName + " not found at search base DN "
+                + ldapServer.getSearchBaseDn() + ".";
+            throw new ServiceConfigurationException( message, e );
+        }
+
+        if ( entry == null )
+        {
+            String message = "Service principal " + servicePrincipalName + " not found at search base DN "
+                + ldapServer.getSearchBaseDn() + ".";
+            throw new ServiceConfigurationException( message );
+        }
+
+        EncryptionKey key = entry.getKeyMap().get( EncryptionType.DES_CBC_MD5 );
+        byte[] keyBytes = key.getKeyValue();
+        int type = key.getKeyType().getOrdinal();
+        int kvno = key.getKeyVersion();
+
+        KerberosKey serviceKey = new KerberosKey( servicePrincipal, keyBytes, type, kvno );
+        Subject subject = new Subject();
+        subject.getPrivateCredentials().add( serviceKey );
+
+        return subject;
+    }
+    
+    private PrincipalStoreEntry findPrincipal( LdapServer ldapServer, GetPrincipal getPrincipal ) throws Exception
+    {
+        if ( ctx == null )
+        {
+            try
+            {
+                LdapPrincipal principal = new LdapPrincipal(
+                        new LdapDN( PartitionNexus.ADMIN_PRINCIPAL ), AuthenticationLevel.SIMPLE );
+                ctx = ldapServer.getDirectoryService().getJndiContext( principal, ldapServer.getSearchBaseDn() );
+            }
+            catch ( NamingException ne )
+            {
+                String message = "Failed to get initial context " + ldapServer.getSearchBaseDn();
+                throw new ServiceConfigurationException( message, ne );
+            }
+        }
+
+        return (PrincipalStoreEntry)getPrincipal.execute( ctx, null );
+    }    
+    
+    
+    private Hashtable<String, Object> getEnvironment( IoSession session, BindRequest bindRequest )
+    {
+        Object principal = session.getAttribute( Context.SECURITY_PRINCIPAL );
+
+        /**
+         * For simple, this is a password.  For strong, this is unused.
+         */
+        Object credentials = session.getAttribute( Context.SECURITY_CREDENTIALS );
+
+        String sessionMechanism = bindRequest.getSaslMechanism();
+        String authenticationLevel = getAuthenticationLevel( sessionMechanism );
+
+        LOG.debug( "{} {}", Context.SECURITY_PRINCIPAL, principal );
+        LOG.debug( "{} {}", Context.SECURITY_CREDENTIALS, credentials );
+        LOG.debug( "{} {}", Context.SECURITY_AUTHENTICATION, authenticationLevel );
+
+        // clone the environment first then add the required security settings
+        Hashtable<String, Object> env = registry.getEnvironmentByCopy();
+        env.put( Context.SECURITY_PRINCIPAL, principal );
+
+        if ( credentials != null )
+        {
+            env.put( Context.SECURITY_CREDENTIALS, credentials );
+        }
+
+        env.put( Context.SECURITY_AUTHENTICATION, authenticationLevel );
+
+        if ( bindRequest.getControls().containsKey( ManageDsaITControl.CONTROL_OID ) )
+        {
+            env.put( Context.REFERRAL, "ignore" );
+        }
+        else
+        {
+            env.put( Context.REFERRAL, "throw" );
+        }
+
+        return env;
+    }
+    
+    
+    private void getLdapContext( IoSession session, BindRequest bindRequest ) throws Exception
+    {
+        Hashtable<String, Object> env = getEnvironment( session, bindRequest );
+        LdapResult result = bindRequest.getResultResponse().getLdapResult();
+        LdapContext ctx;
+
+        try
+        {
+            MutableControl[] connCtls = bindRequest.getControls().values().toArray( EMPTY_CONTROL );
+            ctx = new InitialLdapContext( env, connCtls );
+
+            registry.setLdapContext( session, ctx );
+            
+            // add the bind response controls 
+            bindRequest.getResultResponse().addAll( ctx.getResponseControls() );
+            
+            returnSuccess( session, bindRequest );
+        }
+        catch ( NamingException e )
+        {
+            ResultCodeEnum code;
+
+            if ( e instanceof LdapException )
+            {
+                code = ( ( LdapException ) e ).getResultCode();
+                result.setResultCode( code );
+            }
+            else
+            {
+                code = ResultCodeEnum.getBestEstimate( e, bindRequest.getType() );
+                result.setResultCode( code );
+            }
+
+            String msg = "Bind failed: " + e.getMessage();
+
+            if ( LOG.isDebugEnabled() )
+            {
+                msg += ":\n" + ExceptionUtils.getStackTrace( e );
+                msg += "\n\nBindRequest = \n" + bindRequest.toString();
+            }
+
+            if ( ( e.getResolvedName() != null )
+                && ( ( code == ResultCodeEnum.NO_SUCH_OBJECT ) || ( code == ResultCodeEnum.ALIAS_PROBLEM )
+                    || ( code == ResultCodeEnum.INVALID_DN_SYNTAX ) || ( code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM ) ) )
+            {
+                result.setMatchedDn( ( LdapDN ) e.getResolvedName() );
+            }
+
+            result.setErrorMessage( msg );
+            session.write( bindRequest.getResultResponse() );
+
+            ctx = null;
+        }
+    }
+
+    
+    private void returnSuccess( IoSession session, BindRequest bindRequest ) throws Exception
+    {
+        /*
+         * We have now both authenticated the client and retrieved a JNDI context for them.
+         * We can return a success message to the client.
+         */
+        LdapResult result = bindRequest.getResultResponse().getLdapResult();
+
+        byte[] tokenBytes = ( byte[] ) session.getAttribute( "saslCreds" );
+
+        result.setResultCode( ResultCodeEnum.SUCCESS );
+        BindResponse response = ( BindResponse ) bindRequest.getResultResponse();
+        response.setServerSaslCreds( tokenBytes );
+
+        String sessionMechanism = bindRequest.getSaslMechanism();
+
+        /*
+         * If the SASL mechanism is DIGEST-MD5 or GSSAPI, we insert a SASLFilter.
+         */
+        if ( sessionMechanism.equals( SupportedSASLMechanisms.DIGEST_MD5 ) || 
+             sessionMechanism.equals( SupportedSASLMechanisms.GSSAPI ) )
+        {
+            LOG.debug( "Inserting SaslFilter to engage negotiated security layer." );
+
+            IoFilterChain chain = session.getFilterChain();
+            if ( !chain.contains( "SASL" ) )
+            {
+                SaslServer saslContext = ( SaslServer ) session.getAttribute( MechanismHandler.SASL_CONTEXT );
+                chain.addBefore( "codec", "SASL", new SaslFilter( saslContext ) );
+            }
+
+            /*
+             * We disable the SASL security layer once, to write the outbound SUCCESS
+             * message without SASL security layer processing.
+             */
+            session.setAttribute( SaslFilter.DISABLE_SECURITY_LAYER_ONCE, Boolean.TRUE );
+        }
+
+        session.write( response );
+        LOG.debug( "Returned SUCCESS message." );
+    }
+
+    
+    /**
+     * Convert a SASL mechanism to an Authentication level
+     *
+     * @param sessionMechanism The resquested mechanism
+     * @return The corresponding authentication level
+     */
+    private String getAuthenticationLevel( String sessionMechanism )
+    {
+        if ( sessionMechanism.equals( SupportedSASLMechanisms.SIMPLE ) )
+        {
+            return AuthenticationLevel.SIMPLE.toString();
+        }
+        else
+        {
+            return AuthenticationLevel.STRONG.toString();
+        }
+    }
+    
+    
     /**
      * Deal with a received BindRequest
      */
@@ -296,7 +707,7 @@
         }
         else
         {
-            saslBindHandler.execute( null, session, bindRequest );
+            handleSaslAuth( session, bindRequest );
         }
     }
 }