1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
65
66
67
68
69
70
71 public class BindHandler extends LdapRequestHandler<BindRequest>
72 {
73 private static final Logger LOG = LoggerFactory.getLogger( BindHandler.class );
74
75
76 private Map<String, MechanismHandler> handlers;
77
78
79
80
81
82
83 public void setSaslMechanismHandlers( Map<String, MechanismHandler> handlers )
84 {
85 this.handlers = handlers;
86 }
87
88
89
90
91
92
93
94
95
96 public void handleSimpleAuth( LdapSession ldapSession, BindRequest bindRequest ) throws Exception
97 {
98
99 if ( !ldapSession.isAnonymous() )
100 {
101
102
103 ldapSession.getCoreSession().unbind();
104
105
106 ldapSession.setAnonymous();
107 }
108
109
110
111
112
113 BindOperationContext opContext = new BindOperationContext( null );
114
115
116 opContext.setDn( bindRequest.getName() );
117 opContext.setCredentials( bindRequest.getCredentials() );
118
119
120 LdapProtocolUtils.setRequestControls( opContext, bindRequest );
121
122 try
123 {
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
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
165
166
167
168
169
170
171
172 getLdapServer().getDirectoryService().getOperationManager().bind( opContext );
173
174
175 ldapSession.setCoreSession( opContext.getSession() );
176
177 if ( ! ldapSession.getCoreSession().isAnonymous() )
178 {
179 ldapSession.setAuthenticated();
180 }
181
182
183 sendBindSuccess( ldapSession, bindRequest, null );
184 }
185 catch ( Exception e )
186 {
187
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
232
233 private boolean checkMechanism( LdapSession ldapSession, String saslMechanism ) throws Exception
234 {
235
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
253
254
255
256
257
258 private void generateSaslChallenge( LdapSession ldapSession, SaslServer ss, BindRequest bindRequest )
259 {
260 LdapResult result = bindRequest.getResultResponse().getLdapResult();
261
262
263 if ( bindRequest.getCredentials() == null )
264 {
265 bindRequest.setCredentials( StringTools.EMPTY_BYTES );
266 }
267
268 try
269 {
270
271 byte[] tokenBytes = ss.evaluateResponse( bindRequest.getCredentials() );
272
273 if ( ss.isComplete() )
274 {
275
276 if ( tokenBytes != null )
277 {
278
279
280
281
282
283 ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes );
284 }
285
286
287 sendBindSuccess( ldapSession, bindRequest, tokenBytes );
288 }
289 else
290 {
291
292 LOG.info( "Continuation token had length " + tokenBytes.length );
293
294
295 result.setResultCode( ResultCodeEnum.SASL_BIND_IN_PROGRESS );
296 BindResponse resp = ( BindResponse ) bindRequest.getResultResponse();
297
298
299 resp.setServerSaslCreds( tokenBytes );
300
301
302 ldapSession.setAuthPending();
303
304
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
317 ldapSession.clearSaslProperties();
318 ldapSession.setAnonymous();
319
320
321 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
322 }
323 }
324
325
326
327
328
329 private void sendAuthMethNotSupported( LdapSession ldapSession, BindRequest bindRequest )
330 {
331
332
333 ldapSession.clearSaslProperties();
334 ldapSession.setAnonymous();
335
336
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
343 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
344
345 return;
346 }
347
348
349
350
351
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
373 ldapSession.clearSaslProperties();
374 ldapSession.setAnonymous();
375
376
377 ldapSession.getIoSession().write( bindRequest.getResultResponse() );
378 }
379
380
381
382
383
384 private void sendBindSuccess( LdapSession ldapSession, BindRequest bindRequest, byte[] tokenBytes )
385 {
386
387 BindResponse response = ( BindResponse ) bindRequest.getResultResponse();
388 response.getLdapResult().setResultCode( ResultCodeEnum.SUCCESS );
389 response.setServerSaslCreds( tokenBytes );
390
391 if ( ! ldapSession.getCoreSession().isAnonymous() )
392 {
393
394 ldapSession.setAuthenticated();
395 }
396 else
397 {
398
399 ldapSession.setAnonymous();
400 }
401
402
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
419 String saslMechanism = bindRequest.getSaslMechanism();
420
421
422 if ( StringTools.isEmpty( saslMechanism ) ||
423 !ldapSession.getSaslProperty( SaslConstants.SASL_MECH ).equals( saslMechanism ) )
424 {
425 sendAuthMethNotSupported( ldapSession, bindRequest );
426 return;
427 }
428
429
430
431 MechanismHandler mechanismHandler = handlers.get( saslMechanism );
432
433 if ( mechanismHandler == null )
434 {
435 String message = "Handler unavailable for " + saslMechanism;
436
437
438 ldapSession.clearSaslProperties();
439 ldapSession.setAnonymous();
440
441 LOG.error( message );
442 throw new IllegalArgumentException( message );
443 }
444
445
446 SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest );
447
448
449
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
464
465
466
467 ldapSession.putSaslProperty( SaslConstants.SASL_CREDS, tokenBytes );
468 }
469
470
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
478 ldapSession.setCoreSession( userSession );
479
480
481 ldapSession.setAuthenticated();
482
483
484 MechanismHandler handler = (MechanismHandler)ldapSession.getSaslProperty( SaslConstants.SASL_MECH_HANDLER );
485 handler.cleanup( ldapSession );
486
487
488 sendBindSuccess( ldapSession, bindRequest, tokenBytes );
489 }
490 catch ( Exception e )
491 {
492
493
494 LOG.error( "Exception encountered while processing Sasl BindRequest", e );
495 }
496 }
497 }
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526 public void handleSaslAuth( LdapSession ldapSession, BindRequest bindRequest ) throws Exception
527 {
528 String saslMechanism = bindRequest.getSaslMechanism();
529 DirectoryService ds = getLdapServer().getDirectoryService();
530
531
532 if ( ldapSession.isAuthenticated() )
533 {
534
535
536 ldapSession.getCoreSession().unbind();
537
538
539 ldapSession.setAnonymous();
540
541
542 ldapSession.clearSaslProperties();
543
544
545 }
546
547
548 if ( ldapSession.isAnonymous() )
549 {
550 if ( !StringTools.isEmpty( saslMechanism ) )
551 {
552
553 if ( !checkMechanism( ldapSession, saslMechanism ) )
554 {
555
556 sendAuthMethNotSupported( ldapSession, bindRequest );
557
558 return;
559 }
560
561
562 ldapSession.putSaslProperty( SaslConstants.SASL_MECH, saslMechanism );
563
564
565
566 MechanismHandler mechanismHandler = handlers.get( saslMechanism );
567
568
569 ldapSession.putSaslProperty( SaslConstants.SASL_MECH_HANDLER, mechanismHandler );
570
571
572 mechanismHandler.init( ldapSession );
573
574
575 SaslServer ss = mechanismHandler.handleMechanism( ldapSession, bindRequest );
576
577
578 generateSaslChallenge( ldapSession, ss, bindRequest );
579
580
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
602
603
604
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
685
686
687
688
689
690 @Override
691 public void handle( LdapSession ldapSession, BindRequest bindRequest ) throws Exception
692 {
693 LOG.debug( "Received: {}", bindRequest );
694
695
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
707 if ( bindRequest.isSimple() )
708 {
709 handleSimpleAuth( ldapSession, bindRequest );
710 }
711 else
712 {
713 handleSaslAuth( ldapSession, bindRequest );
714 }
715 }
716 }