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;
021
022
023 import java.util.Map;
024
025 import javax.naming.Name;
026 import javax.naming.NameNotFoundException;
027 import javax.security.sasl.SaslException;
028 import javax.security.sasl.SaslServer;
029
030 import org.apache.directory.server.core.CoreSession;
031 import org.apache.directory.server.core.DirectoryService;
032 import org.apache.directory.server.core.authn.LdapPrincipal;
033 import org.apache.directory.server.core.entry.ClonedServerEntry;
034 import org.apache.directory.server.core.interceptor.context.BindOperationContext;
035 import org.apache.directory.server.ldap.LdapProtocolUtils;
036 import org.apache.directory.server.ldap.LdapSession;
037 import org.apache.directory.server.ldap.handlers.bind.MechanismHandler;
038 import org.apache.directory.server.ldap.handlers.bind.SaslConstants;
039 import org.apache.directory.shared.ldap.constants.SchemaConstants;
040 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
041 import org.apache.directory.shared.ldap.exception.LdapException;
042 import org.apache.directory.shared.ldap.message.InternalBindRequest;
043 import org.apache.directory.shared.ldap.message.InternalBindResponse;
044 import org.apache.directory.shared.ldap.message.InternalLdapResult;
045 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
046 import org.apache.directory.shared.ldap.name.LdapDN;
047 import org.apache.directory.shared.ldap.util.ExceptionUtils;
048 import org.apache.directory.shared.ldap.util.StringTools;
049 import org.slf4j.Logger;
050 import org.slf4j.LoggerFactory;
051
052
053 /**
054 * A single reply handler for {@link InternalBindRequest}s.
055 *
056 * Implements server-side of RFC 2222, sections 4.2 and 4.3.
057 *
058 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
059 * @version $Rev: 664302 $, $Date: 2008-06-07 04:44:00 -0400 (Sat, 07 Jun 2008) $
060 */
061 public class BindHandler extends LdapRequestHandler<InternalBindRequest>
062 {
063 private static final Logger LOG = LoggerFactory.getLogger( BindHandler.class );
064
065 /** A Hashed Adapter mapping SASL mechanisms to their handlers. */
066 private Map<String, MechanismHandler> handlers;
067
068
069 /**
070 * Set the mechanisms handler map.
071 *
072 * @param handlers The associations btween a machanism and its handler
073 */
074 public void setSaslMechanismHandlers( Map<String, MechanismHandler> handlers )
075 {
076 this.handlers = handlers;
077 }
078
079
080 /**
081 * Handle the Simple authentication.
082 *
083 * @param session The associated Session
084 * @param message The BindRequest received
085 * @throws Exception If the authentication cannot be done
086 */
087 public void handleSimpleAuth( LdapSession ldapSession, InternalBindRequest bindRequest ) throws Exception
088 {
089 // if the user is already bound, we have to unbind him
090 if ( !ldapSession.isAnonymous() )
091 {
092 // We already have a bound session for this user. We have to
093 // abandon it first.
094 ldapSession.getCoreSession().unbind();
095
096 // Reset the status to Anonymous
097 ldapSession.setAnonymous();
098 }
099
100 // Now, bind the user
101
102 // create a new Bind context, with a null session, as we don't have
103 // any context yet.
104 BindOperationContext opContext = new BindOperationContext( null );
105
106 // Stores the DN of the user to check, and its password
107 opContext.setDn( bindRequest.getName() );
108 opContext.setCredentials( bindRequest.getCredentials() );
109
110 // Stores the request controls into the operation context
111 LdapProtocolUtils.setRequestControls( opContext, bindRequest );
112
113 try
114 {
115 /*
116 * Referral handling as specified by RFC 3296 here:
117 *
118 * http://www.faqs.org/rfcs/rfc3296.html
119 *
120 * See section 5.6.1 where if the bind principal DN is a referral
121 * we return an invalidCredentials result response. Optionally we
122 * could support delegated authentication in the future with this
123 * potential. See the following JIRA for more on this possibility:
124 *
125 * https://issues.apache.org/jira/browse/DIRSERVER-1217
126 *
127 * NOTE: if this is done then this handler should extend the
128 * a modified form of the ReferralAwareRequestHandler so it can
129 * detect conditions where ancestors of the DN are referrals
130 * and delegate appropriately.
131 */
132 ClonedServerEntry principalEntry = null;
133
134 try
135 {
136 principalEntry = getLdapServer().getDirectoryService().getAdminSession().lookup( bindRequest.getName() );
137 }
138 catch ( NameNotFoundException e )
139 {
140 // this is OK
141 }
142
143 if ( principalEntry == null )
144 {
145 LOG.info( "The {} principalDN cannot be found in the server : bind failure.", bindRequest.getName() );
146 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult();
147 result.setErrorMessage( "cannot bind the principalDn." );
148 result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
149 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
150 return;
151 }
152
153 if ( principalEntry.getOriginalEntry().contains( SchemaConstants.OBJECT_CLASS_AT,
154 SchemaConstants.REFERRAL_OC ) )
155 {
156 LOG.info( "Bind principalDn points to referral." );
157 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult();
158 result.setErrorMessage( "Bind principalDn points to referral." );
159 result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
160 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
161 return;
162 }
163
164 // TODO - might cause issues since lookups are not returning all
165 // attributes right now - this is an optimization that can be
166 // enabled later after determining whether or not this will cause
167 // issues.
168 // reuse the looked up entry so we don't incur another lookup
169 // opContext.setEntry( principalEntry );
170
171 // And call the OperationManager bind operation.
172 getLdapServer().getDirectoryService().getOperationManager().bind( opContext );
173
174 // As a result, store the created session in the Core Session
175 ldapSession.setCoreSession( opContext.getSession() );
176
177 if ( !ldapSession.getCoreSession().isAnonymous() )
178 {
179 ldapSession.setAuthenticated();
180 }
181
182 // Return the successful response
183 sendBindSuccess( ldapSession, bindRequest, null );
184 }
185 catch ( Exception e )
186 {
187 // Something went wrong. Write back an error message
188 ResultCodeEnum code = null;
189 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult();
190
191 if ( e instanceof LdapException )
192 {
193 code = ( ( LdapException ) e ).getResultCode();
194 result.setResultCode( code );
195 }
196 else
197 {
198 code = ResultCodeEnum.getBestEstimate( e, bindRequest.getType() );
199 result.setResultCode( code );
200 }
201
202 String msg = code.toString() + ": Bind failed: " + e.getMessage();
203
204 if ( LOG.isDebugEnabled() )
205 {
206 msg += ":\n" + ExceptionUtils.getStackTrace( e );
207 msg += "\n\nBindRequest = \n" + bindRequest.toString();
208 }
209
210 Name name = null;
211
212 if ( e instanceof LdapAuthenticationException )
213 {
214 name = ( ( LdapAuthenticationException ) e ).getResolvedName();
215 }
216
217 if ( ( name != null )
218 && ( ( code == ResultCodeEnum.NO_SUCH_OBJECT ) || ( code == ResultCodeEnum.ALIAS_PROBLEM )
219 || ( code == ResultCodeEnum.INVALID_DN_SYNTAX ) || ( code == ResultCodeEnum.ALIAS_DEREFERENCING_PROBLEM ) ) )
220 {
221 result.setMatchedDn( new LdapDN( name ) );
222 }
223
224 result.setErrorMessage( msg );
225 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
226 }
227 }
228
229
230 /**
231 * Check if the mechanism exists.
232 */
233 private boolean checkMechanism( LdapSession ldapSession, String saslMechanism ) throws Exception
234 {
235 // Guard clause: Reject unsupported SASL mechanisms.
236 if ( !ldapServer.getSupportedMechanisms().contains( saslMechanism ) )
237 {
238 LOG.error( "Bind error : {} mechanism not supported. Please check the server.xml "
239 + "configuration file (supportedMechanisms field)", saslMechanism );
240
241 return false;
242 }
243 else
244 {
245 return true;
246 }
247 }
248
249
250 /**
251 * For challenge/response exchange, generate the challenge.
252 * If the exchange is complete then send bind success.
253 *
254 * @param ldapSession
255 * @param ss
256 * @param bindRequest
257 */
258 private void generateSaslChallengeOrComplete( LdapSession ldapSession, SaslServer ss,
259 InternalBindRequest bindRequest ) throws Exception
260 {
261 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult();
262
263 // SaslServer will throw an exception if the credentials are null.
264 if ( bindRequest.getCredentials() == null )
265 {
266 bindRequest.setCredentials( StringTools.EMPTY_BYTES );
267 }
268
269 try
270 {
271 // Compute the challenge
272 byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() );
273
274 if ( ss.isComplete() )
275 {
276 // This is the end of the C/R exchange
277 if ( tokenBytes != null )
278 {
279 /*
280 * There may be a token to return to the client. We set it here
281 * so it will be returned in a SUCCESS message, after an LdapContext
282 * has been initialized for the client.
283 */
284 ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes );
285 }
286
287 LdapPrincipal ldapPrincipal = ( LdapPrincipal ) ldapSession
288 .getSaslProperty( SaslConstants.SASL_AUTHENT_USER );
289 if ( ldapPrincipal != null )
290 {
291 DirectoryService ds = ldapSession.getLdapServer().getDirectoryService();
292 String saslMechanism = bindRequest.getSaslMechanism();
293 CoreSession userSession = ds.getSession( ldapPrincipal.getJndiName(), ldapPrincipal
294 .getUserPassword(), saslMechanism, null );
295
296 // Set the user session into the ldap session
297 ldapSession.setCoreSession( userSession );
298 }
299
300 // Mark the user as authenticated
301 ldapSession.setAuthenticated();
302
303 // Call the cleanup method for the selected mechanism
304 MechanismHandler handler = ( MechanismHandler ) ldapSession
305 .getSaslProperty( SaslConstants.SASL_MECH_HANDLER );
306 handler.cleanup( ldapSession );
307
308 // Return the successful response
309 sendBindSuccess( ldapSession, bindRequest, tokenBytes );
310 }
311 else
312 {
313 // The SASL bind must continue, we are sending the computed challenge
314 LOG.info( "Continuation token had length " + tokenBytes.length );
315
316 // Build the response
317 result.setResultCode( ResultCodeEnum.SASL_BIND_IN_PROGRESS );
318 InternalBindResponse resp = ( InternalBindResponse ) bindRequest.getResultResponse();
319
320 // Store the challenge
321 resp.setServerSaslCreds( tokenBytes );
322
323 // Switch to AuthPending
324 ldapSession.setAuthPending();
325
326 // And write back the response
327 ldapSession.getIoSession().write( resp );
328 LOG.debug( "Returning final authentication data to client to complete context." );
329 }
330 }
331 catch ( SaslException se )
332 {
333 sendInvalidCredentials( ldapSession, bindRequest, se );
334 }
335 }
336
337
338 /**
339 * Send back an AUTH-METH-NOT-SUPPORTED error message to the client
340 */
341 private void sendAuthMethNotSupported( LdapSession ldapSession, InternalBindRequest bindRequest )
342 {
343 // First, r-einit the state to Anonymous, and clear the
344 // saslProperty map
345 ldapSession.clearSaslProperties();
346 ldapSession.setAnonymous();
347
348 // And send the response to the client
349 InternalLdapResult bindResult = bindRequest.getResultResponse().getLdapResult();
350 bindResult.setResultCode( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED );
351 bindResult.setErrorMessage( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED.toString() + ": "
352 + bindRequest.getSaslMechanism() + " is not a supported mechanism." );
353
354 // Write back the error
355 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
356
357 return;
358 }
359
360
361 /**
362 * Send back an INVALID-CREDENTIAL error message to the user. If we have an exception
363 * as a third argument, then send back the associated message to the client.
364 */
365 private void sendInvalidCredentials( LdapSession ldapSession, InternalBindRequest bindRequest, Exception e )
366 {
367 InternalLdapResult result = bindRequest.getResultResponse().getLdapResult();
368
369 String message = "";
370
371 if ( e != null )
372 {
373 message = ResultCodeEnum.INVALID_CREDENTIALS + ": " + e.getMessage();
374 }
375 else
376 {
377 message = ResultCodeEnum.INVALID_CREDENTIALS.toString();
378 }
379
380 LOG.error( message );
381 result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
382 result.setErrorMessage( message );
383
384 // Reinitialize the state to Anonymous and clear the sasl properties
385 ldapSession.clearSaslProperties();
386 ldapSession.setAnonymous();
387
388 // Write back the error response
389 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
390 }
391
392
393 /**
394 * Send a SUCCESS message back to the client.
395 */
396 private void sendBindSuccess( LdapSession ldapSession, InternalBindRequest bindRequest, byte[] tokenBytes )
397 {
398 // Return the successful response
399 InternalBindResponse response = ( InternalBindResponse ) bindRequest.getResultResponse();
400 response.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS );
401 response.setServerSaslCreds( tokenBytes );
402
403 if ( !ldapSession.getCoreSession().isAnonymous() )
404 {
405 // If we have not been asked to authenticate as Anonymous, authenticate the user
406 ldapSession.setAuthenticated();
407 }
408 else
409 {
410 // Otherwise, switch back to Anonymous
411 ldapSession.setAnonymous();
412 }
413
414 // Clean the SaslProperties, we don't need them anymore
415 MechanismHandler handler = ( MechanismHandler ) ldapSession.getSaslProperty( SaslConstants.SASL_MECH_HANDLER );
416
417 if ( handler != null )
418 {
419 handler.cleanup( ldapSession );
420 }
421
422 ldapSession.getIoSession().write( response );
423
424 LOG.debug( "Returned SUCCESS message: {}.", response );
425 }
426
427
428 private void handleSaslAuthPending( LdapSession ldapSession, InternalBindRequest bindRequest, DirectoryService ds )
429 throws Exception
430 {
431 // First, check that we have the same mechanism
432 String saslMechanism = bindRequest.getSaslMechanism();
433
434 // The empty mechanism is also a request for a new Bind session
435 if ( StringTools.isEmpty( saslMechanism )
436 || !ldapSession.getSaslProperty( SaslConstants.SASL_MECH ).equals( saslMechanism ) )
437 {
438 sendAuthMethNotSupported( ldapSession, bindRequest );
439 return;
440 }
441
442 // We have already received a first BindRequest, and sent back some challenge.
443 // First, check if the mechanism is the same
444 MechanismHandler mechanismHandler = handlers.get( saslMechanism );
445
446 if ( mechanismHandler == null )
447 {
448 String message = "Handler unavailable for " + saslMechanism;
449
450 // Clear the saslProperties, and move to the anonymous state
451 ldapSession.clearSaslProperties();
452 ldapSession.setAnonymous();
453
454 LOG.error( message );
455 throw new IllegalArgumentException( message );
456 }
457
458 // Get the previously created SaslServer instance
459 SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest );
460
461 generateSaslChallengeOrComplete( ldapSession, ss, bindRequest );
462 }
463
464
465 /**
466 * Handle the SASL authentication. If the mechanism is known, we are
467 * facing three cases :
468 * <ul>
469 * <li>The user does not has a session yet</li>
470 * <li>The user already has a session</li>
471 * <li>The user has started a SASL negotiation</li>
472 * </lu><br/>
473 *
474 * In the first case, we initiate a SaslBind session, which will be used all
475 * along the negotiation.<br/>
476 * In the second case, we first have to unbind the user, and initiate a new
477 * SaslBind session.<br/>
478 * In the third case, we have sub cases :
479 * <ul>
480 * <li>The mechanism is not provided : that means the user want to reset the
481 * current negotiation. We move back to an Anonymous state</li>
482 * <li>The mechanism is provided : the user is initializing a new negotiation
483 * with another mechanism. The current SaslBind session is reinitialized</li>
484 * <li></li>
485 * </ul><br/>
486 *
487 * @param session The associated Session
488 * @param message The BindRequest received
489 * @throws Exception If the authentication cannot be done
490 */
491 public void handleSaslAuth( LdapSession ldapSession, InternalBindRequest bindRequest ) throws Exception
492 {
493 String saslMechanism = bindRequest.getSaslMechanism();
494 DirectoryService ds = getLdapServer().getDirectoryService();
495
496 // Case #2 : the user does have a session. We have to unbind him
497 if ( ldapSession.isAuthenticated() )
498 {
499 // We already have a bound session for this user. We have to
500 // close the previous session first.
501 ldapSession.getCoreSession().unbind();
502
503 // Reset the status to Anonymous
504 ldapSession.setAnonymous();
505
506 // Clean the sasl properties
507 ldapSession.clearSaslProperties();
508
509 // Now we can continue as if the client was Anonymous from the beginning
510 }
511
512 // case #1 : The user does not have a session.
513 if ( ldapSession.isAnonymous() )
514 {
515 if ( !StringTools.isEmpty( saslMechanism ) )
516 {
517 // fist check that the mechanism exists
518 if ( !checkMechanism( ldapSession, saslMechanism ) )
519 {
520 // get out !
521 sendAuthMethNotSupported( ldapSession, bindRequest );
522
523 return;
524 }
525
526 // Store the mechanism in the ldap session
527 ldapSession.putSaslProperty( SaslConstants.SASL_MECH, saslMechanism );
528
529 // Get the handler for this mechanism
530 MechanismHandler mechanismHandler = handlers.get( saslMechanism );
531
532 // Store the mechanism handler in the salsProperties
533 ldapSession.putSaslProperty( SaslConstants.SASL_MECH_HANDLER, mechanismHandler );
534
535 // Initialize the mechanism specific data
536 mechanismHandler.init( ldapSession );
537
538 // Get the SaslServer instance which manage the C/R exchange
539 SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest );
540
541 // We have to generate a challenge
542 generateSaslChallengeOrComplete( ldapSession, ss, bindRequest );
543
544 // And get back
545 return;
546 }
547 }
548 else if ( ldapSession.isAuthPending() )
549 {
550 try
551 {
552 handleSaslAuthPending( ldapSession, bindRequest, ds );
553 }
554 catch ( SaslException se )
555 {
556 sendInvalidCredentials( ldapSession, bindRequest, se );
557 }
558
559 return;
560 }
561 }
562
563
564 /**
565 * Deal with a received BindRequest
566 *
567 * @param session The current session
568 * @param bindRequest The received BindRequest
569 * @throws Exception If the authentication cannot be handled
570 */
571 @Override
572 public void handle( LdapSession ldapSession, InternalBindRequest bindRequest ) throws Exception
573 {
574 LOG.debug( "Received: {}", bindRequest );
575
576 // Guard clause: LDAP version 3
577 if ( !bindRequest.getVersion3() )
578 {
579 LOG.error( "Bind error : Only LDAP v3 is supported." );
580 InternalLdapResult bindResult = bindRequest.getResultResponse().getLdapResult();
581 bindResult.setResultCode( ResultCodeEnum.PROTOCOL_ERROR );
582 bindResult.setErrorMessage( "Only LDAP v3 is supported." );
583 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
584 return;
585 }
586
587 // Deal with the two kinds of authentication : Simple and SASL
588 if ( bindRequest.isSimple() )
589 {
590 handleSimpleAuth( ldapSession, bindRequest );
591 }
592 else
593 {
594 handleSaslAuth( ldapSession, bindRequest );
595 }
596 }
597 }