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;
21  
22  
23  import java.util.Map;
24  
25  import javax.naming.Name;
26  import javax.naming.NameNotFoundException;
27  import javax.security.auth.Subject;
28  import javax.security.auth.kerberos.KerberosKey;
29  import javax.security.auth.kerberos.KerberosPrincipal;
30  import javax.security.sasl.SaslException;
31  import javax.security.sasl.SaslServer;
32  
33  import org.apache.directory.server.core.CoreSession;
34  import org.apache.directory.server.core.DirectoryService;
35  import org.apache.directory.server.core.authn.LdapPrincipal;
36  import org.apache.directory.server.core.entry.ClonedServerEntry;
37  import org.apache.directory.server.core.interceptor.context.BindOperationContext;
38  import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
39  import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
40  import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry;
41  import org.apache.directory.server.kerberos.shared.store.operations.GetPrincipal;
42  import org.apache.directory.server.ldap.LdapProtocolUtils;
43  import org.apache.directory.server.ldap.LdapService;
44  import org.apache.directory.server.ldap.LdapSession;
45  import org.apache.directory.server.ldap.handlers.bind.MechanismHandler;
46  import org.apache.directory.server.ldap.handlers.bind.SaslConstants;
47  import org.apache.directory.server.protocol.shared.ServiceConfigurationException;
48  import org.apache.directory.shared.ldap.constants.SchemaConstants;
49  import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
50  import org.apache.directory.shared.ldap.exception.LdapException;
51  import org.apache.directory.shared.ldap.message.BindRequest;
52  import org.apache.directory.shared.ldap.message.BindResponse;
53  import org.apache.directory.shared.ldap.message.LdapResult;
54  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
55  import org.apache.directory.shared.ldap.name.LdapDN;
56  import org.apache.directory.shared.ldap.util.ExceptionUtils;
57  import org.apache.directory.shared.ldap.util.StringTools;
58  
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  
63  /**
64   * A single reply handler for {@link BindRequest}s.
65   *
66   * Implements server-side of RFC 2222, sections 4.2 and 4.3.
67   *
68   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
69   * @version $Rev: 664302 $, $Date: 2008-06-07 04:44:00 -0400 (Sat, 07 Jun 2008) $
70   */
71  public class BindHandler extends LdapRequestHandler<BindRequest>
72  {
73      private static final Logger LOG = LoggerFactory.getLogger( BindHandler.class );
74  
75      /** A Hashed Adapter mapping SASL mechanisms to their handlers. */
76      private Map<String, MechanismHandler> handlers;
77  
78      /**
79       * Set the mechanisms handler map.
80       * 
81       * @param handlers The associations btween a machanism and its handler
82       */
83      public void setSaslMechanismHandlers( Map<String, MechanismHandler> handlers )
84      {
85          this.handlers = handlers;
86      }
87      
88  
89      /**
90       * Handle the Simple authentication.
91       *
92       * @param session The associated Session
93       * @param message The BindRequest received
94       * @throws Exception If the authentication cannot be done
95       */
96      public void handleSimpleAuth( LdapSession ldapSession, BindRequest bindRequest ) throws Exception
97      {
98          // if the user is already bound, we have to unbind him
99          if ( !ldapSession.isAnonymous() )
100         {
101             // We already have a bound session for this user. We have to
102             // abandon it first.
103             ldapSession.getCoreSession().unbind();
104             
105             // Reset the status to Anonymous
106             ldapSession.setAnonymous();
107         }
108 
109         // Now, bind the user
110         
111         // create a new Bind context, with a null session, as we don't have 
112         // any context yet.
113         BindOperationContext opContext = new BindOperationContext( null );
114         
115         // Stores the DN of the user to check, and its password
116         opContext.setDn( bindRequest.getName() );
117         opContext.setCredentials( bindRequest.getCredentials() );
118 
119         // Stores the request controls into the operation context
120         LdapProtocolUtils.setRequestControls( opContext, bindRequest );
121         
122         try
123         {
124             /*
125              * Referral handling as specified by RFC 3296 here:
126              *    
127              *      http://www.faqs.org/rfcs/rfc3296.html
128              *      
129              * See section 5.6.1 where if the bind principal DN is a referral 
130              * we return an invalidCredentials result response.  Optionally we
131              * could support delegated authentication in the future with this
132              * potential.  See the following JIRA for more on this possibility:
133              * 
134              *      https://issues.apache.org/jira/browse/DIRSERVER-1217
135              *      
136              * NOTE: if this is done then this handler should extend the 
137              * a modified form of the ReferralAwareRequestHandler so it can 
138              * detect conditions where ancestors of the DN are referrals
139              * and delegate appropriately.
140              */
141             ClonedServerEntry principalEntry = null;
142             
143             try
144             {
145                 principalEntry = getLdapServer().getDirectoryService()
146                     .getAdminSession().lookup( bindRequest.getName() );
147             }
148             catch ( NameNotFoundException e ) 
149             {
150                 // this is OK
151             }
152 
153             if ( principalEntry == null || 
154                  principalEntry.getOriginalEntry().contains( SchemaConstants.OBJECT_CLASS_AT, 
155                      SchemaConstants.REFERRAL_OC ) )
156             {
157                 LdapResult 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             LdapResult 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 ( ! ldapService.getSupportedMechanisms().contains( saslMechanism ) )
237         {
238             LOG.error( "Bind error : {} mechanism not supported. Please check the server.xml " + 
239                 "configuration file (supportedMechanisms field)", 
240                 saslMechanism );
241 
242             return false;
243         }
244         else
245         {
246             return true;
247         }
248     }
249     
250     
251     /**
252      * For challenge/response exchange, generate the challenge 
253      *
254      * @param ldapSession
255      * @param ss
256      * @param bindRequest
257      */
258     private void generateSaslChallenge( LdapSession ldapSession, SaslServer ss, BindRequest bindRequest )
259     {
260         LdapResult result = bindRequest.getResultResponse().getLdapResult();
261 
262         // SaslServer will throw an exception if the credentials are null.
263         if ( bindRequest.getCredentials() == null )
264         {
265             bindRequest.setCredentials( StringTools.EMPTY_BYTES );
266         }
267 
268         try
269         {
270             // Compute the challenge
271             byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() );
272     
273             if ( ss.isComplete() )
274             {
275                 // This is the end of the C/R exchange
276                 if ( tokenBytes != null )
277                 {
278                     /*
279                      * There may be a token to return to the client.  We set it here
280                      * so it will be returned in a SUCCESS message, after an LdapContext
281                      * has been initialized for the client.
282                      */
283                     ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes );
284                 }
285                 
286                 // Return the successful response
287                 sendBindSuccess( ldapSession, bindRequest, tokenBytes );
288             }
289             else
290             {
291                 // The SASL bind must continue, we are sending the computed challenge
292                 LOG.info( "Continuation token had length " + tokenBytes.length );
293                 
294                 // Build the response
295                 result.setResultCode( ResultCodeEnum.SASL_BIND_IN_PROGRESS );
296                 BindResponse resp = ( BindResponse ) bindRequest.getResultResponse();
297 
298                 // Store the challenge
299                 resp.setServerSaslCreds( tokenBytes );
300                 
301                 // Switch to AuthPending
302                 ldapSession.setAuthPending();
303                 
304                 // And write back the response
305                 ldapSession.getIoSession().write( resp );
306                 LOG.debug( "Returning final authentication data to client to complete context." );
307             }
308         }
309         catch ( SaslException se )
310         {
311             LOG.error( se.getMessage() );
312             result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
313             result.setErrorMessage( ResultCodeEnum.INVALID_CREDENTIALS.toString() + ": " 
314                 + se.getMessage() );
315             
316             // Reinitialize the state to Anonymous and clear the sasl properties
317             ldapSession.clearSaslProperties();
318             ldapSession.setAnonymous();
319             
320             // Write back the error response
321             ldapSession.getIoSession().write( bindRequest.getResultResponse() );
322         }
323     }
324     
325     
326     /**
327      * Send back an AUTH-METH-NOT-SUPPORTED error message to the client
328      */
329     private void sendAuthMethNotSupported( LdapSession ldapSession, BindRequest bindRequest )
330     {
331         // First, r-einit the state to Anonymous, and clear the
332         // saslProperty map
333         ldapSession.clearSaslProperties();
334         ldapSession.setAnonymous();
335         
336         // And send the response to the client
337         LdapResult bindResult = bindRequest.getResultResponse().getLdapResult();
338         bindResult.setResultCode( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED );
339         bindResult.setErrorMessage( ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED.toString() + ": " 
340             + bindRequest.getSaslMechanism() + " is not a supported mechanism." );
341         
342         // Write back the error
343         ldapSession.getIoSession().write( bindRequest.getResultResponse() );
344 
345         return;
346     }
347     
348     
349     /**
350      * Send back an INVALID-CREDENTIAL error message to the user. If we have an exception
351      * as a third argument, then send back the associated message to the client. 
352      */
353     private void sendInvalidCredentials( LdapSession ldapSession, BindRequest bindRequest, Exception e )
354     {
355         LdapResult result = bindRequest.getResultResponse().getLdapResult();
356         
357         String message = "";
358         
359         if ( e != null )
360         {
361             message = ResultCodeEnum.INVALID_CREDENTIALS + ": " + e.getMessage();
362         }
363         else
364         {
365             message = ResultCodeEnum.INVALID_CREDENTIALS.toString();
366         }
367         
368         LOG.error( message );
369         result.setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
370         result.setErrorMessage( message );
371         
372         // Reinitialize the state to Anonymous and clear the sasl properties
373         ldapSession.clearSaslProperties();
374         ldapSession.setAnonymous();
375         
376         // Write back the error response
377         ldapSession.getIoSession().write( bindRequest.getResultResponse() );
378     }
379     
380     
381     /**
382      * Send a SUCCESS message back to the client.
383      */
384     private void sendBindSuccess( LdapSession ldapSession, BindRequest bindRequest, byte[] tokenBytes )
385     {
386         // Return the successful response
387         BindResponse response = ( BindResponse ) bindRequest.getResultResponse();
388         response.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS );
389         response.setServerSaslCreds( tokenBytes );
390         
391         if ( ! ldapSession.getCoreSession().isAnonymous() )
392         {
393             // If we have not been asked to authenticate as Anonymous, authenticate the user
394             ldapSession.setAuthenticated();
395         }
396         else
397         {
398             // Otherwise, switch back to Anonymous
399             ldapSession.setAnonymous();
400         }
401         
402         // Clean the SaslProperties, we don't need them anymore
403         MechanismHandler handler = (MechanismHandler)ldapSession.getSaslProperty( SaslConstants.SASL_MECH_HANDLER );
404         
405         if ( handler != null )
406         {
407             handler.cleanup( ldapSession );
408         }
409 
410         ldapSession.getIoSession().write( response );
411         
412         LOG.debug( "Returned SUCCESS message: {}.", response );
413     }
414 
415     
416     private void handleSaslAuthPending( LdapSession ldapSession, BindRequest bindRequest, DirectoryService ds ) throws Exception
417     {
418         // First, check that we have the same mechanism
419         String saslMechanism = bindRequest.getSaslMechanism();
420         
421         // The empty mechanism is also a request for a new Bind session
422         if ( StringTools.isEmpty( saslMechanism ) || 
423             !ldapSession.getSaslProperty( SaslConstants.SASL_MECH ).equals( saslMechanism ) )
424         {
425             sendAuthMethNotSupported( ldapSession, bindRequest );
426             return;
427         }
428         
429         // We have already received a first BindRequest, and sent back some challenge.
430         // First, check if the mechanism is the same
431         MechanismHandler mechanismHandler = handlers.get( saslMechanism );
432 
433         if ( mechanismHandler == null )
434         {
435             String message = "Handler unavailable for " + saslMechanism;
436             
437             // Clear the saslProperties, and move to the anonymous state
438             ldapSession.clearSaslProperties();
439             ldapSession.setAnonymous();
440             
441             LOG.error( message );
442             throw new IllegalArgumentException( message );
443         }
444 
445         // Get the previously created SaslServer instance
446         SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest );
447         
448         /*
449          * SaslServer will throw an exception if the credentials are null.
450          */
451         if ( bindRequest.getCredentials() == null )
452         {
453             bindRequest.setCredentials( StringTools.EMPTY_BYTES );
454         }
455         
456         byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() );
457         
458         if ( ss.isComplete() )
459         {
460             if ( tokenBytes != null )
461             {
462                 /*
463                  * There may be a token to return to the client.  We set it here
464                  * so it will be returned in a SUCCESS message, after an LdapContext
465                  * has been initialized for the client.
466                  */
467                 ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes );
468             }
469             
470             // Create the user's coreSession
471             try
472             {
473                 LdapPrincipal ldapPrincipal = (LdapPrincipal)ldapSession.getSaslProperty( SaslConstants.SASL_AUTHENT_USER );
474                 
475                 CoreSession userSession = ds.getSession( ldapPrincipal.getJndiName(), ldapPrincipal.getUserPassword(), saslMechanism, null );
476                 
477                 // Set the user session into the ldap session 
478                 ldapSession.setCoreSession( userSession );
479                 
480                 // Mark the user as authenticated
481                 ldapSession.setAuthenticated();
482                 
483                 // Call the cleanup method for the selected mechanism
484                 MechanismHandler handler = (MechanismHandler)ldapSession.getSaslProperty( SaslConstants.SASL_MECH_HANDLER );
485                 handler.cleanup( ldapSession );
486 
487                 // And send a Success response
488                 sendBindSuccess( ldapSession, bindRequest, tokenBytes );
489             }
490             catch ( Exception e )
491             {
492                 // TODO - why is this exception being ignored?  Isn't this 
493                 // really bad?
494                 LOG.error( "Exception encountered while processing Sasl BindRequest", e );
495             }
496         }
497     }
498     
499     
500     /**
501      * Handle the SASL authentication. If the mechanism is known, we are
502      * facing three cases :
503      * <ul>
504      * <li>The user does not has a session yet</li>
505      * <li>The user already has a session</li>
506      * <li>The user has started a SASL negotiation</li>
507      * </lu><br/>
508      * 
509      * In the first case, we initiate a SaslBind session, which will be used all
510      * along the negotiation.<br/>
511      * In the second case, we first have to unbind the user, and initiate a new
512      * SaslBind session.<br/>
513      * In the third case, we have sub cases :
514      * <ul>
515      * <li>The mechanism is not provided : that means the user want to reset the
516      * current negotiation. We move back to an Anonymous state</li>
517      * <li>The mechanism is provided : the user is initializing a new negotiation
518      * with another mechanism. The current SaslBind session is reinitialized</li>
519      * <li></li>
520      * </ul><br/>
521      *
522      * @param session The associated Session
523      * @param message The BindRequest received
524      * @throws Exception If the authentication cannot be done
525      */
526     public void handleSaslAuth( LdapSession ldapSession, BindRequest bindRequest ) throws Exception
527     {
528         String saslMechanism = bindRequest.getSaslMechanism();
529         DirectoryService ds = getLdapServer().getDirectoryService();
530         
531         // Case #2 : the user does have a session. We have to unbind him
532         if ( ldapSession.isAuthenticated() )
533         {
534             // We already have a bound session for this user. We have to
535             // close the previous session first.
536             ldapSession.getCoreSession().unbind();
537             
538             // Reset the status to Anonymous
539             ldapSession.setAnonymous();
540             
541             // Clean the sasl properties
542             ldapSession.clearSaslProperties();
543             
544             // Now we can continue as if the client was Anonymous from the beginning
545         }
546 
547         // case #1 : The user does not have a session.
548         if ( ldapSession.isAnonymous() )
549         {
550             if ( !StringTools.isEmpty( saslMechanism ) )
551             {
552                 // fist check that the mechanism exists
553                 if ( !checkMechanism( ldapSession, saslMechanism ) )
554                 {
555                     // get out !
556                     sendAuthMethNotSupported( ldapSession, bindRequest );
557 
558                     return;
559                 }
560 
561                 // Store the mechanism in the ldap session
562                 ldapSession.putSaslProperty( SaslConstants.SASL_MECH, saslMechanism );
563                 
564 
565                 // Get the handler for this mechanism
566                 MechanismHandler mechanismHandler = handlers.get( saslMechanism );
567                 
568                 // Store the mechanism handler in the salsProperties
569                 ldapSession.putSaslProperty( SaslConstants.SASL_MECH_HANDLER, mechanismHandler );
570                 
571                 // Initialize the mechanism specific data
572                 mechanismHandler.init( ldapSession );
573 
574                 // Get the SaslServer instance which manage the C/R exchange
575                 SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest );
576                 
577                 // We have to generate a challenge
578                 generateSaslChallenge( ldapSession, ss, bindRequest );
579                 
580                 // And get back
581                 return;
582             }
583         }
584         else if ( ldapSession.isAuthPending() )
585         {
586             try
587             {
588                 handleSaslAuthPending( ldapSession, bindRequest, ds );
589             }
590             catch ( SaslException se )
591             {
592                 sendInvalidCredentials( ldapSession, bindRequest, se );
593             }
594             
595             return;
596         }
597     }
598 
599 
600     /**
601      * Create a list of all the configured realms.
602      * 
603      * @param ldapService the LdapService for which we want to get the realms
604      * @return a list of realms, separated by spaces
605      */
606     private String getActiveRealms( LdapService ldapService )
607     {
608         StringBuilder realms = new StringBuilder();
609         boolean isFirst = true;
610 
611         for ( String realm:ldapService.getSaslRealms() )
612         {
613             if ( isFirst )
614             {
615                 isFirst = false;
616             }
617             else
618             {
619                 realms.append( ' ' );
620             }
621             
622             realms.append( realm );
623         }
624 
625         return realms.toString();
626     }
627 
628 
629     private Subject getSubject( LdapService ldapService ) throws Exception
630     {
631         String servicePrincipalName = ldapService.getSaslPrincipal();
632 
633         KerberosPrincipal servicePrincipal = new KerberosPrincipal( servicePrincipalName );
634         GetPrincipal getPrincipal = new GetPrincipal( servicePrincipal );
635 
636         PrincipalStoreEntry entry = null;
637 
638         try
639         {
640             entry = findPrincipal( ldapService, getPrincipal );
641         }
642         catch ( ServiceConfigurationException sce )
643         {
644             String message = "Service principal " + servicePrincipalName + " not found at search base DN "
645                 + ldapService.getSearchBaseDn() + ".";
646             throw new ServiceConfigurationException( message, sce );
647         }
648 
649         if ( entry == null )
650         {
651             String message = "Service principal " + servicePrincipalName + " not found at search base DN "
652                 + ldapService.getSearchBaseDn() + ".";
653             throw new ServiceConfigurationException( message );
654         }
655 
656         Subject subject = new Subject();
657 
658         for ( EncryptionType encryptionType:entry.getKeyMap().keySet() )
659         {
660             EncryptionKey key = entry.getKeyMap().get( encryptionType );
661 
662             byte[] keyBytes = key.getKeyValue();
663             int type = key.getKeyType().getOrdinal();
664             int kvno = key.getKeyVersion();
665 
666             KerberosKey serviceKey = new KerberosKey( servicePrincipal, keyBytes, type, kvno );
667 
668             subject.getPrivateCredentials().add( serviceKey );
669         }
670 
671         return subject;
672     }
673     
674 
675     private PrincipalStoreEntry findPrincipal( LdapService ldapService, GetPrincipal getPrincipal ) throws Exception
676     {
677         CoreSession adminSession = ldapService.getDirectoryService().getAdminSession();
678 
679         return ( PrincipalStoreEntry ) getPrincipal.execute( adminSession, null );
680     }    
681     
682 
683     /**
684      * Deal with a received BindRequest
685      * 
686      * @param session The current session
687      * @param bindRequest The received BindRequest
688      * @throws Exception If the authentication cannot be handled
689      */
690     @Override
691     public void handle( LdapSession ldapSession, BindRequest bindRequest ) throws Exception
692     {
693         LOG.debug( "Received: {}", bindRequest );
694 
695         // Guard clause:  LDAP version 3
696         if ( ! bindRequest.getVersion3() )
697         {
698             LOG.error( "Bind error : Only LDAP v3 is supported." );
699             LdapResult bindResult = bindRequest.getResultResponse().getLdapResult();
700             bindResult.setResultCode( ResultCodeEnum.PROTOCOL_ERROR );
701             bindResult.setErrorMessage( "Only LDAP v3 is supported." );
702             ldapSession.getIoSession().write( bindRequest.getResultResponse() );
703             return;
704         }
705 
706         // Deal with the two kinds of authentication : Simple and SASL
707         if ( bindRequest.isSimple() )
708         {
709             handleSimpleAuth( ldapSession, bindRequest );
710         }
711         else
712         {
713             handleSaslAuth( ldapSession, bindRequest );
714         }
715     }
716 }