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 */
019package org.apache.directory.server.factory;
020
021
022import java.io.File;
023import java.io.IOException;
024import java.lang.annotation.Annotation;
025import java.lang.reflect.Method;
026import java.net.ServerSocket;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.List;
030
031import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
032import org.apache.directory.api.util.Network;
033import org.apache.directory.api.util.Strings;
034import org.apache.directory.server.annotations.CreateChngPwdServer;
035import org.apache.directory.server.annotations.CreateConsumer;
036import org.apache.directory.server.annotations.CreateKdcServer;
037import org.apache.directory.server.annotations.CreateLdapServer;
038import org.apache.directory.server.annotations.CreateTransport;
039import org.apache.directory.server.annotations.SaslMechanism;
040import org.apache.directory.server.core.annotations.AnnotationUtils;
041import org.apache.directory.server.core.api.DirectoryService;
042import org.apache.directory.server.core.security.CertificateUtil;
043import org.apache.directory.server.i18n.I18n;
044import org.apache.directory.server.kerberos.ChangePasswordConfig;
045import org.apache.directory.server.kerberos.KerberosConfig;
046import org.apache.directory.server.kerberos.changepwd.ChangePasswordServer;
047import org.apache.directory.server.kerberos.kdc.KdcServer;
048import org.apache.directory.server.ldap.ExtendedOperationHandler;
049import org.apache.directory.server.ldap.LdapServer;
050import org.apache.directory.server.ldap.handlers.sasl.MechanismHandler;
051import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler;
052import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmProvider;
053import org.apache.directory.server.ldap.replication.SyncReplConfiguration;
054import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumer;
055import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumerImpl;
056import org.apache.directory.server.protocol.shared.transport.TcpTransport;
057import org.apache.directory.server.protocol.shared.transport.Transport;
058import org.apache.directory.server.protocol.shared.transport.UdpTransport;
059import org.junit.runner.Description;
060
061
062/**
063 * 
064 * Annotation processor for creating LDAP and Kerberos servers.
065 *
066 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
067 */
068public final class ServerAnnotationProcessor
069{
070    private ServerAnnotationProcessor()
071    {
072    }
073
074
075    private static void createTransports( LdapServer ldapServer, CreateTransport[] transportBuilders )
076    {
077        if ( transportBuilders.length != 0 )
078        {
079            for ( CreateTransport transportBuilder : transportBuilders )
080            {
081                List< Transport > transports = createTransports( transportBuilder );
082                
083                for ( Transport t : transports )
084                {
085                    ldapServer.addTransports( t );
086                }
087            }
088        }
089        else
090        {
091            // Create default LDAP and LDAPS transports
092            try 
093            {
094                int port = getFreePort();
095                Transport ldap = new TcpTransport( port );
096                ldapServer.addTransports( ldap );
097            }
098            catch ( IOException ioe )
099            {
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            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 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 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            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        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 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        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            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            ChangePasswordConfig config = new ChangePasswordConfig( kdcConfig );
452            config.setServicePrincipal( createChngPwdServer.srvPrincipal() );
453
454            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}