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.operations.bind;
21
22
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Hashtable;
26 import java.util.Map;
27 import java.util.Set;
28
29 import javax.naming.AuthenticationNotSupportedException;
30 import javax.naming.Context;
31 import javax.naming.NamingEnumeration;
32 import javax.naming.NamingException;
33 import javax.naming.NoPermissionException;
34 import javax.naming.directory.Attribute;
35 import javax.naming.directory.Attributes;
36 import javax.naming.directory.DirContext;
37 import javax.naming.directory.InitialDirContext;
38
39 import org.apache.commons.net.SocketClient;
40 import org.apache.directory.server.core.DefaultDirectoryService;
41 import org.apache.directory.server.core.DirectoryService;
42 import org.apache.directory.server.core.entry.ServerEntry;
43 import org.apache.directory.server.core.integ.IntegrationUtils;
44 import org.apache.directory.server.core.integ.Level;
45 import org.apache.directory.server.core.integ.annotations.ApplyLdifs;
46 import org.apache.directory.server.core.integ.annotations.CleanupLevel;
47 import org.apache.directory.server.core.integ.annotations.Factory;
48 import org.apache.directory.server.core.partition.Partition;
49 import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
50 import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
51 import org.apache.directory.server.integ.LdapServerFactory;
52 import org.apache.directory.server.integ.SiRunner;
53 import org.apache.directory.server.ldap.LdapService;
54 import org.apache.directory.server.ldap.handlers.bind.MechanismHandler;
55 import org.apache.directory.server.ldap.handlers.bind.cramMD5.CramMd5MechanismHandler;
56 import org.apache.directory.server.ldap.handlers.bind.digestMD5.DigestMd5MechanismHandler;
57 import org.apache.directory.server.ldap.handlers.bind.gssapi.GssapiMechanismHandler;
58 import org.apache.directory.server.ldap.handlers.bind.ntlm.NtlmMechanismHandler;
59 import org.apache.directory.server.ldap.handlers.bind.ntlm.NtlmProvider;
60 import org.apache.directory.server.ldap.handlers.bind.plain.PlainMechanismHandler;
61 import org.apache.directory.server.ldap.handlers.extended.StoredProcedureExtendedOperationHandler;
62 import org.apache.directory.server.protocol.shared.SocketAcceptor;
63 import org.apache.directory.server.xdbm.Index;
64 import org.apache.directory.shared.ldap.constants.SupportedSaslMechanisms;
65 import org.apache.directory.shared.ldap.message.BindRequestImpl;
66 import org.apache.directory.shared.ldap.message.BindResponse;
67 import org.apache.directory.shared.ldap.message.MessageDecoder;
68 import org.apache.directory.shared.ldap.message.MessageEncoder;
69 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
70 import org.apache.directory.shared.ldap.message.spi.BinaryAttributeDetector;
71 import org.apache.directory.shared.ldap.name.LdapDN;
72 import org.apache.directory.shared.ldap.util.ArrayUtils;
73 import org.apache.mina.common.IoSession;
74 import org.apache.mina.util.AvailablePortFinder;
75 import org.junit.Before;
76 import org.junit.Test;
77 import org.junit.runner.RunWith;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80
81 import static org.junit.Assert.fail;
82 import static org.junit.Assert.assertTrue;
83 import static org.junit.Assert.assertEquals;
84
85
86
87
88
89
90
91
92 @RunWith ( SiRunner.class )
93 @CleanupLevel ( Level.CLASS )
94 @Factory ( SaslBindIT.Factory.class )
95 @ApplyLdifs( {
96
97 "dn: dc=example,dc=com\n" +
98 "dc: example\n" +
99 "objectClass: top\n" +
100 "objectClass: domain\n\n" +
101
102
103 "dn: ou=users,dc=example,dc=com\n" +
104 "objectClass: organizationalUnit\n" +
105 "objectClass: top\n" +
106 "ou: users\n\n" +
107
108 "dn: uid=hnelson,ou=users,dc=example,dc=com\n" +
109 "objectClass: inetOrgPerson\n" +
110 "objectClass: organizationalPerson\n" +
111 "objectClass: person\n" +
112 "objectClass: top\n" +
113 "uid: hnelson\n" +
114 "userPassword: secret\n" +
115 "cn: Horatio Nelson\n" +
116 "sn: Nelson\n\n"
117 }
118 )
119 public class SaslBindIT
120 {
121 public static LdapService ldapService;
122 public BogusNtlmProvider provider = new BogusNtlmProvider();
123
124
125 public static class Factory implements LdapServerFactory
126 {
127 public LdapService newInstance() throws Exception
128 {
129 DirectoryService service = new DefaultDirectoryService();
130 IntegrationUtils.doDelete( service.getWorkingDirectory() );
131 service.getChangeLog().setEnabled( true );
132 service.setAllowAnonymousAccess( false );
133 service.setShutdownHookEnabled( false );
134
135 Set<Partition> partitions = new HashSet<Partition>();
136 JdbmPartition partition = new JdbmPartition();
137 partition.setId( "example" );
138 partition.setSuffix( "dc=example,dc=com" );
139
140 Set<Index<?,ServerEntry>> indexedAttrs = new HashSet<Index<?,ServerEntry>>();
141 indexedAttrs.add( new JdbmIndex<String,ServerEntry>( "ou" ) );
142 indexedAttrs.add( new JdbmIndex<String,ServerEntry>( "dc" ) );
143 indexedAttrs.add( new JdbmIndex<String,ServerEntry>( "objectClass" ) );
144 partition.setIndexedAttributes( indexedAttrs );
145
146 partitions.add( partition );
147 service.setPartitions( partitions );
148
149
150
151
152
153 LdapService ldapService = new LdapService();
154 ldapService.setDirectoryService( service );
155 ldapService.setSocketAcceptor( new SocketAcceptor( null ) );
156 ldapService.setIpPort( AvailablePortFinder.getNextAvailable( 1024 ) );
157 ldapService.setAllowAnonymousAccess( false );
158 ldapService.addExtendedOperationHandler( new StoredProcedureExtendedOperationHandler() );
159
160
161
162 Map<String, MechanismHandler> mechanismHandlerMap = new HashMap<String,MechanismHandler>();
163 mechanismHandlerMap.put( SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler() );
164
165 CramMd5MechanismHandler cramMd5MechanismHandler = new CramMd5MechanismHandler();
166 mechanismHandlerMap.put( SupportedSaslMechanisms.CRAM_MD5, cramMd5MechanismHandler );
167
168 DigestMd5MechanismHandler digestMd5MechanismHandler = new DigestMd5MechanismHandler();
169 mechanismHandlerMap.put( SupportedSaslMechanisms.DIGEST_MD5, digestMd5MechanismHandler );
170
171 GssapiMechanismHandler gssapiMechanismHandler = new GssapiMechanismHandler();
172 mechanismHandlerMap.put( SupportedSaslMechanisms.GSSAPI, gssapiMechanismHandler );
173
174 NtlmMechanismHandler ntlmMechanismHandler = new NtlmMechanismHandler();
175 mechanismHandlerMap.put( SupportedSaslMechanisms.NTLM, ntlmMechanismHandler );
176 mechanismHandlerMap.put( SupportedSaslMechanisms.GSS_SPNEGO, ntlmMechanismHandler );
177
178 ldapService.setSaslMechanismHandlers( mechanismHandlerMap );
179 ldapService.setSaslHost( "localhost" );
180
181 return ldapService;
182 }
183 }
184
185
186 @Before
187 public void setupNewNtlmProvider()
188 {
189 provider = new BogusNtlmProvider();
190 NtlmMechanismHandler handler = ( NtlmMechanismHandler )
191 ldapService.getSaslMechanismHandlers().get( SupportedSaslMechanisms.NTLM );
192 handler.setNtlmProvider( provider );
193 }
194
195
196
197
198
199 @Test
200 public void testSupportedSASLMechanisms()
201 {
202 try
203 {
204
205
206 ldapService.setAllowAnonymousAccess( true );
207 ldapService.getDirectoryService().setAllowAnonymousAccess( true );
208
209
210 DirContext context = new InitialDirContext();
211
212 Attributes attrs = context.getAttributes( "ldap://localhost:"
213 + ldapService.getIpPort(), new String[]
214 { "supportedSASLMechanisms" } );
215
216 NamingEnumeration<? extends Attribute> answer = attrs.getAll();
217 Attribute result = answer.next();
218 assertEquals( 6, result.size() );
219 assertTrue( result.contains( SupportedSaslMechanisms.GSSAPI ) );
220 assertTrue( result.contains( SupportedSaslMechanisms.DIGEST_MD5 ) );
221 assertTrue( result.contains( SupportedSaslMechanisms.CRAM_MD5 ) );
222 assertTrue( result.contains( SupportedSaslMechanisms.NTLM ) );
223 assertTrue( result.contains( SupportedSaslMechanisms.PLAIN ) );
224 assertTrue( result.contains( SupportedSaslMechanisms.GSS_SPNEGO ) );
225 }
226 catch ( NamingException e )
227 {
228 fail( "Should not have caught exception." );
229 }
230 }
231
232
233
234
235
236 @Test
237 public void testSaslBindPLAIN()
238 {
239 try
240 {
241 Hashtable<String, String> env = new Hashtable<String, String>();
242 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
243 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
244
245 env.put( Context.SECURITY_AUTHENTICATION, "PLAIN" );
246 env.put( Context.SECURITY_PRINCIPAL, "uid=hnelson,ou=users,dc=example,dc=com" );
247 env.put( Context.SECURITY_CREDENTIALS, "secret" );
248
249 DirContext context = new InitialDirContext( env );
250
251 String[] attrIDs =
252 { "uid" };
253
254 Attributes attrs = context.getAttributes( "uid=hnelson,ou=users,dc=example,dc=com", attrIDs );
255 String uid = null;
256
257 if ( attrs.get( "uid" ) != null )
258 {
259 uid = ( String ) attrs.get( "uid" ).get();
260 }
261
262 assertEquals( uid, "hnelson" );
263 }
264 catch ( NamingException e )
265 {
266 fail( "Should not have caught exception." );
267 }
268 }
269
270
271
272
273
274 @Test
275 public void testSaslBindNoMech()
276 {
277 try
278 {
279 Hashtable<String, String> env = new Hashtable<String, String>();
280 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
281 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
282
283 env.put( Context.SECURITY_AUTHENTICATION, "" );
284 env.put( Context.SECURITY_PRINCIPAL, "uid=hnelson,ou=users,dc=example,dc=com" );
285 env.put( Context.SECURITY_CREDENTIALS, "secret" );
286
287 new InitialDirContext( env );
288 fail( "Should not be there" );
289 }
290 catch ( AuthenticationNotSupportedException anse )
291 {
292 assertTrue( true );
293 }
294 catch ( NamingException ne )
295 {
296 fail( "Should not have caught exception." );
297 }
298 }
299
300
301
302
303
304 @Test
305 public void testAnonymousBelowRootDSE()
306 {
307 ldapService.getDirectoryService().setAllowAnonymousAccess( false );
308
309 try
310 {
311 Hashtable<String, String> env = new Hashtable<String, String>();
312 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
313 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
314
315 DirContext context = new InitialDirContext( env );
316
317 String[] attrIDs =
318 { "vendorName" };
319
320 context.getAttributes( "dc=example,dc=com", attrIDs );
321
322 fail( "Should not have gotten here." );
323 }
324 catch ( NoPermissionException npe )
325 {
326 assertTrue( npe.getMessage().contains( "error code 50" ) );
327 }
328 catch ( NamingException ne )
329 {
330 fail( "Should not have gotten here" );
331 }
332 }
333
334
335
336
337
338 @Test
339 public void testSaslCramMd5Bind()
340 {
341 try
342 {
343 Hashtable<String, String> env = new Hashtable<String, String>();
344 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
345 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
346
347 env.put( Context.SECURITY_AUTHENTICATION, "CRAM-MD5" );
348 env.put( Context.SECURITY_PRINCIPAL, "hnelson" );
349 env.put( Context.SECURITY_CREDENTIALS, "secret" );
350
351 DirContext context = new InitialDirContext( env );
352
353 String[] attrIDs =
354 { "uid" };
355
356 Attributes attrs = context.getAttributes( "uid=hnelson,ou=users,dc=example,dc=com", attrIDs );
357
358 String uid = null;
359
360 if ( attrs.get( "uid" ) != null )
361 {
362 uid = ( String ) attrs.get( "uid" ).get();
363 }
364
365 assertEquals( uid, "hnelson" );
366 }
367 catch ( NamingException e )
368 {
369 fail( "Should not have caught exception." );
370 }
371 }
372
373
374
375
376
377 @Test
378 public void testSaslCramMd5BindBadPassword()
379 {
380 try
381 {
382 Hashtable<String, String> env = new Hashtable<String, String>();
383 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
384 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
385
386 env.put( Context.SECURITY_AUTHENTICATION, "CRAM-MD5" );
387 env.put( Context.SECURITY_PRINCIPAL, "hnelson" );
388 env.put( Context.SECURITY_CREDENTIALS, "badsecret" );
389
390 DirContext context = new InitialDirContext( env );
391
392 String[] attrIDs =
393 { "uid" };
394
395 context.getAttributes( "uid=hnelson,ou=users,dc=example,dc=com", attrIDs );
396
397 fail( "Should have thrown exception." );
398 }
399 catch ( NamingException e )
400 {
401 assertTrue( e.getMessage().contains( "Invalid response" ) );
402 }
403 }
404
405
406
407
408
409 @Test
410 public void testSaslDigestMd5Bind() throws Exception
411 {
412 Hashtable<String, String> env = new Hashtable<String, String>();
413 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
414 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
415
416 env.put( Context.SECURITY_AUTHENTICATION, "DIGEST-MD5" );
417 env.put( Context.SECURITY_PRINCIPAL, "hnelson" );
418 env.put( Context.SECURITY_CREDENTIALS, "secret" );
419
420
421 env.put( "java.naming.security.sasl.realm", "example.com" );
422
423
424 env.put( "javax.security.sasl.qop", "auth-conf" );
425
426 DirContext context = new InitialDirContext( env );
427
428 String[] attrIDs =
429 { "uid" };
430
431 Attributes attrs = context.getAttributes( "uid=hnelson,ou=users,dc=example,dc=com", attrIDs );
432
433 String uid = null;
434
435 if ( attrs.get( "uid" ) != null )
436 {
437 uid = ( String ) attrs.get( "uid" ).get();
438 }
439
440 assertEquals( uid, "hnelson" );
441 }
442
443
444
445
446
447 @Test
448 public void testSaslDigestMd5BindBadRealm()
449 {
450 try
451 {
452 Hashtable<String, String> env = new Hashtable<String, String>();
453 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
454 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
455
456 env.put( Context.SECURITY_AUTHENTICATION, "DIGEST-MD5" );
457 env.put( Context.SECURITY_PRINCIPAL, "hnelson" );
458 env.put( Context.SECURITY_CREDENTIALS, "secret" );
459
460
461 env.put( "java.naming.security.sasl.realm", "badrealm.com" );
462
463
464 env.put( "javax.security.sasl.qop", "auth-conf" );
465
466 DirContext context = new InitialDirContext( env );
467
468 String[] attrIDs =
469 { "uid" };
470
471 context.getAttributes( "uid=hnelson,ou=users,dc=example,dc=com", attrIDs );
472
473 fail( "Should have thrown exception." );
474 }
475 catch ( NamingException e )
476 {
477 assertTrue( e.getMessage().contains( "Nonexistent realm" ) );
478 }
479 }
480
481
482
483
484
485 @Test
486 public void testSaslDigestMd5BindBadPassword()
487 {
488 try
489 {
490 Hashtable<String, String> env = new Hashtable<String, String>();
491 env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
492 env.put( Context.PROVIDER_URL, "ldap://localhost:" + ldapService.getIpPort() );
493
494 env.put( Context.SECURITY_AUTHENTICATION, "DIGEST-MD5" );
495 env.put( Context.SECURITY_PRINCIPAL, "hnelson" );
496 env.put( Context.SECURITY_CREDENTIALS, "badsecret" );
497
498 DirContext context = new InitialDirContext( env );
499 String[] attrIDs = { "uid" };
500
501 context.getAttributes( "uid=hnelson,ou=users,dc=example,dc=com", attrIDs );
502 fail( "Should have thrown exception." );
503 }
504 catch ( NamingException e )
505 {
506 assertTrue( e.getMessage().contains( "digest response format violation" ) );
507 }
508 }
509
510
511
512
513
514 @Test
515 public void testNtlmBind() throws Exception
516 {
517 NtlmSaslBindClient client = new NtlmSaslBindClient( SupportedSaslMechanisms.NTLM );
518 BindResponse type2response = client.bindType1( "type1_test".getBytes() );
519 assertEquals( 1, type2response.getMessageId() );
520 assertEquals( ResultCodeEnum.SASL_BIND_IN_PROGRESS, type2response.getLdapResult().getResultCode() );
521 assertTrue( ArrayUtils.isEquals( "type1_test".getBytes(), provider.getType1Response() ) );
522 assertTrue( ArrayUtils.isEquals( "challenge".getBytes(), type2response.getServerSaslCreds() ) );
523
524 BindResponse finalResponse = client.bindType3( "type3_test".getBytes() );
525 assertEquals( 2, finalResponse.getMessageId() );
526 assertEquals( ResultCodeEnum.SUCCESS, finalResponse.getLdapResult().getResultCode() );
527 assertTrue( ArrayUtils.isEquals( "type3_test".getBytes(), provider.getType3Response() ) );
528 }
529
530
531
532
533
534 @Test
535 public void testGssSpnegoBind() throws Exception
536 {
537 NtlmSaslBindClient client = new NtlmSaslBindClient( SupportedSaslMechanisms.GSS_SPNEGO );
538 BindResponse type2response = client.bindType1( "type1_test".getBytes() );
539 assertEquals( 1, type2response.getMessageId() );
540 assertEquals( ResultCodeEnum.SASL_BIND_IN_PROGRESS, type2response.getLdapResult().getResultCode() );
541 assertTrue( ArrayUtils.isEquals( "type1_test".getBytes(), provider.getType1Response() ) );
542 assertTrue( ArrayUtils.isEquals( "challenge".getBytes(), type2response.getServerSaslCreds() ) );
543
544 BindResponse finalResponse = client.bindType3( "type3_test".getBytes() );
545 assertEquals( 2, finalResponse.getMessageId() );
546 assertEquals( ResultCodeEnum.SUCCESS, finalResponse.getLdapResult().getResultCode() );
547 assertTrue( ArrayUtils.isEquals( "type3_test".getBytes(), provider.getType3Response() ) );
548 }
549
550
551
552
553
554
555 class BogusNtlmProvider implements NtlmProvider
556 {
557 private byte[] type1response;
558 private byte[] type3response;
559
560
561 public boolean authenticate( IoSession session, byte[] type3response ) throws Exception
562 {
563 this.type3response = type3response;
564 return true;
565 }
566
567
568 public byte[] generateChallenge( IoSession session, byte[] type1reponse ) throws Exception
569 {
570 this.type1response = type1reponse;
571 return "challenge".getBytes();
572 }
573
574
575 public byte[] getType1Response()
576 {
577 return type1response;
578 }
579
580
581 public byte[] getType3Response()
582 {
583 return type3response;
584 }
585 }
586
587
588
589
590
591 class NtlmSaslBindClient extends SocketClient
592 {
593 private final Logger LOG = LoggerFactory.getLogger( NtlmSaslBindClient.class );
594
595 private final String mechanism;
596
597
598 NtlmSaslBindClient( String mechanism ) throws Exception
599 {
600 this.mechanism = mechanism;
601 setDefaultPort( ldapService.getIpPort() );
602 connect( "localhost", ldapService.getIpPort() );
603 setTcpNoDelay( false );
604
605 LOG.debug( "isConnected() = {}", _isConnected_ );
606 LOG.debug( "LocalPort = {}", getLocalPort() );
607 LOG.debug( "LocalAddress = {}", getLocalAddress() );
608 LOG.debug( "RemotePort = {}", getRemotePort() );
609 LOG.debug( "RemoteAddress = {}", getRemoteAddress() );
610 }
611
612
613 BindResponse bindType1( byte[] type1response ) throws Exception
614 {
615 if ( ! isConnected() )
616 {
617 throw new IllegalStateException( "Client is not connected." );
618 }
619
620
621 BindRequestImpl request = new BindRequestImpl( 1 ) ;
622 request.setName( new LdapDN( "uid=admin,ou=system" ) ) ;
623 request.setSimple( false ) ;
624 request.setCredentials( type1response ) ;
625 request.setSaslMechanism( mechanism );
626 request.setVersion3( true ) ;
627
628
629 MessageEncoder encoder = new MessageEncoder();
630 MessageDecoder decoder = new MessageDecoder( new BinaryAttributeDetector() {
631 public boolean isBinary( String attributeId )
632 {
633 return false;
634 }
635 } );
636
637
638 encoder.encodeBlocking( null, _output_, request );
639 _output_.flush();
640
641 while ( _input_.available() <= 0 )
642 {
643 Thread.sleep( 100 );
644 }
645
646
647 return ( BindResponse ) decoder.decode( null, _input_ );
648 }
649
650
651 BindResponse bindType3( byte[] type3response ) throws Exception
652 {
653 if ( ! isConnected() )
654 {
655 throw new IllegalStateException( "Client is not connected." );
656 }
657
658
659 BindRequestImpl request = new BindRequestImpl( 2 ) ;
660 request.setName( new LdapDN( "uid=admin,ou=system" ) ) ;
661 request.setSimple( false ) ;
662 request.setCredentials( type3response ) ;
663 request.setSaslMechanism( mechanism );
664 request.setVersion3( true ) ;
665
666
667 MessageEncoder encoder = new MessageEncoder();
668 MessageDecoder decoder = new MessageDecoder( new BinaryAttributeDetector() {
669 public boolean isBinary( String attributeId )
670 {
671 return false;
672 }
673 } );
674
675
676 encoder.encodeBlocking( null, _output_, request );
677
678 _output_.flush();
679
680 while ( _input_.available() <= 0 )
681 {
682 Thread.sleep( 100 );
683 }
684
685
686 return ( BindResponse ) decoder.decode( null, _input_ );
687 }
688 }
689 }