blob: c6c345ca669a9c90de996a49a7b8f96d8adad6a0 [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.jndi;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.server.changepw.ChangePasswordConfiguration;
import org.apache.directory.server.changepw.ChangePasswordServer;
import org.apache.directory.server.configuration.ServerStartupConfiguration;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.jndi.CoreContextFactory;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.kerberos.kdc.KdcConfiguration;
import org.apache.directory.server.kerberos.kdc.KerberosServer;
import org.apache.directory.server.kerberos.shared.store.JndiPrincipalStoreImpl;
import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
import org.apache.directory.server.ldap.ExtendedOperationHandler;
import org.apache.directory.server.ldap.LdapProtocolProvider;
import org.apache.directory.server.ntp.NtpConfiguration;
import org.apache.directory.server.ntp.NtpServer;
import org.apache.directory.server.protocol.shared.LoadStrategy;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
import org.apache.directory.shared.ldap.exception.LdapNamingException;
import org.apache.directory.shared.ldap.message.AttributesImpl;
import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;
import org.apache.directory.shared.ldap.util.StringTools;
import org.apache.mina.common.DefaultIoFilterChainBuilder;
import org.apache.mina.common.ExecutorThreadModel;
import org.apache.mina.common.IoAcceptor;
import org.apache.mina.common.IoFilterChainBuilder;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.WriteFuture;
import org.apache.mina.transport.socket.nio.DatagramAcceptor;
import org.apache.mina.transport.socket.nio.DatagramAcceptorConfig;
import org.apache.mina.transport.socket.nio.SocketAcceptor;
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
import org.apache.mina.transport.socket.nio.SocketSessionConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.emory.mathcs.backport.java.util.concurrent.LinkedBlockingQueue;
import edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
/**
* Adds additional bootstrapping for server socket listeners when firing
* up the server.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
* @see javax.naming.spi.InitialContextFactory
*/
public class ServerContextFactory extends CoreContextFactory
{
/** Logger for this class */
private static final Logger log = LoggerFactory.getLogger( ServerContextFactory.class.getName() );
private static final String LDIF_FILES_DN = "ou=loadedLdifFiles,ou=configuration,ou=system";
protected static IoAcceptor tcpAcceptor;
protected static IoAcceptor udpAcceptor;
protected static ThreadPoolExecutor threadPoolExecutor;
protected static ExecutorThreadModel threadModel = ExecutorThreadModel.getInstance( "ApacheDS" );
private static boolean ldapStarted;
private static boolean ldapsStarted;
private static KerberosServer tcpKdcServer;
private static KerberosServer udpKdcServer;
private static ChangePasswordServer tcpChangePasswordServer;
private static ChangePasswordServer udpChangePasswordServer;
private static NtpServer tcpNtpServer;
private static NtpServer udpNtpServer;
private DirectoryService directoryService;
/**
* Initialize the SocketAcceptor so that the server can accept
* incomming requests.
*
* We will start N threads, spreaded on the available CPUs.
*/
public void beforeStartup( DirectoryService service )
{
int maxThreads = service.getConfiguration().getStartupConfiguration().getMaxThreads();
threadPoolExecutor = new ThreadPoolExecutor( maxThreads, maxThreads, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue() );
threadModel.setExecutor( threadPoolExecutor );
udpAcceptor = new DatagramAcceptor();
tcpAcceptor = new SocketAcceptor(
Runtime.getRuntime().availableProcessors(), threadPoolExecutor );
this.directoryService = service;
}
public void afterShutdown( DirectoryService service )
{
ServerStartupConfiguration cfg = ( ServerStartupConfiguration )
service.getConfiguration().getStartupConfiguration();
if ( ldapStarted )
{
stopLDAP0( cfg.getLdapPort() );
ldapStarted = false;
}
if ( ldapsStarted )
{
stopLDAP0( cfg.getLdapsPort() );
ldapsStarted = false;
}
if ( tcpKdcServer != null )
{
tcpKdcServer.destroy();
if ( log.isInfoEnabled() )
{
log.info( "Unbind of KRB5 Service (TCP) complete: " + tcpKdcServer );
}
tcpKdcServer = null;
}
if ( udpKdcServer != null )
{
udpKdcServer.destroy();
if ( log.isInfoEnabled() )
{
log.info( "Unbind of KRB5 Service (UDP) complete: " + udpKdcServer );
}
udpKdcServer = null;
}
if ( tcpChangePasswordServer != null )
{
tcpChangePasswordServer.destroy();
if ( log.isInfoEnabled() )
{
log.info( "Unbind of Change Password Service (TCP) complete: " + tcpChangePasswordServer );
}
tcpChangePasswordServer = null;
}
if ( udpChangePasswordServer != null )
{
udpChangePasswordServer.destroy();
if ( log.isInfoEnabled() )
{
log.info( "Unbind of Change Password Service (UDP) complete: " + udpChangePasswordServer );
}
udpChangePasswordServer = null;
}
if ( tcpNtpServer != null )
{
tcpNtpServer.destroy();
if ( log.isInfoEnabled() )
{
log.info( "Unbind of NTP Service (TCP) complete: " + tcpNtpServer );
}
tcpNtpServer = null;
}
if ( udpNtpServer != null )
{
udpNtpServer.destroy();
if ( log.isInfoEnabled() )
{
log.info( "Unbind of NTP Service complete: " + udpNtpServer );
}
udpNtpServer = null;
}
}
public void afterStartup( DirectoryService service ) throws NamingException
{
ServerStartupConfiguration cfg = ( ServerStartupConfiguration ) service.getConfiguration()
.getStartupConfiguration();
Hashtable env = service.getConfiguration().getEnvironment();
loadLdifs( service );
if ( cfg.isEnableNetworking() )
{
startLDAP( cfg, env );
startLDAPS( cfg, env );
startKerberos( cfg, env );
startChangePassword( cfg, env );
startNTP( cfg, env );
}
}
private void ensureLdifFileBase( DirContext root )
{
Attributes entry = new AttributesImpl( SchemaConstants.OU_AT, "loadedLdifFiles", true );
entry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC );
entry.get( SchemaConstants.OBJECT_CLASS_AT ).add( SchemaConstants.ORGANIZATIONAL_UNIT_OC );
try
{
root.createSubcontext( LDIF_FILES_DN, entry );
log.info( "Creating " + LDIF_FILES_DN );
}
catch ( NamingException e )
{
log.info( LDIF_FILES_DN + " exists" );
}
}
private final static String WINDOWSFILE_ATTR = "windowsFilePath";
private final static String UNIXFILE_ATTR = "unixFilePath";
private final static String WINDOWSFILE_OC = "windowsFile";
private final static String UNIXFILE_OC = "unixFile";
private String buildProtectedFileEntry( File ldif )
{
StringBuffer buf = new StringBuffer();
buf.append( File.separatorChar == '\\' ? WINDOWSFILE_ATTR : UNIXFILE_ATTR );
buf.append( "=" );
buf.append( StringTools.dumpHexPairs( StringTools.getBytesUtf8( getCanonical( ldif ) ) ) );
buf.append( "," );
buf.append( LDIF_FILES_DN );
return buf.toString();
}
private void addFileEntry( DirContext root, File ldif ) throws NamingException
{
String rdnAttr = File.separatorChar == '\\' ? WINDOWSFILE_ATTR : UNIXFILE_ATTR;
String oc = File.separatorChar == '\\' ? WINDOWSFILE_OC : UNIXFILE_OC;
Attributes entry = new AttributesImpl( rdnAttr, getCanonical( ldif ), true );
entry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC );
entry.get( SchemaConstants.OBJECT_CLASS_AT ).add( oc );
root.createSubcontext( buildProtectedFileEntry( ldif ), entry );
}
private Attributes getLdifFileEntry( DirContext root, File ldif )
{
try
{
return root.getAttributes( buildProtectedFileEntry( ldif ), new String[]
{ SchemaConstants.CREATE_TIMESTAMP_AT } );
}
catch ( NamingException e )
{
return null;
}
}
private String getCanonical( File file )
{
String canonical = null;
try
{
canonical = file.getCanonicalPath();
}
catch ( IOException e )
{
log.error( "could not get canonical path", e );
return null;
}
return StringUtils.replace( canonical, "\\", "\\\\" );
}
private void loadLdifs( DirectoryService service ) throws NamingException
{
ServerStartupConfiguration cfg = ( ServerStartupConfiguration ) service.getConfiguration()
.getStartupConfiguration();
// log and bail if property not set
if ( cfg.getLdifDirectory() == null )
{
log.info( "LDIF load directory not specified. No LDIF files will be loaded." );
return;
}
// log and bail if LDIF directory does not exists
if ( !cfg.getLdifDirectory().exists() )
{
log.warn( "LDIF load directory '" + getCanonical( cfg.getLdifDirectory() )
+ "' does not exist. No LDIF files will be loaded." );
return;
}
// get an initial context to the rootDSE for creating the LDIF entries
Hashtable env = ( Hashtable ) service.getConfiguration().getEnvironment().clone();
env.put( Context.PROVIDER_URL, "" );
DirContext root = ( DirContext ) this.getInitialContext( env );
// make sure the configuration area for loaded ldif files is present
ensureLdifFileBase( root );
// if ldif directory is a file try to load it
if ( !cfg.getLdifDirectory().isDirectory() )
{
if ( log.isInfoEnabled() )
{
log.info( "LDIF load directory '" + getCanonical( cfg.getLdifDirectory() )
+ "' is a file. Will attempt to load as LDIF." );
}
Attributes fileEntry = getLdifFileEntry( root, cfg.getLdifDirectory() );
if ( fileEntry != null )
{
String time = ( String ) fileEntry.get( SchemaConstants.CREATE_TIMESTAMP_AT ).get();
if ( log.isInfoEnabled() )
{
log.info( "Load of LDIF file '" + getCanonical( cfg.getLdifDirectory() )
+ "' skipped. It has already been loaded on " + time + "." );
}
return;
}
LdifFileLoader loader = new LdifFileLoader( root, cfg.getLdifDirectory(), cfg.getLdifFilters() );
loader.execute();
addFileEntry( root, cfg.getLdifDirectory() );
return;
}
// get all the ldif files within the directory (should be sorted alphabetically)
File[] ldifFiles = cfg.getLdifDirectory().listFiles( new FileFilter()
{
public boolean accept( File pathname )
{
boolean isLdif = pathname.getName().toLowerCase().endsWith( ".ldif" );
return pathname.isFile() && pathname.canRead() && isLdif;
}
} );
// log and bail if we could not find any LDIF files
if ( ldifFiles == null || ldifFiles.length == 0 )
{
log.warn( "LDIF load directory '" + getCanonical( cfg.getLdifDirectory() )
+ "' does not contain any LDIF files. No LDIF files will be loaded." );
return;
}
// load all the ldif files and load each one that is loaded
for ( int ii = 0; ii < ldifFiles.length; ii++ )
{
Attributes fileEntry = getLdifFileEntry( root, ldifFiles[ii] );
if ( fileEntry != null )
{
String time = ( String ) fileEntry.get( SchemaConstants.CREATE_TIMESTAMP_AT ).get();
log.info( "Load of LDIF file '" + getCanonical( ldifFiles[ii] )
+ "' skipped. It has already been loaded on " + time + "." );
continue;
}
LdifFileLoader loader = new LdifFileLoader( root, ldifFiles[ii], cfg.getLdifFilters() );
int count = loader.execute();
log.info( "Loaded " + count + " entries from LDIF file '" + getCanonical( ldifFiles[ii] ) + "'" );
if ( fileEntry == null )
{
addFileEntry( root, ldifFiles[ii] );
}
}
}
/**
* Starts up the LDAP protocol provider to service LDAP requests
*
* @throws NamingException if there are problems starting the LDAP provider
*/
private void startLDAP( ServerStartupConfiguration cfg, Hashtable env ) throws NamingException
{
// Skip if disabled
int port = cfg.getLdapPort();
if ( port < 0 )
{
return;
}
startLDAP0( cfg, env, port, new DefaultIoFilterChainBuilder() );
}
/**
* Starts up the LDAPS protocol provider to service LDAPS requests
*
* @throws NamingException if there are problems starting the LDAPS provider
*/
private void startLDAPS( ServerStartupConfiguration cfg, Hashtable env ) throws NamingException
{
// Skip if disabled
if ( !cfg.isEnableLdaps() )
{
return;
}
// We use the reflection API in case this is not running on JDK 1.5+.
IoFilterChainBuilder chain;
try
{
chain = ( IoFilterChainBuilder ) Class.forName( "org.apache.directory.server.ssl.LdapsInitializer", true,
ServerContextFactory.class.getClassLoader() ).getMethod( "init", new Class[]
{ ServerStartupConfiguration.class } ).invoke( null, new Object[]
{ cfg } );
ldapsStarted = true;
}
catch ( InvocationTargetException e )
{
if ( e.getCause() instanceof NamingException )
{
throw ( NamingException ) e.getCause();
}
else
{
throw ( NamingException ) new NamingException( "Failed to load LDAPS initializer." ).initCause( e
.getCause() );
}
}
catch ( Exception e )
{
throw ( NamingException ) new NamingException( "Failed to load LDAPS initializer." ).initCause( e );
}
startLDAP0( cfg, env, cfg.getLdapsPort(), chain );
}
private void startLDAP0( ServerStartupConfiguration cfg, Hashtable env, int port,
IoFilterChainBuilder chainBuilder ) throws LdapNamingException, LdapConfigurationException
{
// Register all extended operation handlers.
LdapProtocolProvider protocolProvider = new LdapProtocolProvider( cfg, ( Hashtable ) env.clone() );
for ( Iterator i = cfg.getExtendedOperationHandlers().iterator(); i.hasNext(); )
{
ExtendedOperationHandler h = ( ExtendedOperationHandler ) i.next();
protocolProvider.addExtendedOperationHandler( h );
log.info( "Added Extended Request Handler: " + h.getOid() );
h.setLdapProvider( protocolProvider );
PartitionNexus nexus = directoryService.getConfiguration().getPartitionNexus();
nexus.registerSupportedExtensions( h.getExtensionOids() );
}
try
{
// Disable the disconnection of the clients on unbind
SocketAcceptorConfig acceptorCfg = new SocketAcceptorConfig();
acceptorCfg.setDisconnectOnUnbind( false );
acceptorCfg.setReuseAddress( true );
acceptorCfg.setFilterChainBuilder( chainBuilder );
acceptorCfg.setThreadModel( threadModel );
((SocketSessionConfig)(acceptorCfg.getSessionConfig())).setTcpNoDelay( true );
tcpAcceptor.bind( new InetSocketAddress( port ), protocolProvider.getHandler(), acceptorCfg );
ldapStarted = true;
if ( log.isInfoEnabled() )
{
log.info( "Successful bind of an LDAP Service (" + port + ") is complete." );
}
}
catch ( IOException e )
{
String msg = "Failed to bind an LDAP service (" + port + ") to the service registry.";
LdapConfigurationException lce = new LdapConfigurationException( msg );
lce.setRootCause( e );
log.error( msg, e );
throw lce;
}
}
private void startKerberos( ServerStartupConfiguration cfg, Hashtable env )
{
if ( cfg.isEnableKerberos() )
{
try
{
KdcConfiguration kdcConfiguration = new KdcConfiguration( env, LoadStrategy.PROPS );
PrincipalStore kdcStore = new JndiPrincipalStoreImpl( kdcConfiguration, this );
DatagramAcceptorConfig udpConfig = new DatagramAcceptorConfig();
udpConfig.setThreadModel( threadModel );
SocketAcceptorConfig tcpConfig = new SocketAcceptorConfig();
tcpConfig.setDisconnectOnUnbind( false );
tcpConfig.setReuseAddress( true );
tcpConfig.setFilterChainBuilder( new DefaultIoFilterChainBuilder() );
tcpConfig.setThreadModel( threadModel );
tcpKdcServer = new KerberosServer( kdcConfiguration, tcpAcceptor, tcpConfig, kdcStore );
udpKdcServer = new KerberosServer( kdcConfiguration, udpAcceptor, udpConfig, kdcStore );
}
catch ( Throwable t )
{
log.error( "Failed to start the Kerberos service", t );
}
}
}
private void startChangePassword( ServerStartupConfiguration cfg, Hashtable env )
{
if ( cfg.isEnableChangePassword() )
{
try
{
ChangePasswordConfiguration changePasswordConfiguration = new ChangePasswordConfiguration( env,
LoadStrategy.PROPS );
PrincipalStore store = new JndiPrincipalStoreImpl( changePasswordConfiguration, this );
DatagramAcceptorConfig udpConfig = new DatagramAcceptorConfig();
udpConfig.setThreadModel( threadModel );
SocketAcceptorConfig tcpConfig = new SocketAcceptorConfig();
tcpConfig.setDisconnectOnUnbind( false );
tcpConfig.setReuseAddress( true );
tcpConfig.setFilterChainBuilder( new DefaultIoFilterChainBuilder() );
tcpConfig.setThreadModel( threadModel );
tcpChangePasswordServer = new ChangePasswordServer( changePasswordConfiguration, tcpAcceptor,
tcpConfig, store );
udpChangePasswordServer = new ChangePasswordServer( changePasswordConfiguration, udpAcceptor,
udpConfig, store );
}
catch ( Throwable t )
{
log.error( "Failed to start the Change Password service", t );
}
}
}
private void startNTP( ServerStartupConfiguration cfg, Hashtable env )
{
if ( cfg.isEnableNtp() )
{
try
{
NtpConfiguration ntpConfig = new NtpConfiguration( env, LoadStrategy.PROPS );
DatagramAcceptorConfig udpConfig = new DatagramAcceptorConfig();
udpConfig.setThreadModel( threadModel );
SocketAcceptorConfig tcpConfig = new SocketAcceptorConfig();
tcpConfig.setDisconnectOnUnbind( false );
tcpConfig.setReuseAddress( true );
tcpConfig.setFilterChainBuilder( new DefaultIoFilterChainBuilder() );
tcpConfig.setThreadModel( threadModel );
tcpNtpServer = new NtpServer( ntpConfig, tcpAcceptor, tcpConfig );
udpNtpServer = new NtpServer( ntpConfig, udpAcceptor, udpConfig );
}
catch ( Throwable t )
{
log.error( "Failed to start the NTP service", t );
}
}
}
private void stopLDAP0( int port )
{
try
{
// we should unbind the service before we begin sending the notice
// of disconnect so new connections are not formed while we process
List writeFutures = new ArrayList();
// If the socket has already been unbound as with a successful
// GracefulShutdownRequest then this will complain that the service
// is not bound - this is ok because the GracefulShutdown has already
// sent notices to to the existing active sessions
List sessions = null;
try
{
sessions = new ArrayList( tcpAcceptor.getManagedSessions( new InetSocketAddress( port ) ) );
}
catch ( IllegalArgumentException e )
{
log.warn( "Seems like the LDAP service (" + port + ") has already been unbound." );
return;
}
tcpAcceptor.unbind( new InetSocketAddress( port ) );
if ( log.isInfoEnabled() )
{
log.info( "Unbind of an LDAP service (" + port + ") is complete." );
log.info( "Sending notice of disconnect to existing clients sessions." );
}
// Send Notification of Disconnection messages to all connected clients.
if ( sessions != null )
{
for ( Iterator i = sessions.iterator(); i.hasNext(); )
{
IoSession session = ( IoSession ) i.next();
writeFutures.add( session.write( NoticeOfDisconnect.UNAVAILABLE ) );
}
}
// And close the connections when the NoDs are sent.
Iterator sessionIt = sessions.iterator();
for ( Iterator i = writeFutures.iterator(); i.hasNext(); )
{
WriteFuture future = ( WriteFuture ) i.next();
future.join( 1000 );
( ( IoSession ) sessionIt.next() ).close();
}
}
catch ( Exception e )
{
log.warn( "Failed to sent NoD.", e );
}
}
}