blob: 7d973ca5d6a7ff5715ae2a36a018e10445ecb813 [file] [log] [blame]
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.ldap.server.jndi;
import java.util.Hashtable;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import org.apache.ldap.common.exception.LdapAuthenticationNotSupportedException;
import org.apache.ldap.common.exception.LdapConfigurationException;
import org.apache.ldap.common.exception.LdapNoPermissionException;
import org.apache.ldap.common.message.LockableAttributesImpl;
import org.apache.ldap.common.message.ResultCodeEnum;
import org.apache.ldap.common.name.DnParser;
import org.apache.ldap.common.name.LdapName;
import org.apache.ldap.common.name.NameComponentNormalizer;
import org.apache.ldap.common.util.DateUtils;
import org.apache.ldap.server.configuration.Configuration;
import org.apache.ldap.server.configuration.ConfigurationException;
import org.apache.ldap.server.configuration.StartupConfiguration;
import org.apache.ldap.server.interceptor.InterceptorChain;
import org.apache.ldap.server.partition.ContextPartitionNexus;
import org.apache.ldap.server.partition.DefaultContextPartitionNexus;
import org.apache.ldap.server.schema.AttributeTypeRegistry;
import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
import org.apache.ldap.server.schema.GlobalRegistries;
import org.apache.ldap.server.schema.bootstrap.BootstrapRegistries;
import org.apache.ldap.server.schema.bootstrap.BootstrapSchemaLoader;
/**
* Default implementation of {@link ContextFactoryService}.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
class DefaultContextFactoryService implements ContextFactoryService
{
private final ContextFactoryConfiguration configuration = new DefaultContextFactoryConfiguration( this );
private ContextFactoryServiceListener serviceListener;
/** the initial context environment that fired up the backend subsystem */
private Hashtable environment;
/** the configuration */
private StartupConfiguration startupConfiguration;
/** the registries for system schema objects */
private GlobalRegistries globalRegistries;
/** the root nexus */
private DefaultContextPartitionNexus partitionNexus;
/** whether or not server is started for the first time */
private boolean firstStart;
/** The interceptor (or interceptor chain) for this service */
private InterceptorChain interceptorChain;
/** whether or not this instance has been shutdown */
private boolean started = false;
// ------------------------------------------------------------------------
// Constructor
// ------------------------------------------------------------------------
/**
* Creates a new instance.
*/
public DefaultContextFactoryService()
{
// Register shutdown hook.
Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() {
public void run()
{
try
{
shutdown();
}
catch( NamingException e )
{
e.printStackTrace();
}
}
}, "ApacheDS Shutdown Hook" ) );
}
// ------------------------------------------------------------------------
// BackendSubsystem Interface Method Implemetations
// ------------------------------------------------------------------------
public Context getJndiContext( String rootDN ) throws NamingException
{
return this.getJndiContext( null, null, "none", rootDN );
}
public synchronized Context getJndiContext( String principal, byte[] credential, String authentication, String rootDN ) throws NamingException
{
checkSecuritySettings( principal, credential, authentication );
if ( !started )
{
return new DeadContext();
}
Hashtable environment = getEnvironment();
if( principal != null )
{
environment.put( Context.SECURITY_PRINCIPAL, principal );
}
if( credential != null )
{
environment.put( Context.SECURITY_CREDENTIALS, credential );
}
if( authentication != null )
{
environment.put( Context.SECURITY_AUTHENTICATION, authentication );
}
if( rootDN == null )
{
rootDN = "";
}
environment.put( Context.PROVIDER_URL, rootDN );
return new ServerLdapContext( this, environment );
}
public synchronized void startup( ContextFactoryServiceListener listener, Hashtable env ) throws NamingException
{
if( started )
{
return;
}
StartupConfiguration cfg = ( StartupConfiguration ) Configuration.toConfiguration( env );
env.put( Context.PROVIDER_URL, "" );
try
{
cfg.validate();
}
catch( ConfigurationException e )
{
NamingException ne = new LdapConfigurationException( "Invalid configuration." );
ne.initCause( e );
throw ne;
}
this.environment = env;
this.startupConfiguration = cfg;
listener.beforeStartup( this );
try
{
initialize();
firstStart = createBootstrapEntries();
createTestEntries();
this.serviceListener = listener;
started = true;
}
finally
{
listener.afterStartup( this );
}
}
public synchronized void sync() throws NamingException
{
if ( !started )
{
return;
}
serviceListener.beforeSync( this );
try
{
this.partitionNexus.sync();
}
finally
{
serviceListener.afterSync( this );
}
}
public synchronized void shutdown() throws NamingException
{
if ( !started )
{
return;
}
serviceListener.beforeShutdown( this );
try
{
this.partitionNexus.sync();
this.partitionNexus.destroy();
this.interceptorChain.destroy();
this.started = false;
}
finally
{
environment = null;
interceptorChain = null;
startupConfiguration = null;
serviceListener.afterShutdown( this );
}
}
public ContextFactoryConfiguration getConfiguration()
{
return configuration;
}
public Hashtable getEnvironment()
{
return ( Hashtable ) environment.clone();
}
public ContextFactoryServiceListener getServiceListener()
{
return serviceListener;
}
public StartupConfiguration getStartupConfiguration()
{
return startupConfiguration;
}
public GlobalRegistries getGlobalRegistries()
{
return globalRegistries;
}
public ContextPartitionNexus getPartitionNexus()
{
return partitionNexus;
}
public InterceptorChain getInterceptorChain()
{
return interceptorChain;
}
public boolean isFirstStart()
{
return firstStart;
}
public boolean isStarted()
{
return started;
}
/**
* Checks to make sure security environment parameters are set correctly.
*
* @throws javax.naming.NamingException if the security settings are not correctly configured.
*/
private void checkSecuritySettings( String principal, byte[] credential, String authentication ) throws NamingException
{
if( authentication == null )
{
authentication = "";
}
/*
* If bind is simple make sure we have the credentials and the
* principal name set within the environment, otherwise complain
*/
if ( "simple".equalsIgnoreCase( authentication ) )
{
if ( credential == null )
{
throw new LdapConfigurationException( "missing required "
+ Context.SECURITY_CREDENTIALS + " property for simple authentication" );
}
if ( principal == null )
{
throw new LdapConfigurationException( "missing required "
+ Context.SECURITY_PRINCIPAL + " property for simple authentication" );
}
}
/*
* If bind is none make sure credentials and the principal
* name are NOT set within the environment, otherwise complain
*/
else if ( "none".equalsIgnoreCase( authentication ) )
{
if ( credential != null )
{
throw new LdapConfigurationException( "ambiguous bind "
+ "settings encountered where bind is anonymous yet "
+ Context.SECURITY_CREDENTIALS + " property is set" );
}
if ( principal != null )
{
throw new LdapConfigurationException( "ambiguous bind "
+ "settings encountered where bind is anonymous yet "
+ Context.SECURITY_PRINCIPAL + " property is set" );
}
if( !startupConfiguration.isAllowAnonymousAccess() )
{
throw new LdapNoPermissionException( "Anonymous access disabled." );
}
}
else
{
/*
* If bind is anything other than simple or none we need to
* complain because SASL is not a supported auth method yet
*/
throw new LdapAuthenticationNotSupportedException( "Unknown authentication type: '" + authentication + "'", ResultCodeEnum.AUTHMETHODNOTSUPPORTED );
}
}
/**
* Returns true if we had to create the bootstrap entries on the first
* start of the server. Otherwise if all entries exist, meaning none
* had to be created, then we are not starting for the first time.
*
* @throws javax.naming.NamingException
*/
private boolean createBootstrapEntries() throws NamingException
{
boolean firstStart = false;
// -------------------------------------------------------------------
// create admin entry
// -------------------------------------------------------------------
/*
* If the admin entry is there, then the database was already created
*/
if ( !partitionNexus.hasEntry( ContextPartitionNexus.getAdminName() ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
attributes.put( "objectClass", "top" );
attributes.put( "objectClass", "person" );
attributes.put( "objectClass", "organizationalPerson" );
attributes.put( "objectClass", "inetOrgPerson" );
attributes.put( "uid", ContextPartitionNexus.ADMIN_UID );
attributes.put( "userPassword", ContextPartitionNexus.ADMIN_PW );
attributes.put( "displayName", "Directory Superuser" );
attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
attributes.put( "displayName", "Directory Superuser" );
partitionNexus.add( ContextPartitionNexus.ADMIN_PRINCIPAL, ContextPartitionNexus.getAdminName(), attributes );
}
// -------------------------------------------------------------------
// create system users area
// -------------------------------------------------------------------
if ( !partitionNexus.hasEntry( new LdapName( "ou=users,ou=system" ) ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
attributes.put( "objectClass", "top" );
attributes.put( "objectClass", "organizationalUnit" );
attributes.put( "ou", "users" );
attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( "ou=users,ou=system", new LdapName( "ou=users,ou=system" ), attributes );
}
// -------------------------------------------------------------------
// create system groups area
// -------------------------------------------------------------------
if ( !partitionNexus.hasEntry( new LdapName( "ou=groups,ou=system" ) ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
attributes.put( "objectClass", "top" );
attributes.put( "objectClass", "organizationalUnit" );
attributes.put( "ou", "groups" );
attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( "ou=groups,ou=system", new LdapName( "ou=groups,ou=system" ), attributes );
}
// -------------------------------------------------------------------
// create system preferences area
// -------------------------------------------------------------------
if ( !partitionNexus.hasEntry( new LdapName( "prefNodeName=sysPrefRoot,ou=system" ) ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
attributes.put( "objectClass", "top" );
attributes.put( "objectClass", "prefNode" );
attributes.put( "objectClass", "extensibleObject" );
attributes.put( "prefNodeName", "sysPrefRoot" );
attributes.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
LdapName dn = new LdapName( "prefNodeName=sysPrefRoot,ou=system" );
partitionNexus.add( "prefNodeName=sysPrefRoot,ou=system", dn, attributes );
}
return firstStart;
}
private void createTestEntries() throws NamingException
{
/*
* Unfortunately to test non-root user startup of the core and make sure
* all the appropriate functionality is there we need to load more user
* entries at startup due to a chicken and egg like problem. The value
* of this property is a list of attributes to be added.
*/
Iterator i = startupConfiguration.getTestEntries().iterator();
while( i.hasNext() )
{
Attributes entry = ( Attributes ) i.next();
entry.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL );
entry.put( "createTimestamp", DateUtils.getGeneralizedTime() );
Attribute dn = entry.remove( "dn" );
AttributeTypeRegistry registry = globalRegistries.getAttributeTypeRegistry();
NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( registry );
DnParser parser = new DnParser( ncn );
Name ndn = parser.parse( ( String ) dn.get() );
partitionNexus.add( ( String ) dn.get(), ndn, entry );
}
}
/**
* Kicks off the initialization of the entire system.
*
* @throws javax.naming.NamingException if there are problems along the way
*/
private void initialize() throws NamingException
{
// --------------------------------------------------------------------
// Load the schema here and check that it is ok!
// --------------------------------------------------------------------
BootstrapRegistries bootstrapRegistries = new BootstrapRegistries();
BootstrapSchemaLoader loader = new BootstrapSchemaLoader();
loader.load( startupConfiguration.getBootstrapSchemas(), bootstrapRegistries );
java.util.List errors = bootstrapRegistries.checkRefInteg();
if ( !errors.isEmpty() )
{
NamingException e = new NamingException();
e.setRootCause( ( Throwable ) errors.get( 0 ) );
throw e;
}
globalRegistries = new GlobalRegistries( bootstrapRegistries );
partitionNexus = new DefaultContextPartitionNexus( new LockableAttributesImpl() );
partitionNexus.init( configuration, null );
interceptorChain = new InterceptorChain();
interceptorChain.init( configuration );
}
}