View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.ldap.handlers.bind.ntlm;
21  
22  
23  import org.apache.directory.server.core.CoreSession;
24  import org.apache.directory.server.core.authn.LdapPrincipal;
25  import org.apache.directory.server.core.interceptor.context.BindOperationContext;
26  import org.apache.directory.server.ldap.LdapSession;
27  import org.apache.directory.server.ldap.handlers.bind.AbstractSaslServer;
28  import org.apache.directory.server.ldap.handlers.bind.SaslConstants;
29  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
30  import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
31  import org.apache.directory.shared.ldap.message.BindRequest;
32  import org.apache.directory.shared.ldap.name.LdapDN;
33  import org.apache.directory.shared.ldap.util.StringTools;
34  
35  import javax.naming.Context;
36  import javax.naming.InvalidNameException;
37  import javax.security.sasl.SaslException;
38  
39  
40  /**
41   * A SaslServer implementation for NTLM based SASL mechanism.  This is
42   * required unfortunately because the JDK's SASL provider does not support
43   * this mechanism.
44   *
45   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
46   * @version $$Rev$$
47   */
48  public class NtlmSaslServer extends AbstractSaslServer
49  {
50      /** The different states during a NTLM negotiation */ 
51      enum NegotiationState { INITIALIZED, TYPE_1_RECEIVED, TYPE_2_SENT, TYPE_3_RECEIVED, COMPLETED }
52  
53      /** The current state */
54      private NegotiationState state = NegotiationState.INITIALIZED;
55      private final NtlmProvider provider;
56  
57      
58      public NtlmSaslServer( NtlmProvider provider, BindRequest bindRequest, LdapSession ldapSession )
59      {
60          super( ldapSession, null, bindRequest );
61          this.provider = provider;
62      }
63  
64  
65      /**
66       * {@inheritDoc}
67       */
68      public String getMechanismName()
69      {
70          return SupportedSaslMechanisms.NTLM;
71      }
72  
73  
74      protected void responseRecieved()
75      {
76          switch ( state )
77          {
78              case INITIALIZED:
79                  state = NegotiationState.TYPE_1_RECEIVED;
80                  break;
81                  
82              case TYPE_1_RECEIVED:
83                  throw new IllegalStateException( "Cannot receive NTLM message before sending Type 2 challenge." );
84                  
85              case TYPE_2_SENT:
86                  state = NegotiationState.TYPE_3_RECEIVED;
87                  break;
88                  
89              case TYPE_3_RECEIVED:
90                  throw new IllegalStateException( "Cannot receive NTLM message after Type 3 has been received." );
91                  
92              case COMPLETED:
93                  throw new IllegalStateException( "Sasl challenge response already completed." );
94          }
95      }
96  
97  
98      protected void responseSent()
99      {
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 }