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.sasl.plain;
21  
22  
23  import java.io.IOException;
24  
25  import javax.naming.InvalidNameException;
26  import javax.security.sasl.SaslException;
27  
28  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
29  import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
30  import org.apache.directory.api.ldap.model.entry.Entry;
31  import org.apache.directory.api.ldap.model.entry.Value;
32  import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
33  import org.apache.directory.api.ldap.model.filter.EqualityNode;
34  import org.apache.directory.api.ldap.model.message.BindRequest;
35  import org.apache.directory.api.ldap.model.message.SearchScope;
36  import org.apache.directory.api.ldap.model.schema.PrepareString;
37  import org.apache.directory.api.util.Strings;
38  import org.apache.directory.server.core.api.CoreSession;
39  import org.apache.directory.server.core.api.DirectoryService;
40  import org.apache.directory.server.core.api.OperationEnum;
41  import org.apache.directory.server.core.api.OperationManager;
42  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
43  import org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
44  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
45  import org.apache.directory.server.i18n.I18n;
46  import org.apache.directory.server.ldap.LdapServer;
47  import org.apache.directory.server.ldap.LdapSession;
48  import org.apache.directory.server.ldap.handlers.sasl.AbstractSaslServer;
49  
50  
51  /**
52   * A SaslServer implementation for PLAIN based SASL mechanism.  This is
53   * required unfortunately because the JDK's SASL provider does not support
54   * this mechanism.
55   *
56   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
57   */
58  public final class PlainSaslServer extends AbstractSaslServer
59  {
60      /** The authzid property stored into the LdapSession instance */
61      public static final String SASL_PLAIN_AUTHZID = "authzid";
62  
63      /** The authcid property stored into the LdapSession instance */
64      public static final String SASL_PLAIN_AUTHCID = "authcid";
65  
66      /** The password property stored into the LdapSession instance */
67      public static final String SASL_PLAIN_PASSWORD = "password";
68  
69      /**
70       * The possible states for the negotiation of a PLAIN mechanism. 
71       */
72      private enum NegotiationState
73      {
74          INITIALIZED, // Negotiation has just started 
75          MECH_RECEIVED, // We have received the PLAIN mechanism
76          COMPLETED // The user/password have been received
77      }
78  
79      /**
80       * The different state used by the iInitialResponse decoding
81       */
82      private enum InitialResponse
83      {
84          AUTHZID_EXPECTED, // We are expecting a authzid element
85          AUTHCID_EXPECTED, // We are expecting a authcid element 
86          PASSWORD_EXPECTED // We are expecting a password element
87      }
88  
89      /** The current negotiation state */
90      private NegotiationState state;
91  
92      /**
93       * 
94       * Creates a new instance of PlainSaslServer.
95       *
96       * @param ldapSession The associated LdapSession instance
97       * @param adminSession The Administrator session 
98       * @param bindRequest The associated BindRequest object
99       */
100     public PlainSaslServer( LdapSession ldapSession, CoreSession adminSession, BindRequest bindRequest )
101     {
102         super( ldapSession, adminSession, bindRequest );
103         state = NegotiationState.INITIALIZED;
104 
105         // Reinitialize the SASL properties
106         getLdapSession().removeSaslProperty( SASL_PLAIN_AUTHZID );
107         getLdapSession().removeSaslProperty( SASL_PLAIN_AUTHCID );
108         getLdapSession().removeSaslProperty( SASL_PLAIN_PASSWORD );
109     }
110 
111 
112     /**
113      * {@inheritDoc}
114      */
115     public String getMechanismName()
116     {
117         return SupportedSaslMechanisms.PLAIN;
118     }
119 
120 
121     /**
122      * {@inheritDoc}
123      */
124     public byte[] evaluateResponse( byte[] initialResponse ) throws SaslException
125     {
126         if ( Strings.isEmpty( initialResponse ) )
127         {
128             state = NegotiationState.MECH_RECEIVED;
129             return null;
130         }
131         else
132         {
133             // Split the credentials in three parts :
134             // - the optional authzId
135             // - the authId
136             // - the password
137             // The message should have this structure :
138             // message   = [authzid] '0x00' authcid '0x00' passwd
139             // authzid   = 1*SAFE ; MUST accept up to 255 octets
140             // authcid   = 1*SAFE ; MUST accept up to 255 octets
141             // passwd    = 1*SAFE ; MUST accept up to 255 octets
142             InitialResponse element = InitialResponse.AUTHZID_EXPECTED;
143             String authzId = null;
144             String authcId = null;
145             String password = null;
146 
147             int start = 0;
148             int end = 0;
149 
150             try
151             {
152                 for ( byte b : initialResponse )
153                 {
154                     if ( b == '\0' )
155                     {
156                         if ( start - end == 0 )
157                         {
158                             // We don't have any value
159                             if ( element == InitialResponse.AUTHZID_EXPECTED )
160                             {
161                                 // This is optional : do nothing, but change
162                                 // the element type
163                                 element = InitialResponse.AUTHCID_EXPECTED;
164                             }
165                             else
166                             {
167                                 // This not allowed
168                                 throw new IllegalArgumentException( I18n.err( I18n.ERR_671 ) );
169                             }
170                         }
171                         else
172                         {
173                             start++;
174                             String value = new String( initialResponse, start, end - start + 1, "UTF-8" );
175 
176                             switch ( element )
177                             {
178                                 case AUTHZID_EXPECTED:
179                                     element = InitialResponse.AUTHCID_EXPECTED;
180                                     authzId = PrepareString.normalize( value );
181                                     end++;
182                                     start = end;
183                                     break;
184 
185                                 case AUTHCID_EXPECTED:
186                                     element = InitialResponse.PASSWORD_EXPECTED;
187                                     authcId = PrepareString
188                                         .normalize( value );
189                                     end++;
190                                     start = end;
191                                     break;
192 
193                                 default:
194                                     // This is an error !
195                                     throw new IllegalArgumentException( I18n.err( I18n.ERR_672 ) );
196                             }
197                         }
198                     }
199                     else
200                     {
201                         end++;
202                     }
203                 }
204 
205                 if ( start == end )
206                 {
207                     throw new IllegalArgumentException( I18n.err( I18n.ERR_671 ) );
208                 }
209 
210                 start++;
211                 String value = Strings.utf8ToString( initialResponse, start, end - start + 1 );
212 
213                 password = PrepareString.normalize( value );
214 
215                 if ( ( authcId == null ) || ( password == null ) )
216                 {
217                     throw new IllegalArgumentException( I18n.err( I18n.ERR_671 ) );
218                 }
219 
220                 // Now that we have the authcid and password, try to authenticate.
221                 CoreSession userSession = authenticate( authcId, password );
222 
223                 getLdapSession().setCoreSession( userSession );
224 
225                 state = NegotiationState.COMPLETED;
226             }
227             catch ( IOException ioe )
228             {
229                 throw new IllegalArgumentException( I18n.err( I18n.ERR_674 ) );
230             }
231             catch ( InvalidNameException ine )
232             {
233                 throw new IllegalArgumentException( I18n.err( I18n.ERR_675 ) );
234             }
235             catch ( Exception e )
236             {
237                 throw new SaslException( I18n.err( I18n.ERR_676, authcId ) );
238             }
239         }
240 
241         return Strings.EMPTY_BYTES;
242     }
243 
244 
245     public boolean isComplete()
246     {
247         return state == NegotiationState.COMPLETED;
248     }
249 
250 
251     /**
252      * Try to authenticate the user against the underlying LDAP server. The SASL PLAIN
253      * authentication is based on the entry which uid is equal to the user name we received.
254      */
255     private CoreSession authenticate( String user, String password ) throws Exception
256     {
257         LdapSession ldapSession = getLdapSession();
258         CoreSession adminSession = getAdminSession();
259         DirectoryService directoryService = adminSession.getDirectoryService();
260         LdapServer ldapServer = ldapSession.getLdapServer();
261         OperationManager operationManager = directoryService.getOperationManager();
262 
263         // first, we have to find the entries which has the uid value
264         EqualityNode<String> filter = new EqualityNode<>(
265             directoryService.getSchemaManager().getAttributeType( SchemaConstants.UID_AT ), new Value( user ) );
266 
267         SearchOperationContextterceptor/context/SearchOperationContext.html#SearchOperationContext">SearchOperationContext searchContext = new SearchOperationContext( directoryService.getAdminSession() );
268         searchContext.setDn( directoryService.getDnFactory().create( ldapServer.getSearchBaseDn() ) );
269         searchContext.setScope( SearchScope.SUBTREE );
270         searchContext.setFilter( filter );
271         searchContext.setNoAttributes( true );
272 
273         EntryFilteringCursor cursor = operationManager.search( searchContext );
274         Exception bindException = new LdapAuthenticationException( "Cannot authenticate user uid=" + user );
275 
276         while ( cursor.next() )
277         {
278             Entry entry = cursor.get();
279 
280             try
281             {
282                 BindOperationContexti/interceptor/context/BindOperationContext.html#BindOperationContext">BindOperationContext bindContext = new BindOperationContext( ldapSession.getCoreSession() );
283                 bindContext.setDn( entry.getDn() );
284                 bindContext.setCredentials( Strings.getBytesUtf8( password ) );
285                 bindContext.setIoSession( ldapSession.getIoSession() );
286                 bindContext.setInterceptors( directoryService.getInterceptors( OperationEnum.BIND ) );
287 
288                 operationManager.bind( bindContext );
289 
290                 cursor.close();
291 
292                 return bindContext.getSession();
293             }
294             catch ( Exception e )
295             {
296                 bindException = e;// Nothing to do here : we will try to bind with the next user
297             }
298         }
299 
300         cursor.close();
301 
302         throw bindException;
303     }
304 }