blob: d2a36750621309c770b579dbe05b1713947f047e [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 java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import org.apache.directory.server.core.authz.AuthorizationService;
import org.apache.directory.server.core.configuration.Configuration;
import org.apache.directory.server.core.configuration.ConfigurationException;
import org.apache.directory.server.core.configuration.StartupConfiguration;
import org.apache.directory.server.core.interceptor.Interceptor;
import org.apache.directory.server.core.interceptor.InterceptorChain;
import org.apache.directory.server.core.jndi.AbstractContextFactory;
import org.apache.directory.server.core.jndi.DeadContext;
import org.apache.directory.server.core.jndi.PropertyKeys;
import org.apache.directory.server.core.jndi.ServerLdapContext;
import org.apache.directory.server.core.partition.DefaultPartitionNexus;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.core.schema.AttributeTypeRegistry;
import org.apache.directory.server.core.schema.GlobalRegistries;
import org.apache.directory.server.core.schema.bootstrap.BootstrapRegistries;
import org.apache.directory.server.core.schema.bootstrap.BootstrapSchemaLoader;
import org.apache.directory.shared.ldap.exception.LdapAuthenticationNotSupportedException;
import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
import org.apache.directory.shared.ldap.ldif.Entry;
import org.apache.directory.shared.ldap.message.LockableAttributeImpl;
import org.apache.directory.shared.ldap.message.LockableAttributesImpl;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.directory.shared.ldap.util.DateUtils;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default implementation of {@link DirectoryService}.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
class DefaultDirectoryService extends DirectoryService
{
private static final Logger log = LoggerFactory.getLogger( DefaultDirectoryService.class );
private static final String BINARY_KEY = "java.naming.ldap.attributes.binary";
private final String instanceId;
private final DirectoryServiceConfiguration configuration = new DefaultDirectoryServiceConfiguration( this );
private DirectoryServiceListener 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 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 = false;
// ------------------------------------------------------------------------
// Constructor
// ------------------------------------------------------------------------
/**
* Creates a new instance.
*/
public DefaultDirectoryService(String instanceId)
{
if ( instanceId == null )
{
throw new NullPointerException( "instanceId" );
}
this.instanceId = instanceId;
}
// ------------------------------------------------------------------------
// BackendSubsystem Interface Method Implemetations
// ------------------------------------------------------------------------
public Context getJndiContext( String rootDN ) throws NamingException
{
return this.getJndiContext( null, null, null, "none", rootDN );
}
public synchronized Context getJndiContext( LdapDN principalDn, String principal, byte[] credential,
String authentication, String rootDN ) throws NamingException
{
checkSecuritySettings( principal, credential, authentication );
if ( !started )
{
return new DeadContext();
}
Hashtable environment = getEnvironment();
environment.remove( Context.SECURITY_PRINCIPAL );
environment.remove( Context.SECURITY_CREDENTIALS );
environment.remove( Context.SECURITY_AUTHENTICATION );
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 );
if ( principalDn != null )
{
environment.put( PropertyKeys.PARSED_BIND_DN, principalDn );
}
return new ServerLdapContext( this, environment );
}
public synchronized void startup( DirectoryServiceListener listener, Hashtable env ) throws NamingException
{
if ( started )
{
return;
}
Hashtable envCopy = ( Hashtable ) env.clone();
StartupConfiguration cfg = ( StartupConfiguration ) Configuration.toConfiguration( env );
if ( cfg.isShutdownHookEnabled() )
{
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." );
}
envCopy.put( Context.PROVIDER_URL, "" );
try
{
cfg.validate();
}
catch ( ConfigurationException e )
{
NamingException ne = new LdapConfigurationException( "Invalid configuration." );
ne.initCause( e );
throw ne;
}
this.environment = envCopy;
this.startupConfiguration = cfg;
listener.beforeStartup( this );
initialize();
firstStart = createBootstrapEntries();
showSecurityWarnings();
this.serviceListener = listener;
started = true;
if ( !startupConfiguration.getTestEntries().isEmpty() )
{
createTestEntries( env );
}
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
{
serviceListener.afterShutdown( this );
environment = null;
interceptorChain = null;
startupConfiguration = null;
}
}
public String getInstanceId()
{
return instanceId;
}
public DirectoryServiceConfiguration getConfiguration()
{
return configuration;
}
public Hashtable getEnvironment()
{
return ( Hashtable ) environment.clone();
}
public DirectoryServiceListener getServiceListener()
{
return serviceListener;
}
public StartupConfiguration getStartupConfiguration()
{
return startupConfiguration;
}
public GlobalRegistries getGlobalRegistries()
{
return globalRegistries;
}
public PartitionNexus 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( PartitionNexus.getAdminName() ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "person" );
objectClass.add( "organizationalPerson" );
objectClass.add( "inetOrgPerson" );
attributes.put( objectClass );
attributes.put( "uid", PartitionNexus.ADMIN_UID );
attributes.put( "userPassword", PartitionNexus.ADMIN_PASSWORD );
attributes.put( "displayName", "Directory Superuser" );
attributes.put( "cn", "system administrator" );
attributes.put( "sn", "administrator" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
attributes.put( "displayName", "Directory Superuser" );
partitionNexus.add(PartitionNexus.getAdminName(),
attributes );
}
// -------------------------------------------------------------------
// create system users area
// -------------------------------------------------------------------
Map oidsMap = configuration.getGlobalRegistries().getAttributeTypeRegistry().getNormalizerMapping();
LdapDN userDn = new LdapDN( "ou=users,ou=system" );
userDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( userDn ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
attributes.put( objectClass );
attributes.put( "ou", "users" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( userDn, attributes );
}
// -------------------------------------------------------------------
// create system groups area
// -------------------------------------------------------------------
LdapDN groupDn = new LdapDN( "ou=groups,ou=system" );
groupDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( groupDn ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
attributes.put( objectClass );
attributes.put( "ou", "groups" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( groupDn, attributes );
}
// -------------------------------------------------------------------
// create administrator group
// -------------------------------------------------------------------
String upName = "cn=Administrators,ou=groups,ou=system";
LdapDN normName = new LdapDN( "cn=administrators,ou=groups,ou=system" );
normName.normalize( oidsMap );
if ( !partitionNexus.hasEntry( normName ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "groupOfUniqueNames" );
attributes.put( objectClass );
attributes.put( "cn", "Administrators" );
attributes.put( "uniqueMember", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add(normName, attributes );
Interceptor authzInterceptor = interceptorChain.get( "authorizationService" );
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 AuthorizationService) )
{
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" );
}
AuthorizationService authzSrvc = ( AuthorizationService ) authzInterceptor;
authzSrvc.cacheNewGroup( upName, normName, attributes );
}
// -------------------------------------------------------------------
// create system configuration area
// -------------------------------------------------------------------
LdapDN configurationDn = new LdapDN( "ou=configuration,ou=system" );
configurationDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( configurationDn ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
attributes.put( objectClass );
attributes.put( "ou", "configuration" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( configurationDn, attributes );
}
// -------------------------------------------------------------------
// create system configuration area for partition information
// -------------------------------------------------------------------
LdapDN partitionsDn = new LdapDN( "ou=partitions,ou=configuration,ou=system" );
partitionsDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( partitionsDn ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
attributes.put( objectClass );
attributes.put( "ou", "partitions" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( partitionsDn, attributes );
}
// -------------------------------------------------------------------
// create system configuration area for services
// -------------------------------------------------------------------
LdapDN servicesDn = new LdapDN( "ou=services,ou=configuration,ou=system" );
servicesDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( servicesDn ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
attributes.put( objectClass );
attributes.put( "ou", "services" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( servicesDn, attributes );
}
// -------------------------------------------------------------------
// create system configuration area for interceptors
// -------------------------------------------------------------------
LdapDN interceptorsDn = new LdapDN( "ou=interceptors,ou=configuration,ou=system" );
interceptorsDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( interceptorsDn ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
attributes.put( objectClass );
attributes.put( "ou", "interceptors" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( interceptorsDn, attributes );
}
// -------------------------------------------------------------------
// create system preferences area
// -------------------------------------------------------------------
LdapDN sysPrefRootDn = new LdapDN( "prefNodeName=sysPrefRoot,ou=system");
sysPrefRootDn.normalize( oidsMap );
if ( !partitionNexus.hasEntry( sysPrefRootDn ) )
{
firstStart = true;
Attributes attributes = new LockableAttributesImpl();
Attribute objectClass = new LockableAttributeImpl( "objectClass" );
objectClass.add( "top" );
objectClass.add( "organizationalUnit" );
attributes.put( objectClass );
attributes.put( "objectClass", "extensibleObject" );
attributes.put( "prefNodeName", "sysPrefRoot" );
attributes.put( "creatorsName", PartitionNexus.ADMIN_PRINCIPAL_NORMALIZED );
attributes.put( "createTimestamp", DateUtils.getGeneralizedTime() );
partitionNexus.add( sysPrefRootDn, attributes );
}
return firstStart;
}
/**
* Displays security warning messages if any possible secutiry issue is found.
*/
private void showSecurityWarnings() throws NamingException
{
// Warn if the default password is not changed.
boolean needToChangeAdminPassword = false;
LdapDN adminDn = new LdapDN( PartitionNexus.ADMIN_PRINCIPAL );
adminDn.normalize( configuration.getGlobalRegistries().getAttributeTypeRegistry().getNormalizerMapping() );
Attributes adminEntry = partitionNexus.lookup( adminDn );
Object userPassword = adminEntry.get( "userPassword" ).get();
if ( userPassword instanceof byte[] )
{
needToChangeAdminPassword = PartitionNexus.ADMIN_PASSWORD.equals( new String(
( byte[] ) userPassword ) );
}
else if ( userPassword.toString().equals( PartitionNexus.ADMIN_PASSWORD ) )
{
needToChangeAdminPassword = PartitionNexus.ADMIN_PASSWORD.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." );
}
}
private void createTestEntries( Hashtable env ) throws NamingException
{
String principal = AbstractContextFactory.getPrincipal( env );
byte[] credential = AbstractContextFactory.getCredential( env );
String authentication = AbstractContextFactory.getAuthentication( env );
LdapDN principalDn = ( LdapDN ) env.get( PropertyKeys.PARSED_BIND_DN );
ServerLdapContext ctx = ( ServerLdapContext )
getJndiContext( principalDn, principal, credential, authentication, "" );
Iterator i = startupConfiguration.getTestEntries().iterator();
while ( i.hasNext() )
{
try
{
Entry entry = (Entry)( ( Entry ) i.next() ).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 " );
}
// --------------------------------------------------------------------
// 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 );
Set binaries = new HashSet();
if ( this.environment.containsKey( BINARY_KEY ) )
{
if ( log.isInfoEnabled() )
{
log.info( "Startup environment contains " + BINARY_KEY );
}
String binaryIds = ( String ) this.environment.get( BINARY_KEY );
if ( binaryIds == null )
{
if ( log.isWarnEnabled() )
{
log.warn( BINARY_KEY + " in startup environment contains null value. "
+ "Using only schema info to set binary attributeTypes." );
}
}
else
{
if ( !StringTools.isEmpty( binaryIds ) )
{
String[] binaryArray = binaryIds.split( " " );
for ( int i = 0; i < binaryArray.length; i++ )
{
binaries.add( StringTools.lowerCase( StringTools.trim( binaryArray[i] ) ) );
}
}
if ( log.isInfoEnabled() )
{
log.info( "Setting binaries to union of schema defined binaries and those provided in "
+ BINARY_KEY );
}
}
}
// now get all the attributeTypes that are binary from the registry
AttributeTypeRegistry registry = globalRegistries.getAttributeTypeRegistry();
Iterator list = registry.list();
while ( list.hasNext() )
{
AttributeType type = ( AttributeType ) list.next();
if ( !type.getSyntax().isHumanReadable() )
{
// add the OID for the attributeType
binaries.add( type.getOid() );
// add the lowercased name for the names for the attributeType
String[] names = type.getNames();
for ( int ii = 0; ii < names.length; ii++ )
{
binaries.add( StringTools.lowerCase( StringTools.trim( names[ii] ) ) );
}
}
}
this.environment.put( BINARY_KEY, binaries );
if ( log.isDebugEnabled() )
{
log.debug( "binary ids used: " + binaries );
}
partitionNexus = new DefaultPartitionNexus( new LockableAttributesImpl() );
partitionNexus.init( configuration, null );
interceptorChain = new InterceptorChain();
interceptorChain.init( configuration );
if ( log.isDebugEnabled() )
{
log.debug( "<--- DefaultDirectoryService initialized" );
}
}
}