blob: 28e856e089fcbf009c313a0b9fcd0ff5bf64789e [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.core;
import org.apache.directory.server.constants.ServerDNConstants;
import org.apache.directory.server.core.authn.AuthenticationInterceptor;
import org.apache.directory.server.core.authn.LdapPrincipal;
import org.apache.directory.server.core.authz.AciAuthorizationInterceptor;
import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor;
import org.apache.directory.server.core.changelog.ChangeLog;
import org.apache.directory.server.core.changelog.ChangeLogEvent;
import org.apache.directory.server.core.changelog.ChangeLogInterceptor;
import org.apache.directory.server.core.changelog.DefaultChangeLog;
import org.apache.directory.server.core.changelog.Tag;
import org.apache.directory.server.core.collective.CollectiveAttributeInterceptor;
import org.apache.directory.server.core.cursor.Cursor;
import org.apache.directory.server.core.entry.DefaultServerEntry;
import org.apache.directory.server.core.entry.ServerEntry;
import org.apache.directory.server.core.entry.ServerEntryUtils;
import org.apache.directory.server.core.event.EventInterceptor;
import org.apache.directory.server.core.exception.ExceptionInterceptor;
import org.apache.directory.server.core.interceptor.Interceptor;
import org.apache.directory.server.core.interceptor.InterceptorChain;
import org.apache.directory.server.core.interceptor.context.AddContextPartitionOperationContext;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
import org.apache.directory.server.core.interceptor.context.RemoveContextPartitionOperationContext;
import org.apache.directory.server.core.jndi.DeadContext;
import org.apache.directory.server.core.jndi.ServerLdapContext;
import org.apache.directory.server.core.normalization.NormalizationInterceptor;
import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
import org.apache.directory.server.core.partition.DefaultPartitionNexus;
import org.apache.directory.server.core.partition.Partition;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.core.partition.impl.btree.BTreePartition;
import org.apache.directory.server.core.partition.impl.btree.Index;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.core.referral.ReferralInterceptor;
import org.apache.directory.server.core.schema.PartitionSchemaLoader;
import org.apache.directory.server.core.schema.SchemaInterceptor;
import org.apache.directory.server.core.schema.SchemaOperationControl;
import org.apache.directory.server.core.schema.SchemaPartitionDao;
import org.apache.directory.server.core.schema.SchemaService;
import org.apache.directory.server.core.security.TlsKeyGenerator;
import org.apache.directory.server.core.subtree.SubentryInterceptor;
import org.apache.directory.server.core.trigger.TriggerInterceptor;
import org.apache.directory.server.schema.SerializableComparator;
import org.apache.directory.server.schema.bootstrap.ApacheSchema;
import org.apache.directory.server.schema.bootstrap.ApachemetaSchema;
import org.apache.directory.server.schema.bootstrap.BootstrapSchemaLoader;
import org.apache.directory.server.schema.bootstrap.CoreSchema;
import org.apache.directory.server.schema.bootstrap.Schema;
import org.apache.directory.server.schema.bootstrap.SystemSchema;
import org.apache.directory.server.schema.bootstrap.partition.DbFileListing;
import org.apache.directory.server.schema.bootstrap.partition.SchemaPartitionExtractor;
import org.apache.directory.server.schema.registries.DefaultOidRegistry;
import org.apache.directory.server.schema.registries.DefaultRegistries;
import org.apache.directory.server.schema.registries.OidRegistry;
import org.apache.directory.server.schema.registries.Registries;
import org.apache.directory.shared.ldap.NotImplementedException;
import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
import org.apache.directory.shared.ldap.constants.SchemaConstants;
import org.apache.directory.shared.ldap.exception.LdapAuthenticationNotSupportedException;
import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
import org.apache.directory.shared.ldap.exception.LdapNamingException;
import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
import org.apache.directory.shared.ldap.ldif.ChangeType;
import org.apache.directory.shared.ldap.ldif.LdifEntry;
import org.apache.directory.shared.ldap.ldif.LdifReader;
import org.apache.directory.shared.ldap.message.AttributesImpl;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.schema.OidNormalizer;
import org.apache.directory.shared.ldap.util.DateUtils;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.ldap.LdapContext;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Default implementation of {@link DirectoryService}.
*
* @org.apache.xbean.XBean
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class DefaultDirectoryService implements DirectoryService
{
private static final Logger LOG = LoggerFactory.getLogger( DefaultDirectoryService.class );
private static final String ILLEGAL_STATE_MSG = "Something has got to be severely " +
"wrong with the core packaging\nor the build to have " +
"resulted in this exception.";
private SchemaService schemaService;
/** the registries for system schema objects */
private Registries registries;
/** the root nexus */
private DefaultPartitionNexus 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;
/** the change log service */
private ChangeLog changeLog;
private LdapDN adminDn;
/** remove me after implementation is completed */
private static final String PARTIAL_IMPL_WARNING =
"WARNING: the changelog is only partially operational and will revert\n" +
"state without consideration of who made the original change. All reverting " +
"changes are made by the admin user.\n Furthermore the used controls are not at " +
"all taken into account";
// ------------------------------------------------------------------------
// Constructor
// ------------------------------------------------------------------------
/**
* Creates a new instance.
*/
public DefaultDirectoryService()
{
setDefaultInterceptorConfigurations();
changeLog = new DefaultChangeLog();
// --------------------------------------------------------------------
// Load the bootstrap schemas to start up the schema partition
// --------------------------------------------------------------------
// setup temporary loader and temp registry
BootstrapSchemaLoader loader = new BootstrapSchemaLoader();
OidRegistry oidRegistry = new DefaultOidRegistry();
registries = new DefaultRegistries( "bootstrap", loader, oidRegistry );
// load essential bootstrap schemas
Set<Schema> bootstrapSchemas = new HashSet<Schema>();
bootstrapSchemas.add( new ApachemetaSchema() );
bootstrapSchemas.add( new ApacheSchema() );
bootstrapSchemas.add( new CoreSchema() );
bootstrapSchemas.add( new SystemSchema() );
try
{
loader.loadWithDependencies( bootstrapSchemas, registries );
}
catch ( NamingException e )
{
throw new IllegalStateException( ILLEGAL_STATE_MSG, e );
}
// run referential integrity tests
List<Throwable> errors = registries.checkRefInteg();
if ( !errors.isEmpty() )
{
NamingException e = new NamingException();
e.setRootCause( errors.get( 0 ) );
throw new IllegalStateException( ILLEGAL_STATE_MSG, e );
}
SerializableComparator.setRegistry( registries.getComparatorRegistry() );
}
// ------------------------------------------------------------------------
// C O N F I G U R A T I O N M E T H O D S
// ------------------------------------------------------------------------
public static final int MAX_SIZE_LIMIT_DEFAULT = 100;
public static final int MAX_TIME_LIMIT_DEFAULT = 10000;
private String instanceId;
private File workingDirectory = new File( "server-work" );
private boolean exitVmOnShutdown = true; // allow by default
private boolean shutdownHookEnabled = true; // allow by default
private boolean allowAnonymousAccess = true; // allow by default
private boolean accessControlEnabled; // off by default
private boolean denormalizeOpAttrsEnabled; // off by default
private int maxSizeLimit = MAX_SIZE_LIMIT_DEFAULT; // set to default value
private int maxTimeLimit = MAX_TIME_LIMIT_DEFAULT; // set to default value (milliseconds)
private List<Interceptor> interceptors;
private Partition systemPartition;
private Set<Partition> partitions = new HashSet<Partition>();
private List<? extends LdifEntry> testEntries = new ArrayList<LdifEntry>(); // List<Attributes>
public void setInstanceId( String instanceId )
{
this.instanceId = instanceId;
}
public String getInstanceId()
{
return instanceId;
}
/**
* Gets the {@link Partition}s used by this DirectoryService.
*
* @org.apache.xbean.Property nestedType="org.apache.directory.server.core.partition.Partition"
* @return the set of partitions used
*/
public Set<? extends Partition> getPartitions()
{
Set<Partition> cloned = new HashSet<Partition>();
cloned.addAll( partitions );
return cloned;
}
/**
* Sets {@link Partition}s used by this DirectoryService.
*
* @org.apache.xbean.Property nestedType="org.apache.directory.server.core.partition.Partition"
* @param partitions the partitions to used
*/
public void setPartitions( Set<? extends Partition> partitions )
{
Set<Partition> cloned = new HashSet<Partition>();
cloned.addAll( partitions );
Set<String> names = new HashSet<String>();
for ( Partition partition : cloned )
{
String id = partition.getId();
if ( names.contains( id ) )
{
LOG.warn( "Encountered duplicate partition {} identifier.", id );
}
names.add( id );
}
this.partitions = cloned;
}
/**
* Returns <tt>true</tt> if access control checks are enabled.
*
* @return true if access control checks are enabled, false otherwise
*/
public boolean isAccessControlEnabled()
{
return accessControlEnabled;
}
/**
* Sets whether to enable basic access control checks or not.
*
* @param accessControlEnabled true to enable access control checks, false otherwise
*/
public void setAccessControlEnabled( boolean accessControlEnabled )
{
this.accessControlEnabled = accessControlEnabled;
}
/**
* Returns <tt>true</tt> if anonymous access is allowed on entries besides the RootDSE.
* If the access control subsystem is enabled then access to some entries may not be
* allowed even when full anonymous access is enabled.
*
* @return true if anonymous access is allowed on entries besides the RootDSE, false
* if anonymous access is allowed to all entries.
*/
public boolean isAllowAnonymousAccess()
{
return allowAnonymousAccess;
}
/**
* Sets whether to allow anonymous access to entries other than the RootDSE. If the
* access control subsystem is enabled then access to some entries may not be allowed
* even when full anonymous access is enabled.
*
* @param enableAnonymousAccess true to enable anonymous access, false to disable it
*/
public void setAllowAnonymousAccess( boolean enableAnonymousAccess )
{
this.allowAnonymousAccess = enableAnonymousAccess;
}
/**
* Returns interceptors in the server.
*
* @return the interceptors in the server.
*/
public List<Interceptor> getInterceptors()
{
List<Interceptor> cloned = new ArrayList<Interceptor>();
cloned.addAll( interceptors );
return cloned;
}
/**
* Sets the interceptors in the server.
*
* @org.apache.xbean.Property nestedType="org.apache.directory.server.core.interceptor.Interceptor"
* @param interceptors the interceptors to be used in the server.
*/
public void setInterceptors( List<Interceptor> interceptors )
{
Set<String> names = new HashSet<String>();
for ( Interceptor interceptor : interceptors )
{
String name = interceptor.getName();
if ( names.contains( name ) )
{
LOG.warn( "Encountered duplicate definitions for {} interceptor", interceptor.getName() );
}
names.add( name );
}
this.interceptors = interceptors;
}
/**
* Returns test directory entries({@link LdifEntry}) to be loaded while
* bootstrapping.
*
* @org.apache.xbean.Property nestedType="org.apache.directory.shared.ldap.ldif.Entry"
* @return test entries to load during bootstrapping
*/
public List<LdifEntry> getTestEntries()
{
List<LdifEntry> cloned = new ArrayList<LdifEntry>();
cloned.addAll( testEntries );
return cloned;
}
/**
* Sets test directory entries({@link Attributes}) to be loaded while
* bootstrapping.
*
* @org.apache.xbean.Property nestedType="org.apache.directory.shared.ldap.ldif.Entry"
* @param testEntries the test entries to load while bootstrapping
*/
public void setTestEntries( List<? extends LdifEntry> testEntries )
{
//noinspection MismatchedQueryAndUpdateOfCollection
List<LdifEntry> cloned = new ArrayList<LdifEntry>();
cloned.addAll( testEntries );
this.testEntries = testEntries;
}
/**
* Returns working directory (counterpart of <tt>var/lib</tt>) where partitions are
* stored by default.
*
* @return the directory where partition's are stored.
*/
public File getWorkingDirectory()
{
return workingDirectory;
}
/**
* Sets working directory (counterpart of <tt>var/lib</tt>) where partitions are stored
* by default.
*
* @param workingDirectory the directory where the server's partitions are stored by default.
*/
public void setWorkingDirectory( File workingDirectory )
{
this.workingDirectory = workingDirectory;
}
public void setShutdownHookEnabled( boolean shutdownHookEnabled )
{
this.shutdownHookEnabled = shutdownHookEnabled;
}
public boolean isShutdownHookEnabled()
{
return shutdownHookEnabled;
}
public void setExitVmOnShutdown( boolean exitVmOnShutdown )
{
this.exitVmOnShutdown = exitVmOnShutdown;
}
public boolean isExitVmOnShutdown()
{
return exitVmOnShutdown;
}
public void setMaxSizeLimit( int maxSizeLimit )
{
this.maxSizeLimit = maxSizeLimit;
}
public int getMaxSizeLimit()
{
return maxSizeLimit;
}
public void setMaxTimeLimit( int maxTimeLimit )
{
this.maxTimeLimit = maxTimeLimit;
}
public int getMaxTimeLimit()
{
return maxTimeLimit;
}
public void setSystemPartition( Partition systemPartition )
{
this.systemPartition = systemPartition;
}
public Partition getSystemPartition()
{
return systemPartition;
}
public boolean isDenormalizeOpAttrsEnabled()
{
return denormalizeOpAttrsEnabled;
}
public void setDenormalizeOpAttrsEnabled( boolean denormalizeOpAttrsEnabled )
{
this.denormalizeOpAttrsEnabled = denormalizeOpAttrsEnabled;
}
public ChangeLog getChangeLog()
{
return changeLog;
}
public void setChangeLog( ChangeLog changeLog )
{
this.changeLog = changeLog;
}
public void addPartition( Partition parition ) throws NamingException
{
partitions.add( parition );
if ( ! started )
{
return;
}
AddContextPartitionOperationContext addPartitionCtx = new AddContextPartitionOperationContext( registries, parition );
partitionNexus.addContextPartition( addPartitionCtx );
}
public void removePartition( Partition partition ) throws NamingException
{
partitions.remove( partition );
if ( ! started )
{
return;
}
RemoveContextPartitionOperationContext removePartitionCtx =
new RemoveContextPartitionOperationContext( registries, partition.getSuffixDn() );
partitionNexus.removeContextPartition( removePartitionCtx );
}
// ------------------------------------------------------------------------
// BackendSubsystem Interface Method Implemetations
// ------------------------------------------------------------------------
private void setDefaultInterceptorConfigurations()
{
// Set default interceptor chains
List<Interceptor> list = new ArrayList<Interceptor>();
list.add( new NormalizationInterceptor() );
list.add( new AuthenticationInterceptor() );
list.add( new ReferralInterceptor() );
list.add( new AciAuthorizationInterceptor() );
list.add( new DefaultAuthorizationInterceptor() );
list.add( new ExceptionInterceptor() );
list.add( new ChangeLogInterceptor() );
list.add( new OperationalAttributeInterceptor() );
list.add( new SchemaInterceptor() );
list.add( new SubentryInterceptor() );
list.add( new CollectiveAttributeInterceptor() );
list.add( new EventInterceptor() );
list.add( new TriggerInterceptor() );
setInterceptors( list );
}
public LdapContext getJndiContext() throws NamingException
{
return this.getJndiContext( null, null, null, AuthenticationLevel.NONE.toString(), "" );
}
public LdapContext getJndiContext( String dn ) throws NamingException
{
return this.getJndiContext( null, null, null, AuthenticationLevel.NONE.toString(), dn );
}
public LdapContext getJndiContext( LdapPrincipal principal ) throws NamingException
{
return new ServerLdapContext( this, principal, new LdapDN() );
}
public LdapContext getJndiContext( LdapPrincipal principal, String dn ) throws NamingException
{
return new ServerLdapContext( this, principal, new LdapDN( dn ) );
}
public synchronized LdapContext getJndiContext( LdapDN principalDn, String principal, byte[] credential,
String authentication, String rootDN ) throws NamingException
{
checkSecuritySettings( principal, credential, authentication );
if ( !started )
{
return new DeadContext();
}
Hashtable<String, Object> environment = new Hashtable<String, Object>();
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 );
environment.put( DirectoryService.JNDI_KEY, this );
return new ServerLdapContext( this, environment );
}
public long revert() throws NamingException
{
if ( changeLog == null || ! changeLog.isEnabled() )
{
throw new IllegalStateException( "The change log must be enabled to revert to previous log revisions." );
}
Tag latest = changeLog.getLatest();
if ( null != latest )
{
if ( latest.getRevision() < changeLog.getCurrentRevision() )
{
return revert( latest.getRevision() );
}
else
{
LOG.info( "Ignoring request to revert without changes since the latest tag." );
return changeLog.getCurrentRevision();
}
}
throw new IllegalStateException( "There must be at least one tag to revert to the latest tag." );
}
public long revert( long revision ) throws NamingException
{
if ( changeLog == null || ! changeLog.isEnabled() )
{
throw new IllegalStateException( "The change log must be enabled to revert to previous log revisions." );
}
if ( revision < 0 )
{
throw new IllegalArgumentException( "revision must be greater than or equal to 0" );
}
if ( revision >= changeLog.getChangeLogStore().getCurrentRevision() )
{
throw new IllegalArgumentException( "revision must be less than the current revision" );
}
DirContext ctx = getJndiContext( new LdapPrincipal( adminDn, AuthenticationLevel.SIMPLE ) );
Cursor<ChangeLogEvent> cursor = changeLog.getChangeLogStore().findAfter( revision );
/*
* BAD, BAD, BAD!!!
*
* No synchronization no nothing. Just getting this to work for now
* so we can revert tests. Any production grade use of this feature
* needs to synchronize on all changes while the revert is in progress.
*
* How about making this operation transactional?
*
* First of all just stop using JNDI and construct the operations to
* feed into the interceptor pipeline.
*/
try
{
LOG.warn( PARTIAL_IMPL_WARNING );
cursor.afterLast();
while ( cursor.previous() ) // apply ldifs in reverse order
{
ChangeLogEvent event = cursor.get();
LdifEntry reverse = event.getReverseLdif();
switch( reverse.getChangeType().getChangeType() )
{
case( ChangeType.ADD_ORDINAL ):
ctx.createSubcontext( reverse.getDn(), reverse.getAttributes() );
break;
case( ChangeType.DELETE_ORDINAL ):
ctx.destroySubcontext( reverse.getDn() );
break;
case( ChangeType.MODIFY_ORDINAL ):
ctx.modifyAttributes( reverse.getDn(), reverse.getModificationItemsArray() );
break;
case( ChangeType.MODDN_ORDINAL ):
// NOT BREAK - both ModDN and ModRDN handling is the same
case( ChangeType.MODRDN_ORDINAL ):
if ( reverse.isDeleteOldRdn() )
{
ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
}
else
{
ctx.addToEnvironment( "java.naming.ldap.deleteRDN", "true" );
}
ctx.rename( reverse.getDn(), event.getForwardLdif().getDn() );
break;
default:
throw new NotImplementedException( "Reverts of change type " + reverse.getChangeType()
+ " has not yet been implemented!");
}
}
}
catch ( IOException e )
{
throw new NamingException( "Encountered a failure while trying to revert to a previous revision: "
+ revision );
}
return changeLog.getCurrentRevision();
}
/**
* @throws NamingException if the LDAP server cannot be started
*/
public synchronized void startup() throws NamingException
{
if ( started )
{
return;
}
if ( shutdownHookEnabled )
{
Runtime.getRuntime().addShutdownHook( new Thread( new Runnable()
{
public void run()
{
try
{
shutdown();
}
catch ( NamingException e )
{
LOG.warn( "Failed to shut down the directory service: "
+ DefaultDirectoryService.this.instanceId, e );
}
}
}, "ApacheDS Shutdown Hook (" + instanceId + ')' ) );
LOG.info( "ApacheDS shutdown hook has been registered with the runtime." );
}
else if ( LOG.isWarnEnabled() )
{
LOG.warn( "ApacheDS shutdown hook has NOT been registered with the runtime."
+ " This default setting for standalone operation has been overriden." );
}
initialize();
firstStart = createBootstrapEntries();
showSecurityWarnings();
started = true;
adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
if ( !testEntries.isEmpty() )
{
createTestEntries();
}
}
public synchronized void sync() throws NamingException
{
if ( !started )
{
return;
}
this.changeLog.sync();
this.partitionNexus.sync();
}
public synchronized void shutdown() throws NamingException
{
if ( !started )
{
return;
}
this.changeLog.sync();
this.changeLog.destroy();
this.partitionNexus.sync();
this.partitionNexus.destroy();
this.interceptorChain.destroy();
this.started = false;
setDefaultInterceptorConfigurations();
}
public Registries getRegistries()
{
return registries;
}
public void setRegistries( Registries registries )
{
this.registries = registries;
}
public SchemaService getSchemaService()
{
return schemaService;
}
public void setSchemaService( SchemaService schemaService )
{
this.schemaService = schemaService;
}
public PartitionNexus getPartitionNexus()
{
return partitionNexus;
}
public InterceptorChain getInterceptorChain()
{
return interceptorChain;
}
public boolean isFirstStart()
{
return firstStart;
}
public boolean isStarted()
{
return started;
}
public ServerEntry newEntry( LdapDN dn ) throws NamingException
{
return new DefaultServerEntry( registries, dn );
}
/**
* Checks to make sure security environment parameters are set correctly.
*
* @throws javax.naming.NamingException if the security settings are not correctly configured.
* @param authentication the mechanism for authentication
* @param credential the password
* @param principal the distinguished name of the principal
*/
private void checkSecuritySettings( String principal, byte[] credential, String authentication )
throws NamingException
{
if ( authentication == null )
{
authentication = "";
}
/*
* If bind is strong make sure we have the principal name
* set within the environment, otherwise complain
*/
if ( AuthenticationLevel.STRONG.toString().equalsIgnoreCase( authentication ) )
{
if ( principal == null )
{
throw new LdapConfigurationException( "missing required " + Context.SECURITY_PRINCIPAL
+ " property for strong authentication" );
}
}
/*
* If bind is simple make sure we have the credentials and the
* principal name set within the environment, otherwise complain
*/
else if ( AuthenticationLevel.SIMPLE.toString().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 ( AuthenticationLevel.NONE.toString().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 ( !allowAnonymousAccess )
{
throw new LdapNoPermissionException( "Anonymous access disabled." );
}
}
else
{
/*
* If bind is anything other than strong, simple, or none we need to complain
*/
throw new LdapAuthenticationNotSupportedException( "Unknown authentication type: '" + authentication + "'",
ResultCodeEnum.AUTH_METHOD_NOT_SUPPORTED );
}
}
/**
* 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.
*
* @return true if the bootstrap entries had to be created, false otherwise
* @throws javax.naming.NamingException if entries cannot be created
*/
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( new EntryOperationContext( registries, PartitionNexus.getAdminName() ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, PartitionNexus.getAdminName() );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT,
SchemaConstants.TOP_OC,
SchemaConstants.PERSON_OC,
SchemaConstants.ORGANIZATIONAL_PERSON_OC,
SchemaConstants.INET_ORG_PERSON_OC );
serverEntry.put( SchemaConstants.UID_AT, PartitionNexus.ADMIN_UID );
serverEntry.put( SchemaConstants.USER_PASSWORD_AT, PartitionNexus.ADMIN_PASSWORD_BYTES );
serverEntry.put( SchemaConstants.DISPLAY_NAME_AT, "Directory Superuser" );
serverEntry.put( SchemaConstants.CN_AT, "system administrator" );
serverEntry.put( SchemaConstants.SN_AT, "administrator" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
serverEntry.put( SchemaConstants.DISPLAY_NAME_AT, "Directory Superuser" );
TlsKeyGenerator.addKeyPair( serverEntry );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
// -------------------------------------------------------------------
// create system users area
// -------------------------------------------------------------------
Map<String,OidNormalizer> oidsMap = registries.getAttributeTypeRegistry().getNormalizerMapping();
LdapDN userDn = new LdapDN( ServerDNConstants.USERS_SYSTEM_DN );
userDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, userDn ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, userDn );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT,
SchemaConstants.TOP_OC,
SchemaConstants.ORGANIZATIONAL_UNIT_OC );
serverEntry.put( SchemaConstants.OU_AT, "users" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
// -------------------------------------------------------------------
// create system groups area
// -------------------------------------------------------------------
LdapDN groupDn = new LdapDN( ServerDNConstants.GROUPS_SYSTEM_DN );
groupDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, groupDn ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, groupDn );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT,
SchemaConstants.TOP_OC,
SchemaConstants.ORGANIZATIONAL_UNIT_OC );
serverEntry.put( SchemaConstants.OU_AT, "groups" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
// -------------------------------------------------------------------
// create administrator group
// -------------------------------------------------------------------
LdapDN name = new LdapDN( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
name.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, name ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, name );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT,
SchemaConstants.TOP_OC,
SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC );
serverEntry.put( SchemaConstants.CN_AT, "Administrators" );
serverEntry.put( SchemaConstants.UNIQUE_MEMBER_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
Interceptor authzInterceptor = interceptorChain.get( AciAuthorizationInterceptor.class.getName() );
if ( authzInterceptor == null )
{
LOG.error( "The Authorization service is null : this is not allowed" );
throw new NamingException( "The Authorization service is null" );
}
if ( !( authzInterceptor instanceof AciAuthorizationInterceptor ) )
{
LOG.error( "The Authorization service is not set correctly : '{}' is an incorect interceptor",
authzInterceptor.getClass().getName() );
throw new NamingException( "The Authorization service is incorrectly set" );
}
AciAuthorizationInterceptor authzSrvc = ( AciAuthorizationInterceptor ) authzInterceptor;
authzSrvc.cacheNewGroup( name, serverEntry );
}
// -------------------------------------------------------------------
// create system configuration area
// -------------------------------------------------------------------
LdapDN configurationDn = new LdapDN( "ou=configuration,ou=system" );
configurationDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, configurationDn ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, configurationDn );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
serverEntry.put( SchemaConstants.OU_AT, "configuration" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
// -------------------------------------------------------------------
// create system configuration area for partition information
// -------------------------------------------------------------------
LdapDN partitionsDn = new LdapDN( "ou=partitions,ou=configuration,ou=system" );
partitionsDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, partitionsDn ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, partitionsDn );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
serverEntry.put( SchemaConstants.OU_AT, "partitions" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
// -------------------------------------------------------------------
// create system configuration area for services
// -------------------------------------------------------------------
LdapDN servicesDn = new LdapDN( "ou=services,ou=configuration,ou=system" );
servicesDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, servicesDn ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, servicesDn );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
serverEntry.put( SchemaConstants.OU_AT, "services" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
// -------------------------------------------------------------------
// create system configuration area for interceptors
// -------------------------------------------------------------------
LdapDN interceptorsDn = new LdapDN( "ou=interceptors,ou=configuration,ou=system" );
interceptorsDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, interceptorsDn ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, interceptorsDn );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
serverEntry.put( SchemaConstants.OU_AT, "interceptors" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
// -------------------------------------------------------------------
// create system preferences area
// -------------------------------------------------------------------
LdapDN sysPrefRootDn = new LdapDN( ServerDNConstants.SYSPREFROOT_SYSTEM_DN );
sysPrefRootDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( new EntryOperationContext( registries, sysPrefRootDn ) ) )
{
firstStart = true;
ServerEntry serverEntry = new DefaultServerEntry( registries, sysPrefRootDn );
serverEntry.put( SchemaConstants.OBJECT_CLASS_AT,
SchemaConstants.TOP_OC,
SchemaConstants.ORGANIZATIONAL_UNIT_OC,
SchemaConstants.EXTENSIBLE_OBJECT_OC );
serverEntry.put( "prefNodeName", "sysPrefRoot" );
serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
partitionNexus.add( new AddOperationContext( registries, serverEntry ) );
}
return firstStart;
}
/**
* Displays security warning messages if any possible secutiry issue is found.
* @throws NamingException if there are failures parsing and accessing internal structures
*/
private void showSecurityWarnings() throws NamingException
{
// Warn if the default password is not changed.
boolean needToChangeAdminPassword = false;
LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN );
adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
ServerEntry adminEntry = partitionNexus.lookup( new LookupOperationContext( registries, adminDn ) );
Object userPassword = adminEntry.get( SchemaConstants.USER_PASSWORD_AT ).get();
if ( userPassword instanceof byte[] )
{
needToChangeAdminPassword = Arrays.equals( PartitionNexus.ADMIN_PASSWORD_BYTES, ( byte[] ) userPassword );
}
else if ( userPassword.toString().equals( PartitionNexus.ADMIN_PASSWORD_STRING ) )
{
needToChangeAdminPassword = PartitionNexus.ADMIN_PASSWORD_STRING.equals( userPassword.toString() );
}
if ( needToChangeAdminPassword )
{
LOG.warn( "You didn't change the admin password of directory service " + "instance '" + instanceId + "'. "
+ "Please update the admin password as soon as possible " + "to prevent a possible security breach." );
}
}
/**
* Adds test entries into the core.
*
* @todo this may no longer be needed when JNDI is not used for bootstrapping
*
* @throws NamingException if the creation of test entries fails.
*/
private void createTestEntries() throws NamingException
{
LdapPrincipal principal = new LdapPrincipal( adminDn, AuthenticationLevel.SIMPLE );
ServerLdapContext ctx = new ServerLdapContext( this, principal, new LdapDN() );
for ( LdifEntry testEntry : testEntries )
{
try
{
LdifEntry entry = testEntry.clone();
Attributes attributes = entry.getAttributes();
String dn = entry.getDn();
try
{
ctx.createSubcontext( dn, attributes );
}
catch ( Exception e )
{
LOG.warn( dn + " test entry already exists.", e );
}
}
catch ( CloneNotSupportedException cnse )
{
LOG.warn( "Cannot clone the entry ", cnse );
}
}
}
/**
* Kicks off the initialization of the entire system.
*
* @throws javax.naming.NamingException if there are problems along the way
*/
private void initialize() throws NamingException
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( "---> Initializing the DefaultDirectoryService " );
}
// --------------------------------------------------------------------
// If not present extract schema partition from jar
// --------------------------------------------------------------------
File schemaDirectory = new File( workingDirectory, "schema" );
SchemaPartitionExtractor extractor;
if ( ! schemaDirectory.exists() )
{
try
{
extractor = new SchemaPartitionExtractor( workingDirectory );
extractor.extract();
}
catch ( IOException e )
{
NamingException ne = new NamingException( "Failed to extract pre-loaded schema partition." );
ne.setRootCause( e );
throw ne;
}
}
// --------------------------------------------------------------------
// Initialize schema partition
// --------------------------------------------------------------------
JdbmPartition schemaPartition = new JdbmPartition();
schemaPartition.setId( "schema" );
schemaPartition.setCacheSize( 1000 );
DbFileListing listing;
try
{
listing = new DbFileListing();
}
catch( IOException e )
{
throw new LdapNamingException( "Got IOException while trying to read DBFileListing: " + e.getMessage(),
ResultCodeEnum.OTHER );
}
Set<Index> indexedAttributes = new HashSet<Index>();
for ( String attributeId : listing.getIndexedAttributes() )
{
indexedAttributes.add( new JdbmIndex( attributeId ) );
}
schemaPartition.setIndexedAttributes( indexedAttributes );
schemaPartition.setSuffix( ServerDNConstants.OU_SCHEMA_DN );
ServerEntry entry = new DefaultServerEntry( registries, new LdapDN( ServerDNConstants.OU_SCHEMA_DN ) );
entry.put( SchemaConstants.OBJECT_CLASS_AT,
SchemaConstants.TOP_OC,
SchemaConstants.ORGANIZATIONAL_UNIT_OC );
entry.put( SchemaConstants.OU_AT, "schema" );
schemaPartition.setContextEntry( entry );
schemaPartition.init( this );
// --------------------------------------------------------------------
// Enable schemas of all indices of partition configurations
// --------------------------------------------------------------------
/*
* We need to make sure that every attribute indexed by a partition is
* loaded into the registries on the next step. So here we must enable
* the schemas of those attributes so they are loaded into the global
* registries.
*/
SchemaPartitionDao dao = new SchemaPartitionDao( schemaPartition, registries );
Map<String,Schema> schemaMap = dao.getSchemas();
Set<Partition> partitions = new HashSet<Partition>();
partitions.add( systemPartition );
partitions.addAll( this.partitions );
for ( Partition partition : partitions )
{
if ( partition instanceof BTreePartition )
{
JdbmPartition btpconf = ( JdbmPartition ) partition;
for ( Index index : btpconf.getIndexedAttributes() )
{
String schemaName = dao.findSchema( index.getAttributeId() );
if ( schemaName == null )
{
throw new NamingException( "Index on unidentified attribute: " + index.toString() );
}
Schema schema = schemaMap.get( schemaName );
if ( schema.isDisabled() )
{
dao.enableSchema( schemaName );
}
}
}
}
// --------------------------------------------------------------------
// Initialize schema subsystem and reset registries
// --------------------------------------------------------------------
PartitionSchemaLoader schemaLoader = new PartitionSchemaLoader( schemaPartition, registries );
Registries globalRegistries = new DefaultRegistries( "global", schemaLoader, registries.getOidRegistry() );
schemaLoader.loadEnabled( globalRegistries );
registries = globalRegistries;
SerializableComparator.setRegistry( globalRegistries.getComparatorRegistry() );
SchemaOperationControl schemaControl = new SchemaOperationControl( registries, schemaLoader,
new SchemaPartitionDao( schemaPartition, registries ) );
schemaService = new SchemaService( registries, schemaPartition, schemaControl );
partitionNexus = new DefaultPartitionNexus( new DefaultServerEntry( registries, LdapDN.EMPTY_LDAPDN ) );
partitionNexus.init( this );
partitionNexus.addContextPartition( new AddContextPartitionOperationContext( registries, schemaPartition ) );
interceptorChain = new InterceptorChain();
interceptorChain.init( this );
if ( changeLog.isEnabled() )
{
changeLog.init( this );
}
if ( LOG.isDebugEnabled() )
{
LOG.debug( "<--- DefaultDirectoryService initialized" );
}
}
/**
* Read an entry (without DN)
*
* @param text The ldif format file
* @return An Attributes.
*/
private Attributes readEntry( String text )
{
StringReader strIn = new StringReader( text );
BufferedReader in = new BufferedReader( strIn );
String line = null;
Attributes attributes = new AttributesImpl( true );
try
{
while ( ( line = in.readLine() ) != null )
{
if ( line.length() == 0 )
{
continue;
}
String addedLine = line.trim();
if ( StringTools.isEmpty( addedLine ) )
{
continue;
}
Attribute attribute = LdifReader.parseAttributeValue( addedLine );
Attribute oldAttribute = attributes.get( attribute.getID() );
if ( oldAttribute != null )
{
try
{
oldAttribute.add( attribute.get() );
attributes.put( oldAttribute );
}
catch (NamingException ne)
{
// Do nothing
}
}
else
{
attributes.put( attribute );
}
}
}
catch (IOException ioe)
{
// Do nothing : we can't reach this point !
}
return attributes;
}
/**
* Create a new ServerEntry
*
* @param ldif The String representing the attributes, as a LDIF file
* @param dn The DN for this new entry
*/
public ServerEntry newEntry( String ldif, String dn )
{
try
{
Attributes entry = readEntry( ldif );
ServerEntry serverEntry = ServerEntryUtils.toServerEntry( entry, new LdapDN( dn ), registries );
return serverEntry;
}
catch ( Exception e )
{
LOG.error( "Cannot build an entry for '{}' and this DN :'{}'", ldif, dn );
// do nothing
return null;
}
}
}