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.plain;
21  
22  
23  import java.io.IOException;
24  
25  import org.apache.directory.server.core.CoreSession;
26  import org.apache.directory.server.core.interceptor.context.BindOperationContext;
27  import org.apache.directory.server.ldap.LdapSession;
28  import org.apache.directory.server.ldap.handlers.bind.AbstractSaslServer;
29  import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
30  import org.apache.directory.shared.ldap.message.BindRequest;
31  import org.apache.directory.shared.ldap.name.LdapDN;
32  import org.apache.directory.shared.ldap.schema.PrepareString;
33  import org.apache.directory.shared.ldap.util.StringTools;
34  
35  import javax.naming.InvalidNameException;
36  import javax.security.sasl.SaslException;
37  
38  
39  /**
40   * A SaslServer implementation for PLAIN based SASL mechanism.  This is
41   * required unfortunately because the JDK's SASL provider does not support
42   * this mechanism.
43   *
44   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
45   * @version $$Rev$$
46   */
47  public class PlainSaslServer extends AbstractSaslServer
48  {
49      /** The authzid property stored into the LdapSession instance */
50      public static final String SASL_PLAIN_AUTHZID = "authzid";
51      
52      /** The authcid property stored into the LdapSession instance */
53      public static final String SASL_PLAIN_AUTHCID = "authcid";
54  
55      /** The password property stored into the LdapSession instance */
56      public static final String SASL_PLAIN_PASSWORD = "password";
57      
58      
59      /**
60       * The possible states for the negotiation of a PLAIN mechanism. 
61       */
62      private enum NegotiationState 
63      {
64          INITIALIZED,    // Negotiation has just started 
65          MECH_RECEIVED,  // We have received the PLAIN mechanism
66          COMPLETED       // The user/password have been received
67      }
68      
69      
70      /**
71       * The different state used by the iInitialResponse decoding
72       */
73      private enum InitialResponse
74      {
75          AUTHZID_EXPECTED,    // We are expecting a authzid element
76          AUTHCID_EXPECTED,    // We are expecting a authcid element 
77          PASSWORD_EXPECTED    // We are expecting a password element
78      }
79  
80      /** The current negotiation state */
81      private NegotiationState state;
82      
83      
84      /**
85       * 
86       * Creates a new instance of PlainSaslServer.
87       *
88       * @param bindRequest The associated BindRequest object
89       * @param ldapSession The associated LdapSession instance 
90       */
91      public PlainSaslServer( LdapSession ldapSession, CoreSession adminSession, BindRequest bindRequest )
92      {
93          super( ldapSession, adminSession, bindRequest );
94          state = NegotiationState.INITIALIZED;
95          
96          // Reinitialize the SASL properties
97          getLdapSession().removeSaslProperty( SASL_PLAIN_AUTHZID );
98          getLdapSession().removeSaslProperty( SASL_PLAIN_AUTHCID );
99          getLdapSession().removeSaslProperty( SASL_PLAIN_PASSWORD );
100     }
101 
102 
103     /**
104      * {@inheritDoc}
105      */
106     public String getMechanismName()
107     {
108         return SupportedSaslMechanisms.PLAIN;
109     }
110 
111 
112     /**
113      * {@inheritDoc}
114      */
115     public byte[] evaluateResponse( byte[] initialResponse ) throws SaslException
116     {
117         if ( StringTools.isEmpty( initialResponse ) )
118         {
119             state = NegotiationState.MECH_RECEIVED;
120             return null;
121         }
122         else
123         {
124             // Split the credentials in three parts :
125             // - the optional authzId
126             // - the authId
127             // - the password
128             InitialResponse element = InitialResponse.AUTHZID_EXPECTED;
129             String authzId = null;
130             String authcId = null;
131             String password = null;
132             
133             int start = 0;
134             int end = 0;
135             
136             try
137             {
138                 for ( byte b:initialResponse )
139                 {
140                     if ( b == '\0' )
141                     {
142                         if ( start - end == 0 )
143                         {
144                             // We don't have any value
145                             if ( element == InitialResponse.AUTHZID_EXPECTED )
146                             {
147                                 // This is optional : do nothing, but change
148                                 // the element type
149                                 element = InitialResponse.AUTHCID_EXPECTED;
150                                 continue;
151                             }
152                             else
153                             {
154                                 // This not allowed
155                                 throw new IllegalArgumentException( "response with no auhcid or no password" );
156                             }
157                         }
158                         else
159                         {
160                             start++;
161                             String value = new String( initialResponse, start, end - start + 1, "UTF-8" );
162                             
163                             switch ( element )
164                             {
165                                 case AUTHZID_EXPECTED :
166                                     element = InitialResponse.AUTHCID_EXPECTED;
167                                     authzId = PrepareString.normalize( value, PrepareString.StringType.CASE_EXACT_IA5 );
168                                     end++;
169                                     start = end;
170                                     break;
171                                     
172                                 case AUTHCID_EXPECTED :
173                                     element = InitialResponse.PASSWORD_EXPECTED;
174                                     authcId = PrepareString.normalize( value, PrepareString.StringType.DIRECTORY_STRING );
175                                     end++;
176                                     start = end;
177                                     break;
178                                     
179                                     
180                                 default :
181                                     // This is an error !
182                                     throw new IllegalArgumentException( "'\0' chars are not allowed in authcid or no password" );
183                             }
184                         }
185                     }
186                     else
187                     {
188                         end++;
189                     }
190                 }
191             
192                 if ( start == end )
193                 {
194                     throw new IllegalArgumentException( "response with no auhcid or no password" );
195                 }
196                 
197                 start++;
198                 String value = StringTools.utf8ToString( initialResponse, start, end - start + 1 );
199                 
200                 password = PrepareString.normalize( value, PrepareString.StringType.CASE_EXACT_IA5 );
201                 
202                 if ( ( authcId == null ) || ( password == null ) )
203                 {
204                     throw new IllegalArgumentException( "response with no auhcid or no password" );
205                 }
206                 
207                 // Now that we have the authcid and password, try to authenticate.
208                 CoreSession userSession = authenticate( authcId, password );
209                 
210                 getLdapSession().setCoreSession( userSession );
211                 
212                 state = NegotiationState.COMPLETED;
213             }
214             catch ( IOException ioe )
215             {
216                 throw new IllegalArgumentException( "The given InitialReponse is incorrect" );
217             }
218             catch ( InvalidNameException ine )
219             {
220                 throw new IllegalArgumentException( "Cannot authenticate an invalid authcid DN" );
221             }
222             catch ( Exception e )
223             {
224                 throw new SaslException( "Cannot authenticate the user " + authcId );
225             }
226         }
227 
228         return StringTools.EMPTY_BYTES;
229     }
230 
231 
232     public boolean isComplete()
233     {
234         return state == NegotiationState.COMPLETED;
235     }
236     
237     
238     /**
239      * Try to authenticate the usr against the underlying LDAP server.
240      */
241     private CoreSession authenticate( String user, String password ) throws InvalidNameException, Exception
242     {
243         BindOperationContext bindContext = new BindOperationContext( getLdapSession().getCoreSession() );
244         bindContext.setDn( new LdapDN( user ) );
245         bindContext.setCredentials( StringTools.getBytesUtf8( password ) );
246         
247         getAdminSession().getDirectoryService().getOperationManager().bind( bindContext );
248         
249         return bindContext.getSession();
250     }
251 }