blob: be226c6a81095b10ade0253d74d7cb9073125e2a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.directory.server.factory;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
import org.apache.directory.api.util.Network;
import org.apache.directory.api.util.Strings;
import org.apache.directory.server.annotations.CreateChngPwdServer;
import org.apache.directory.server.annotations.CreateConsumer;
import org.apache.directory.server.annotations.CreateKdcServer;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.annotations.SaslMechanism;
import org.apache.directory.server.core.annotations.AnnotationUtils;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.security.CertificateUtil;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.server.kerberos.ChangePasswordConfig;
import org.apache.directory.server.kerberos.KerberosConfig;
import org.apache.directory.server.kerberos.changepwd.ChangePasswordServer;
import org.apache.directory.server.kerberos.kdc.KdcServer;
import org.apache.directory.server.ldap.ExtendedOperationHandler;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.ldap.handlers.sasl.MechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmProvider;
import org.apache.directory.server.ldap.replication.SyncReplConfiguration;
import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumer;
import org.apache.directory.server.ldap.replication.consumer.ReplicationConsumerImpl;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.directory.server.protocol.shared.transport.Transport;
import org.apache.directory.server.protocol.shared.transport.UdpTransport;
import org.junit.runner.Description;
/**
*
* Annotation processor for creating LDAP and Kerberos servers.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public final class ServerAnnotationProcessor
{
private ServerAnnotationProcessor()
{
}
private static void createTransports( LdapServer ldapServer, CreateTransport[] transportBuilders )
{
if ( transportBuilders.length != 0 )
{
for ( CreateTransport transportBuilder : transportBuilders )
{
List< Transport > transports = createTransports( transportBuilder );
for ( Transport t : transports )
{
ldapServer.addTransports( t );
}
}
}
else
{
// Create default LDAP and LDAPS transports
try
{
int port = getFreePort();
Transport ldap = new TcpTransport( port );
ldapServer.addTransports( ldap );
}
catch ( IOException ioe )
{
// Don't know what to do here...
}
try
{
int port = getFreePort();
Transport ldaps = new TcpTransport( port );
ldaps.setEnableSSL( true );
ldapServer.addTransports( ldaps );
}
catch ( IOException ioe )
{
// Don't know what to do here...
}
}
}
/**
* Just gives an instance of {@link LdapServer} without starting it.
* For getting a running LdapServer instance see {@link #createLdapServer(CreateLdapServer, DirectoryService)}
* @see #createLdapServer(CreateLdapServer, DirectoryService)
*
* @param createLdapServer The LdapServer to create
* @param directoryService the directory service
* @return The created LdapServer
*/
public static LdapServer instantiateLdapServer( CreateLdapServer createLdapServer, DirectoryService directoryService )
{
if ( createLdapServer != null )
{
LdapServer ldapServer = new LdapServer();
ldapServer.setServiceName( createLdapServer.name() );
// Read the transports
createTransports( ldapServer, createLdapServer.transports() );
// Associate the DS to this LdapServer
ldapServer.setDirectoryService( directoryService );
// Propagate the anonymous flag to the DS
directoryService.setAllowAnonymousAccess( createLdapServer.allowAnonymousAccess() );
ldapServer.setSaslHost( createLdapServer.saslHost() );
ldapServer.setSaslPrincipal( createLdapServer.saslPrincipal() );
if ( !Strings.isEmpty( createLdapServer.keyStore() ) )
{
ldapServer.setKeystoreFile( createLdapServer.keyStore() );
ldapServer.setCertificatePassword( createLdapServer.certificatePassword() );
}
else
{
try
{
// Create a temporary keystore, be sure to remove it when exiting the test
File keyStoreFile = CertificateUtil.createTempKeyStore( "testStore", "secret".toCharArray() );
ldapServer.setKeystoreFile( keyStoreFile.getAbsolutePath() );
ldapServer.setCertificatePassword( "secret" );
}
catch ( Exception e )
{
}
}
for ( Class<?> extOpClass : createLdapServer.extendedOpHandlers() )
{
try
{
ExtendedOperationHandler extOpHandler = ( ExtendedOperationHandler ) extOpClass.newInstance();
ldapServer.addExtendedOperationHandler( extOpHandler );
}
catch ( Exception e )
{
throw new RuntimeException( I18n.err( I18n.ERR_690, extOpClass.getName() ), e );
}
}
for ( SaslMechanism saslMech : createLdapServer.saslMechanisms() )
{
try
{
MechanismHandler handler = ( MechanismHandler ) saslMech.implClass().newInstance();
ldapServer.addSaslMechanismHandler( saslMech.name(), handler );
}
catch ( Exception e )
{
throw new RuntimeException(
I18n.err( I18n.ERR_691, saslMech.name(), saslMech.implClass().getName() ), e );
}
}
NtlmMechanismHandler ntlmHandler = ( NtlmMechanismHandler ) ldapServer.getSaslMechanismHandlers().get(
SupportedSaslMechanisms.NTLM );
if ( ntlmHandler != null )
{
Class<?> ntlmProviderClass = createLdapServer.ntlmProvider();
// default value is a invalid Object.class
if ( ( ntlmProviderClass != null ) && ( ntlmProviderClass != Object.class ) )
{
try
{
ntlmHandler.setNtlmProvider( ( NtlmProvider ) ntlmProviderClass.newInstance() );
}
catch ( Exception e )
{
throw new RuntimeException( I18n.err( I18n.ERR_692 ), e );
}
}
}
List<String> realms = new ArrayList<>();
for ( String s : createLdapServer.saslRealms() )
{
realms.add( s );
}
ldapServer.setSaslRealms( realms );
return ldapServer;
}
else
{
return null;
}
}
/**
* Returns an LdapServer instance and starts it before returning the instance, infering
* the configuration from the Stack trace
*
* @param directoryService the directory service
* @return a running LdapServer instance
* @throws ClassNotFoundException If the CreateLdapServer class cannot be loaded
*/
public static LdapServer getLdapServer( DirectoryService directoryService ) throws ClassNotFoundException
{
Object instance = AnnotationUtils.getInstance( CreateLdapServer.class );
LdapServer ldapServer = null;
if ( instance != null )
{
CreateLdapServer createLdapServer = ( CreateLdapServer ) instance;
ldapServer = createLdapServer( createLdapServer, directoryService );
}
return ldapServer;
}
/**
* Create a replication consumer
*/
private static ReplicationConsumer createConsumer( CreateConsumer createConsumer )
{
ReplicationConsumer consumer = new ReplicationConsumerImpl();
SyncReplConfiguration config = new SyncReplConfiguration();
String remoteHost = createConsumer.remoteHost();
if ( Strings.isEmpty( remoteHost ) )
{
remoteHost = Network.LOOPBACK_HOSTNAME;
}
config.setRemoteHost( remoteHost );
config.setRemotePort( createConsumer.remotePort() );
config.setReplUserDn( createConsumer.replUserDn() );
config.setReplUserPassword( Strings.getBytesUtf8( createConsumer.replUserPassword() ) );
config.setUseTls( createConsumer.useTls() );
config.setBaseDn( createConsumer.baseDn() );
config.setRefreshInterval( createConsumer.refreshInterval() );
consumer.setConfig( config );
return consumer;
}
/**
* creates an LdapServer and starts before returning the instance, infering
* the configuration from the Stack trace
*
* @return a running LdapServer instance
* @throws ClassNotFoundException If the CreateConsumer class cannot be loaded
*/
public static ReplicationConsumer createConsumer() throws ClassNotFoundException
{
Object instance = AnnotationUtils.getInstance( CreateConsumer.class );
ReplicationConsumer consumer = null;
if ( instance != null )
{
CreateConsumer createConsumer = ( CreateConsumer ) instance;
consumer = createConsumer( createConsumer );
}
return consumer;
}
/**
* creates an LdapServer and starts before returning the instance
*
* @param createLdapServer the annotation containing the custom configuration
* @param directoryService the directory service
* @return a running LdapServer instance
*/
private static LdapServer createLdapServer( CreateLdapServer createLdapServer, DirectoryService directoryService )
{
LdapServer ldapServer = instantiateLdapServer( createLdapServer, directoryService );
if ( ldapServer == null )
{
return null;
}
// Launch the server
try
{
ldapServer.start();
}
catch ( Exception e )
{
e.printStackTrace();
}
return ldapServer;
}
/**
* Create a new instance of LdapServer
*
* @param description A description for the created LdapServer
* @param directoryService The associated DirectoryService
* @return An LdapServer instance
*/
public static LdapServer createLdapServer( Description description, DirectoryService directoryService )
{
CreateLdapServer createLdapServer = description.getAnnotation( CreateLdapServer.class );
// Ok, we have found a CreateLdapServer annotation. Process it now.
return createLdapServer( createLdapServer, directoryService );
}
@SuppressWarnings("unchecked")
private static Annotation getAnnotation( Class annotationClass ) throws Exception
{
// Get the caller by inspecting the stackTrace
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// In Java5 the 0th stacktrace element is: java.lang.Thread.dumpThreads(Native Method)
int index = stackTrace[0].getMethodName().equals( "dumpThreads" ) ? 4 : 3;
// Get the enclosing class
Class<?> classCaller = Class.forName( stackTrace[index].getClassName() );
// Get the current method
String methodCaller = stackTrace[index].getMethodName();
// Check if we have any annotation associated with the method
Method[] methods = classCaller.getMethods();
for ( Method method : methods )
{
if ( methodCaller.equals( method.getName() ) )
{
Annotation annotation = method.getAnnotation( annotationClass );
if ( annotation != null )
{
return annotation;
}
}
}
// No : look at the class level
return classCaller.getAnnotation( annotationClass );
}
public static KdcServer getKdcServer( DirectoryService directoryService, int startPort ) throws Exception
{
CreateKdcServer createKdcServer = ( CreateKdcServer ) getAnnotation( CreateKdcServer.class );
return createKdcServer( createKdcServer, directoryService );
}
private static KdcServer createKdcServer( CreateKdcServer createKdcServer, DirectoryService directoryService )
{
if ( createKdcServer == null )
{
return null;
}
KerberosConfig kdcConfig = new KerberosConfig();
kdcConfig.setServicePrincipal( createKdcServer.kdcPrincipal() );
kdcConfig.setPrimaryRealm( createKdcServer.primaryRealm() );
kdcConfig.setMaximumTicketLifetime( createKdcServer.maxTicketLifetime() );
kdcConfig.setMaximumRenewableLifetime( createKdcServer.maxRenewableLifetime() );
KdcServer kdcServer = new KdcServer( kdcConfig );
kdcServer.setSearchBaseDn( createKdcServer.searchBaseDn() );
CreateTransport[] transportBuilders = createKdcServer.transports();
if ( transportBuilders == null )
{
// create only UDP transport if none specified
int port = 0;
try
{
port = getFreePort();
}
catch ( IOException ioe )
{
// Don't know what to do here...
}
UdpTransport defaultTransport = new UdpTransport( port );
kdcServer.addTransports( defaultTransport );
}
else if ( transportBuilders.length > 0 )
{
for ( CreateTransport transportBuilder : transportBuilders )
{
List< Transport > transports = createTransports( transportBuilder );
for ( Transport t : transports )
{
kdcServer.addTransports( t );
}
}
}
CreateChngPwdServer[] createChngPwdServers = createKdcServer.chngPwdServer();
if ( createChngPwdServers.length > 0 )
{
CreateChngPwdServer createChngPwdServer = createChngPwdServers[0];
ChangePasswordConfig config = new ChangePasswordConfig( kdcConfig );
config.setServicePrincipal( createChngPwdServer.srvPrincipal() );
ChangePasswordServer chngPwdServer = new ChangePasswordServer( config );
for ( CreateTransport transportBuilder : createChngPwdServer.transports() )
{
List< Transport > transports = createTransports( transportBuilder );
for ( Transport t : transports )
{
chngPwdServer.addTransports( t );
}
}
chngPwdServer.setDirectoryService( directoryService );
kdcServer.setChangePwdServer( chngPwdServer );
}
kdcServer.setDirectoryService( directoryService );
// Launch the server
try
{
kdcServer.start();
}
catch ( Exception e )
{
e.printStackTrace();
}
return kdcServer;
}
private static List< Transport > createTransports( CreateTransport transportBuilder )
{
String protocol = transportBuilder.protocol();
int port = transportBuilder.port();
int nbThreads = transportBuilder.nbThreads();
int backlog = transportBuilder.backlog();
String address = transportBuilder.address();
boolean clientAuth = transportBuilder.clientAuth();
if ( Strings.isEmpty( address ) )
{
address = Network.LOOPBACK_HOSTNAME;
}
if ( port <= 0 )
{
try
{
port = getFreePort();
}
catch ( IOException ioe )
{
// Don't know what to do here...
}
}
if ( protocol.equalsIgnoreCase( "TCP" ) || protocol.equalsIgnoreCase( "LDAP" ) )
{
Transport tcp = new TcpTransport( address, port, nbThreads, backlog );
return Collections.singletonList( tcp );
}
else if ( protocol.equalsIgnoreCase( "LDAPS" ) )
{
Transport tcp = new TcpTransport( address, port, nbThreads, backlog );
tcp.setEnableSSL( true );
( ( TcpTransport ) tcp ).setWantClientAuth( clientAuth );
return Collections.singletonList( tcp );
}
else if ( protocol.equalsIgnoreCase( "UDP" ) )
{
Transport udp = new UdpTransport( address, port );
return Collections.singletonList( udp );
}
else if ( protocol.equalsIgnoreCase( "KRB" ) || protocol.equalsIgnoreCase( "CPW" ) )
{
Transport tcp = new TcpTransport( address, port, nbThreads, backlog );
List< Transport > transports = new ArrayList<>();
transports.add( tcp );
Transport udp = new UdpTransport( address, port );
transports.add( udp );
return transports;
}
throw new IllegalArgumentException( I18n.err( I18n.ERR_689, protocol ) );
}
private static int getFreePort() throws IOException
{
ServerSocket ss = new ServerSocket( 0 );
int port = ss.getLocalPort();
ss.close();
return port;
}
public static KdcServer getKdcServer( Description description, DirectoryService directoryService, int startPort )
{
CreateKdcServer createLdapServer = description.getAnnotation( CreateKdcServer.class );
return createKdcServer( createLdapServer, directoryService );
}
}