001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.ldap.handlers.bind.ntlm;
021    
022    
023    import org.apache.directory.server.core.CoreSession;
024    import org.apache.directory.server.core.authn.LdapPrincipal;
025    import org.apache.directory.server.core.interceptor.context.BindOperationContext;
026    import org.apache.directory.server.ldap.LdapSession;
027    import org.apache.directory.server.ldap.handlers.bind.AbstractSaslServer;
028    import org.apache.directory.server.ldap.handlers.bind.SaslConstants;
029    import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
030    import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
031    import org.apache.directory.shared.ldap.message.InternalBindRequest;
032    import org.apache.directory.shared.ldap.name.LdapDN;
033    import org.apache.directory.shared.ldap.util.StringTools;
034    
035    import javax.naming.Context;
036    import javax.naming.InvalidNameException;
037    import javax.security.sasl.SaslException;
038    
039    
040    /**
041     * A SaslServer implementation for NTLM based SASL mechanism.  This is
042     * required unfortunately because the JDK's SASL provider does not support
043     * this mechanism.
044     *
045     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046     * @version $$Rev$$
047     */
048    public class NtlmSaslServer extends AbstractSaslServer
049    {
050        /** The different states during a NTLM negotiation */ 
051        enum NegotiationState { INITIALIZED, TYPE_1_RECEIVED, TYPE_2_SENT, TYPE_3_RECEIVED, COMPLETED }
052    
053        /** The current state */
054        private NegotiationState state = NegotiationState.INITIALIZED;
055        private final NtlmProvider provider;
056    
057        
058        public NtlmSaslServer( NtlmProvider provider, InternalBindRequest bindRequest, LdapSession ldapSession )
059        {
060            super( ldapSession, null, bindRequest );
061            this.provider = provider;
062        }
063    
064    
065        /**
066         * {@inheritDoc}
067         */
068        public String getMechanismName()
069        {
070            return SupportedSaslMechanisms.NTLM;
071        }
072    
073    
074        protected void responseRecieved()
075        {
076            switch ( state )
077            {
078                case INITIALIZED:
079                    state = NegotiationState.TYPE_1_RECEIVED;
080                    break;
081                    
082                case TYPE_1_RECEIVED:
083                    throw new IllegalStateException( "Cannot receive NTLM message before sending Type 2 challenge." );
084                    
085                case TYPE_2_SENT:
086                    state = NegotiationState.TYPE_3_RECEIVED;
087                    break;
088                    
089                case TYPE_3_RECEIVED:
090                    throw new IllegalStateException( "Cannot receive NTLM message after Type 3 has been received." );
091                    
092                case COMPLETED:
093                    throw new IllegalStateException( "Sasl challenge response already completed." );
094            }
095        }
096    
097    
098        protected void responseSent()
099        {
100            switch ( state )
101            {
102                case INITIALIZED:
103                    throw new IllegalStateException( "Cannot send Type 2 challenge before Type 1 response." );
104                    
105                case TYPE_1_RECEIVED:
106                    state = NegotiationState.TYPE_2_SENT;
107                    break;
108                    
109                case TYPE_2_SENT:
110                    throw new IllegalStateException( "Cannot send Type 2 after it's already sent." );
111                    
112                case TYPE_3_RECEIVED:
113                    state = NegotiationState.COMPLETED;
114                    break;
115                    
116                case COMPLETED:
117                    throw new IllegalStateException( "Sasl challenge response already completed." );
118            }
119        }
120    
121    
122        /**
123         * {@inheritDoc}
124         */
125        public byte[] evaluateResponse( byte[] response ) throws SaslException
126        {
127            if ( response == null )
128            {
129                throw new NullPointerException( "response was null" );
130            }
131    
132            if ( response.length == 0 )
133            {
134                throw new IllegalArgumentException( "response with zero bytes" );
135            }
136    
137            responseRecieved();
138            byte[] retval = null;
139    
140            switch ( state )
141            {
142                case TYPE_1_RECEIVED:
143                    try
144                    {
145                        retval = provider.generateChallenge( getLdapSession().getIoSession(), response );
146                    }
147                    catch ( Exception e )
148                    {
149                        throw new SaslException( "There was a failure during NTLM Type 1 message handling.", e );
150                    }
151                    
152                    break;
153                    
154                case TYPE_3_RECEIVED:
155                    boolean result;
156                    try
157                    {
158                        result = provider.authenticate( getLdapSession().getIoSession(), response );
159                        LdapDN dn = getBindRequest().getName();
160                        dn.normalize( getLdapSession().getLdapServer().getDirectoryService().getRegistries().getAttributeTypeRegistry().getNormalizerMapping() );
161                        LdapPrincipal ldapPrincipal = new LdapPrincipal( dn, AuthenticationLevel.STRONG ); 
162                        getLdapSession().putSaslProperty( SaslConstants.SASL_AUTHENT_USER, ldapPrincipal );
163                        getLdapSession().putSaslProperty( Context.SECURITY_PRINCIPAL, getBindRequest().getName().toString() );
164                    }
165                    catch ( Exception e )
166                    {
167                        throw new SaslException( "There was a failure during NTLM Type 3 message handling.", e );
168                    }
169    
170                    if ( ! result )
171                    {
172                        throw new SaslException( "Authentication occurred but the credentials were invalid." );
173                    }
174                    
175                    break;
176            }
177            
178            responseSent();
179            return retval;
180        }
181    
182    
183        /**
184         * Try to authenticate the usr against the underlying LDAP server.
185         */
186        private CoreSession authenticate( String user, String password ) throws InvalidNameException, Exception
187        {
188            BindOperationContext bindContext = new BindOperationContext( getLdapSession().getCoreSession() );
189            bindContext.setDn( new LdapDN( user ) );
190            bindContext.setCredentials( StringTools.getBytesUtf8( password ) );
191            
192            getAdminSession().getDirectoryService().getOperationManager().bind( bindContext );
193            
194            return bindContext.getSession();
195        }
196    
197        
198        /**
199         * {@inheritDoc}
200         */
201        public boolean isComplete()
202        {
203            return state == NegotiationState.COMPLETED;
204        }
205    }