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  package org.apache.directory.server.factory;
20  
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.lang.annotation.Annotation;
25  import java.lang.reflect.Method;
26  import java.net.ServerSocket;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.List;
30  
31  import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
32  import org.apache.directory.api.util.Network;
33  import org.apache.directory.api.util.Strings;
34  import org.apache.directory.server.annotations.CreateChngPwdServer;
35  import org.apache.directory.server.annotations.CreateConsumer;
36  import org.apache.directory.server.annotations.CreateKdcServer;
37  import org.apache.directory.server.annotations.CreateLdapServer;
38  import org.apache.directory.server.annotations.CreateTransport;
39  import org.apache.directory.server.annotations.SaslMechanism;
40  import org.apache.directory.server.core.annotations.AnnotationUtils;
41  import org.apache.directory.server.core.api.DirectoryService;
42  import org.apache.directory.server.core.security.CertificateUtil;
43  import org.apache.directory.server.i18n.I18n;
44  import org.apache.directory.server.kerberos.ChangePasswordConfig;
45  import org.apache.directory.server.kerberos.KerberosConfig;
46  import org.apache.directory.server.kerberos.changepwd.ChangePasswordServer;
47  import org.apache.directory.server.kerberos.kdc.KdcServer;
48  import org.apache.directory.server.ldap.ExtendedOperationHandler;
49  import org.apache.directory.server.ldap.LdapServer;
50  import org.apache.directory.server.ldap.handlers.sasl.MechanismHandler;
51  import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler;
52  import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmProvider;
53  import org.apache.directory.server.ldap.replication.SyncReplConfiguration;
54  import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumer;
55  import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumerImpl;
56  import org.apache.directory.server.protocol.shared.transport.TcpTransport;
57  import org.apache.directory.server.protocol.shared.transport.Transport;
58  import org.apache.directory.server.protocol.shared.transport.UdpTransport;
59  import org.junit.runner.Description;
60  
61  
62  /**
63   * 
64   * Annotation processor for creating LDAP and Kerberos servers.
65   *
66   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
67   */
68  public final class ServerAnnotationProcessor
69  {
70      private ServerAnnotationProcessor()
71      {
72      }
73  
74  
75      private static void createTransports( LdapServer ldapServer, CreateTransport[] transportBuilders )
76      {
77          if ( transportBuilders.length != 0 )
78          {
79              for ( CreateTransport transportBuilder : transportBuilders )
80              {
81                  List< Transport > transports = createTransports( transportBuilder );
82                  
83                  for ( Transport t : transports )
84                  {
85                      ldapServer.addTransports( t );
86                  }
87              }
88          }
89          else
90          {
91              // Create default LDAP and LDAPS transports
92              try 
93              {
94                  int port = getFreePort();
95                  Transport ldap = new TcpTransport( port );
96                  ldapServer.addTransports( ldap );
97              }
98              catch ( IOException ioe )
99              {
100                 // Don't know what to do here...
101             }
102 
103             try 
104             {
105                 int port = getFreePort();
106                 Transport ldaps = new TcpTransport( port );
107                 ldaps.setEnableSSL( true );
108                 ldapServer.addTransports( ldaps );
109             }
110             catch ( IOException ioe )
111             {
112                 // Don't know what to do here...
113             }
114         }
115     }
116 
117 
118     /**
119      * Just gives an instance of {@link LdapServer} without starting it.
120      * For getting a running LdapServer instance see {@link #createLdapServer(CreateLdapServer, DirectoryService)}
121      * @see #createLdapServer(CreateLdapServer, DirectoryService)
122      * 
123      * @param createLdapServer The LdapServer to create
124      * @param directoryService the directory service
125      * @return The created LdapServer
126      */
127     public static LdapServer instantiateLdapServer( CreateLdapServer createLdapServer, DirectoryService directoryService )
128     {
129         if ( createLdapServer != null )
130         {
131             LdapServerLdapServer.html#LdapServer">LdapServer ldapServer = new LdapServer();
132 
133             ldapServer.setServiceName( createLdapServer.name() );
134 
135             // Read the transports
136             createTransports( ldapServer, createLdapServer.transports() );
137 
138             // Associate the DS to this LdapServer
139             ldapServer.setDirectoryService( directoryService );
140 
141             // Propagate the anonymous flag to the DS
142             directoryService.setAllowAnonymousAccess( createLdapServer.allowAnonymousAccess() );
143 
144             ldapServer.setSaslHost( createLdapServer.saslHost() );
145 
146             ldapServer.setSaslPrincipal( createLdapServer.saslPrincipal() );
147 
148             if ( !Strings.isEmpty( createLdapServer.keyStore() ) )
149             {
150                 ldapServer.setKeystoreFile( createLdapServer.keyStore() );
151                 ldapServer.setCertificatePassword( createLdapServer.certificatePassword() );
152             }
153             else
154             {
155                 try
156                 {
157                     // Create a temporary keystore, be sure to remove it when exiting the test
158                     File keyStoreFile = CertificateUtil.createTempKeyStore( "testStore", "secret".toCharArray() );
159                     ldapServer.setKeystoreFile( keyStoreFile.getAbsolutePath() );
160                     ldapServer.setCertificatePassword( "secret" );
161                 }
162                 catch ( Exception e )
163                 {
164                     
165                 }
166             }
167 
168             for ( Class<?> extOpClass : createLdapServer.extendedOpHandlers() )
169             {
170                 try
171                 {
172                     ExtendedOperationHandler/directory/server/ldap/ExtendedOperationHandler.html#ExtendedOperationHandler">ExtendedOperationHandler extOpHandler = ( ExtendedOperationHandler ) extOpClass.newInstance();
173                     ldapServer.addExtendedOperationHandler( extOpHandler );
174                 }
175                 catch ( Exception e )
176                 {
177                     throw new RuntimeException( I18n.err( I18n.ERR_690, extOpClass.getName() ), e );
178                 }
179             }
180 
181             for ( SaslMechanism saslMech : createLdapServer.saslMechanisms() )
182             {
183                 try
184                 {
185                     MechanismHandler../org/apache/directory/server/ldap/handlers/sasl/MechanismHandler.html#MechanismHandler">MechanismHandler handler = ( MechanismHandler ) saslMech.implClass().newInstance();
186                     ldapServer.addSaslMechanismHandler( saslMech.name(), handler );
187                 }
188                 catch ( Exception e )
189                 {
190                     throw new RuntimeException(
191                         I18n.err( I18n.ERR_691, saslMech.name(), saslMech.implClass().getName() ), e );
192                 }
193             }
194 
195             NtlmMechanismHandlerpache/directory/server/ldap/handlers/sasl/ntlm/NtlmMechanismHandler.html#NtlmMechanismHandler">NtlmMechanismHandler ntlmHandler = ( NtlmMechanismHandler ) ldapServer.getSaslMechanismHandlers().get(
196                 SupportedSaslMechanisms.NTLM );
197 
198             if ( ntlmHandler != null )
199             {
200                 Class<?> ntlmProviderClass = createLdapServer.ntlmProvider();
201                 // default value is a invalid Object.class
202                 if ( ( ntlmProviderClass != null ) && ( ntlmProviderClass != Object.class ) )
203                 {
204                     try
205                     {
206                         ntlmHandler.setNtlmProvider( ( NtlmProvider ) ntlmProviderClass.newInstance() );
207                     }
208                     catch ( Exception e )
209                     {
210                         throw new RuntimeException( I18n.err( I18n.ERR_692 ), e );
211                     }
212                 }
213             }
214 
215             List<String> realms = new ArrayList<>();
216             for ( String s : createLdapServer.saslRealms() )
217             {
218                 realms.add( s );
219             }
220 
221             ldapServer.setSaslRealms( realms );
222 
223             return ldapServer;
224         }
225         else
226         {
227             return null;
228         }
229     }
230 
231 
232     /**
233      * Returns an LdapServer instance and starts it before returning the instance, infering
234      * the configuration from the Stack trace
235      *  
236      * @param directoryService the directory service
237      * @return a running LdapServer instance
238      * @throws ClassNotFoundException If the CreateLdapServer class cannot be loaded
239      */
240     public static LdapServer getLdapServer( DirectoryService directoryService ) throws ClassNotFoundException
241     {
242         Object instance = AnnotationUtils.getInstance( CreateLdapServer.class );
243         LdapServer ldapServer = null;
244 
245         if ( instance != null )
246         {
247             CreateLdapServer createLdapServer = ( CreateLdapServer ) instance;
248 
249             ldapServer = createLdapServer( createLdapServer, directoryService );
250         }
251 
252         return ldapServer;
253     }
254 
255 
256     /**
257      * Create a replication consumer
258      */
259     private static ReplicationConsumer createConsumer( CreateConsumer createConsumer )
260     {
261         ReplicationConsumer consumer = new ReplicationConsumerImpl();
262 
263         SyncReplConfigurationtion/SyncReplConfiguration.html#SyncReplConfiguration">SyncReplConfiguration config = new SyncReplConfiguration();
264         
265         String remoteHost = createConsumer.remoteHost();
266         
267         if ( Strings.isEmpty( remoteHost ) )
268         {
269             remoteHost = Network.LOOPBACK_HOSTNAME;
270         }
271         
272         config.setRemoteHost( remoteHost );
273         config.setRemotePort( createConsumer.remotePort() );
274         config.setReplUserDn( createConsumer.replUserDn() );
275         config.setReplUserPassword( Strings.getBytesUtf8( createConsumer.replUserPassword() ) );
276         config.setUseTls( createConsumer.useTls() );
277         config.setBaseDn( createConsumer.baseDn() );
278         config.setRefreshInterval( createConsumer.refreshInterval() );
279 
280         consumer.setConfig( config );
281 
282         return consumer;
283     }
284 
285 
286     /**
287      * creates an LdapServer and starts before returning the instance, infering
288      * the configuration from the Stack trace
289      *  
290      * @return a running LdapServer instance
291      * @throws ClassNotFoundException If the CreateConsumer class cannot be loaded
292      */
293     public static ReplicationConsumer createConsumer() throws ClassNotFoundException
294     {
295         Object instance = AnnotationUtils.getInstance( CreateConsumer.class );
296         ReplicationConsumer consumer = null;
297 
298         if ( instance != null )
299         {
300             CreateConsumer createConsumer = ( CreateConsumer ) instance;
301 
302             consumer = createConsumer( createConsumer );
303         }
304 
305         return consumer;
306     }
307 
308 
309     /**
310      * creates an LdapServer and starts before returning the instance
311      *  
312      * @param createLdapServer the annotation containing the custom configuration
313      * @param directoryService the directory service
314      * @return a running LdapServer instance
315      */
316     private static LdapServer createLdapServer( CreateLdapServer createLdapServer, DirectoryService directoryService )
317     {
318         LdapServer ldapServer = instantiateLdapServer( createLdapServer, directoryService );
319 
320         if ( ldapServer == null )
321         {
322             return null;
323         }
324 
325         // Launch the server
326         try
327         {
328             ldapServer.start();
329         }
330         catch ( Exception e )
331         {
332             e.printStackTrace();
333         }
334 
335         return ldapServer;
336     }
337 
338 
339     /**
340      * Create a new instance of LdapServer
341      *
342      * @param description A description for the created LdapServer
343      * @param directoryService The associated DirectoryService
344      * @return An LdapServer instance 
345      */
346     public static LdapServer createLdapServer( Description description, DirectoryService directoryService )
347     {
348         CreateLdapServer createLdapServer = description.getAnnotation( CreateLdapServer.class );
349 
350         // Ok, we have found a CreateLdapServer annotation. Process it now.
351         return createLdapServer( createLdapServer, directoryService );
352     }
353 
354 
355     @SuppressWarnings("unchecked")
356     private static Annotation getAnnotation( Class annotationClass ) throws Exception
357     {
358         // Get the caller by inspecting the stackTrace
359         StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
360 
361         // In Java5 the 0th stacktrace element is: java.lang.Thread.dumpThreads(Native Method)
362         int index = stackTrace[0].getMethodName().equals( "dumpThreads" ) ? 4 : 3;
363 
364         // Get the enclosing class
365         Class<?> classCaller = Class.forName( stackTrace[index].getClassName() );
366 
367         // Get the current method
368         String methodCaller = stackTrace[index].getMethodName();
369 
370         // Check if we have any annotation associated with the method
371         Method[] methods = classCaller.getMethods();
372 
373         for ( Method method : methods )
374         {
375             if ( methodCaller.equals( method.getName() ) )
376             {
377                 Annotation annotation = method.getAnnotation( annotationClass );
378 
379                 if ( annotation != null )
380                 {
381                     return annotation;
382                 }
383             }
384         }
385 
386         // No : look at the class level
387         return classCaller.getAnnotation( annotationClass );
388     }
389 
390 
391     public static KdcServer getKdcServer( DirectoryService directoryService, int startPort ) throws Exception
392     {
393         CreateKdcServer createKdcServer = ( CreateKdcServer ) getAnnotation( CreateKdcServer.class );
394         
395         return createKdcServer( createKdcServer, directoryService );
396     }
397 
398 
399     private static KdcServer createKdcServer( CreateKdcServer createKdcServer, DirectoryService directoryService )
400     {
401         if ( createKdcServer == null )
402         {
403             return null;
404         }
405 
406         KerberosConfig/KerberosConfig.html#KerberosConfig">KerberosConfig kdcConfig = new KerberosConfig();
407         kdcConfig.setServicePrincipal( createKdcServer.kdcPrincipal() );
408         kdcConfig.setPrimaryRealm( createKdcServer.primaryRealm() );
409         kdcConfig.setMaximumTicketLifetime( createKdcServer.maxTicketLifetime() );
410         kdcConfig.setMaximumRenewableLifetime( createKdcServer.maxRenewableLifetime() );
411 
412         KdcServerberos/kdc/KdcServer.html#KdcServer">KdcServer kdcServer = new KdcServer( kdcConfig );
413 
414         kdcServer.setSearchBaseDn( createKdcServer.searchBaseDn() );
415 
416         CreateTransport[] transportBuilders = createKdcServer.transports();
417 
418         if ( transportBuilders == null )
419         {
420             // create only UDP transport if none specified
421             int port = 0;
422             try
423             {
424                 port = getFreePort();
425             }
426             catch ( IOException ioe )
427             {
428                 // Don't know what to do here...
429             }
430             UdpTransported/transport/UdpTransport.html#UdpTransport">UdpTransport defaultTransport = new UdpTransport( port );
431             kdcServer.addTransports( defaultTransport );
432         }
433         else if ( transportBuilders.length > 0 )
434         {
435             for ( CreateTransport transportBuilder : transportBuilders )
436             {
437                 List< Transport > transports = createTransports( transportBuilder );
438                 for ( Transport t : transports )
439                 {
440                     kdcServer.addTransports( t );
441                 }
442             }
443         }
444 
445         CreateChngPwdServer[] createChngPwdServers = createKdcServer.chngPwdServer();
446 
447         if ( createChngPwdServers.length > 0 )
448         {
449 
450             CreateChngPwdServer createChngPwdServer = createChngPwdServers[0];
451             ChangePasswordConfigangePasswordConfig.html#ChangePasswordConfig">ChangePasswordConfig config = new ChangePasswordConfig( kdcConfig );
452             config.setServicePrincipal( createChngPwdServer.srvPrincipal() );
453 
454             ChangePasswordServer/ChangePasswordServer.html#ChangePasswordServer">ChangePasswordServer chngPwdServer = new ChangePasswordServer( config );
455 
456             for ( CreateTransport transportBuilder : createChngPwdServer.transports() )
457             {
458                 List< Transport > transports = createTransports( transportBuilder );
459                 for ( Transport t : transports )
460                 {
461                     chngPwdServer.addTransports( t );
462                 }
463             }
464 
465             chngPwdServer.setDirectoryService( directoryService );
466 
467             kdcServer.setChangePwdServer( chngPwdServer );
468         }
469 
470         kdcServer.setDirectoryService( directoryService );
471 
472         // Launch the server
473         try
474         {
475             kdcServer.start();
476         }
477         catch ( Exception e )
478         {
479             e.printStackTrace();
480         }
481 
482         return kdcServer;
483     }
484 
485 
486     private static List< Transport > createTransports( CreateTransport transportBuilder )
487     {
488         String protocol = transportBuilder.protocol();
489         int port = transportBuilder.port();
490         int nbThreads = transportBuilder.nbThreads();
491         int backlog = transportBuilder.backlog();
492         String address = transportBuilder.address();
493         boolean clientAuth = transportBuilder.clientAuth();
494         
495         if ( Strings.isEmpty( address ) )
496         {
497             address = Network.LOOPBACK_HOSTNAME;
498         }
499 
500         if ( port <= 0 )
501         {
502             try
503             {
504                 port = getFreePort();
505             }
506             catch ( IOException ioe )
507             {
508                 // Don't know what to do here...
509             }
510         }
511 
512         if ( protocol.equalsIgnoreCase( "TCP" ) || protocol.equalsIgnoreCase( "LDAP" ) )
513         {
514             Transport tcp = new TcpTransport( address, port, nbThreads, backlog );
515             return Collections.singletonList( tcp );
516         }
517         else if ( protocol.equalsIgnoreCase( "LDAPS" ) )
518         {
519             Transport tcp = new TcpTransport( address, port, nbThreads, backlog );
520             tcp.setEnableSSL( true );
521             ( ( TcpTransport ) tcp ).setWantClientAuth( clientAuth );
522             return Collections.singletonList( tcp );
523         }
524         else if ( protocol.equalsIgnoreCase( "UDP" ) )
525         {
526             Transport udp = new UdpTransport( address, port );
527             return Collections.singletonList( udp );
528         }
529         else if ( protocol.equalsIgnoreCase( "KRB" ) || protocol.equalsIgnoreCase( "CPW" ) )
530         {
531             Transport tcp = new TcpTransport( address, port, nbThreads, backlog );
532             List< Transport > transports = new ArrayList<>();
533             transports.add( tcp );
534             
535             Transport udp = new UdpTransport( address, port );
536             transports.add( udp );
537             return transports;
538         }
539         
540         throw new IllegalArgumentException( I18n.err( I18n.ERR_689, protocol ) );
541     }
542 
543 
544     private static int getFreePort() throws IOException
545     {
546         ServerSocket ss = new ServerSocket( 0 );
547         int port = ss.getLocalPort();
548         ss.close();
549         
550         return port;
551     }
552 
553 
554     public static KdcServer getKdcServer( Description description, DirectoryService directoryService, int startPort )
555     {
556         CreateKdcServer createLdapServer = description.getAnnotation( CreateKdcServer.class );
557 
558         return createKdcServer( createLdapServer, directoryService );
559     }
560 }