blob: 3dcf6007e42df98f509a04dfc8824b5144ce4b18 [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.ldap;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.util.*;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.Control;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.core.security.CoreKeyStoreSpi;
import org.apache.directory.server.ldap.handlers.AbandonHandler;
import org.apache.directory.server.ldap.handlers.AddHandler;
import org.apache.directory.server.ldap.handlers.BindHandler;
import org.apache.directory.server.ldap.handlers.CompareHandler;
import org.apache.directory.server.ldap.handlers.DefaultAbandonHandler;
import org.apache.directory.server.ldap.handlers.DefaultAddHandler;
import org.apache.directory.server.ldap.handlers.DefaultBindHandler;
import org.apache.directory.server.ldap.handlers.DefaultCompareHandler;
import org.apache.directory.server.ldap.handlers.DefaultDeleteHandler;
import org.apache.directory.server.ldap.handlers.DefaultExtendedHandler;
import org.apache.directory.server.ldap.handlers.DefaultModifyDnHandler;
import org.apache.directory.server.ldap.handlers.DefaultModifyHandler;
import org.apache.directory.server.ldap.handlers.DefaultSearchHandler;
import org.apache.directory.server.ldap.handlers.DefaultUnbindHandler;
import org.apache.directory.server.ldap.handlers.DeleteHandler;
import org.apache.directory.server.ldap.handlers.ExtendedHandler;
import org.apache.directory.server.ldap.handlers.ModifyDnHandler;
import org.apache.directory.server.ldap.handlers.ModifyHandler;
import org.apache.directory.server.ldap.handlers.SearchHandler;
import org.apache.directory.server.ldap.handlers.UnbindHandler;
import org.apache.directory.server.ldap.handlers.bind.*;
import org.apache.directory.server.ldap.handlers.ssl.LdapsInitializer;
import org.apache.directory.server.protocol.shared.DirectoryBackedService;
import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
import org.apache.directory.shared.asn1.codec.Asn1CodecDecoder;
import org.apache.directory.shared.asn1.codec.Asn1CodecEncoder;
import org.apache.directory.shared.ldap.constants.SaslQoP;
import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
import org.apache.directory.shared.ldap.message.AbandonRequest;
import org.apache.directory.shared.ldap.message.AddRequest;
import org.apache.directory.shared.ldap.message.BindRequest;
import org.apache.directory.shared.ldap.message.CascadeControl;
import org.apache.directory.shared.ldap.message.CompareRequest;
import org.apache.directory.shared.ldap.message.DeleteRequest;
import org.apache.directory.shared.ldap.message.EntryChangeControl;
import org.apache.directory.shared.ldap.message.ExtendedRequest;
import org.apache.directory.shared.ldap.message.ExtendedRequestImpl;
import org.apache.directory.shared.ldap.message.ManageDsaITControl;
import org.apache.directory.shared.ldap.message.MessageDecoder;
import org.apache.directory.shared.ldap.message.MessageEncoder;
import org.apache.directory.shared.ldap.message.ModifyDnRequest;
import org.apache.directory.shared.ldap.message.ModifyRequest;
import org.apache.directory.shared.ldap.message.MutableControl;
import org.apache.directory.shared.ldap.message.PersistentSearchControl;
import org.apache.directory.shared.ldap.message.Request;
import org.apache.directory.shared.ldap.message.ResponseCarryingMessageException;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.message.ResultResponse;
import org.apache.directory.shared.ldap.message.ResultResponseRequest;
import org.apache.directory.shared.ldap.message.SearchRequest;
import org.apache.directory.shared.ldap.message.SubentriesControl;
import org.apache.directory.shared.ldap.message.UnbindRequest;
import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;
import org.apache.directory.shared.ldap.message.spi.BinaryAttributeDetector;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.mina.common.DefaultIoFilterChainBuilder;
import org.apache.mina.common.IoFilterChain;
import org.apache.mina.common.IoFilterChainBuilder;
import org.apache.mina.common.IoHandler;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.ThreadModel;
import org.apache.mina.common.WriteFuture;
import org.apache.mina.filter.SSLFilter;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.handler.demux.DemuxingIoHandler;
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
import org.apache.mina.util.SessionLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An LDAP protocol provider implementation which dynamically associates
* handlers.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
* @org.apache.xbean.XBean
*/
public class LdapServer extends DirectoryBackedService
{
@SuppressWarnings( { "UnusedDeclaration" } )
private static final long serialVersionUID = 3757127143811666817L;
/** logger for this class */
private static final Logger LOG = LoggerFactory.getLogger( LdapServer.class.getName() );
/** The default maximum size limit. */
private static final int MAX_SIZE_LIMIT_DEFAULT = 100;
/** The default maximum time limit. */
private static final int MAX_TIME_LIMIT_DEFAULT = 10000;
/**
* The default service pid.
*/
private static final String SERVICE_PID_DEFAULT = "org.apache.directory.server.ldap";
/**
* The default service name.
*/
private static final String SERVICE_NAME_DEFAULT = "ApacheDS LDAP Service";
/** The default IP port. */
private static final int IP_PORT_DEFAULT = 389;
/** the constant service name of this ldap protocol provider **/
public static final String SERVICE_NAME = "ldap";
/** a set of supported controls */
private Set<String> supportedControls;
/** The maximum size limit. */
private int maxSizeLimit = MAX_SIZE_LIMIT_DEFAULT; // set to default value
/** The maximum time limit. */
private int maxTimeLimit = MAX_TIME_LIMIT_DEFAULT; // set to default value (milliseconds)
/** Whether LDAPS is enabled. */
private boolean enableLdaps;
/** Whether to allow anonymous access: allowed by default */
private boolean allowAnonymousAccess = true;
/**
* Whether or not confidentiality (TLS secured connection) is required:
* disabled by default.
*/
private boolean confidentialityRequired;
/** The extended operation handlers. */
private final Collection<ExtendedOperationHandler> extendedOperationHandlers =
new ArrayList<ExtendedOperationHandler>();
/** The supported authentication mechanisms. */
private Map<String, MechanismHandler> saslMechanismHandlers =
new HashMap<String, MechanismHandler>();
/** The name of this host, validated during SASL negotiation. */
private String saslHost = "ldap.example.com";
/** The service principal, used by GSSAPI. */
private String saslPrincipal = "ldap/ldap.example.com@EXAMPLE.COM";
/** The quality of protection (QoP), used by DIGEST-MD5 and GSSAPI. */
private Set<String> saslQop;
private String saslQopString;
/** The list of realms serviced by this host. */
private List<String> saslRealms;
private AbandonHandler abandonHandler;
private AddHandler addHandler;
private BindHandler bindHandler;
private CompareHandler compareHandler;
private DeleteHandler deleteHandler;
private ExtendedHandler extendedHandler;
private ModifyHandler modifyHandler;
private ModifyDnHandler modifyDnHandler;
private SearchHandler searchHandler;
private UnbindHandler unbindHandler;
private SessionRegistry registry;
/** the underlying provider codec factory */
private ProtocolCodecFactory codecFactory;
/** the MINA protocol handler */
private final LdapProtocolHandler handler = new LdapProtocolHandler();
/** tracks state of the server */
private boolean started;
/**
* Creates an LDAP protocol provider.
*/
public LdapServer()
{
super.setIpPort( IP_PORT_DEFAULT );
super.setEnabled( true );
super.setServiceId( SERVICE_PID_DEFAULT );
super.setServiceName( SERVICE_NAME_DEFAULT );
saslQop = new HashSet<String>();
saslQop.add( SaslQoP.QOP_AUTH );
saslQop.add( SaslQoP.QOP_AUTH_INT );
saslQop.add( SaslQoP.QOP_AUTH_CONF );
saslQopString = SaslQoP.QOP_AUTH + ',' + SaslQoP.QOP_AUTH_INT + ',' + SaslQoP.QOP_AUTH_CONF;
saslRealms = new ArrayList<String>();
saslRealms.add( "example.com" );
this.supportedControls = new HashSet<String>();
this.supportedControls.add( PersistentSearchControl.CONTROL_OID );
this.supportedControls.add( EntryChangeControl.CONTROL_OID );
this.supportedControls.add( SubentriesControl.CONTROL_OID );
this.supportedControls.add( ManageDsaITControl.CONTROL_OID );
this.supportedControls.add( CascadeControl.CONTROL_OID );
}
/**
* Install the LDAP request handlers.
*/
private void installDefaultHandlers()
{
if ( getAbandonHandler() == null )
{
setAbandonHandler( new DefaultAbandonHandler() );
}
if ( getAddHandler() == null )
{
setAddHandler( new DefaultAddHandler() );
}
if ( getBindHandler() == null )
{
DefaultBindHandler handler = new DefaultBindHandler();
handler.setSessionRegistry( registry );
handler.setSaslMechanismHandlers( saslMechanismHandlers );
setBindHandler( handler );
}
if ( getCompareHandler() == null )
{
setCompareHandler( new DefaultCompareHandler() );
}
if ( getDeleteHandler() == null )
{
setDeleteHandler( new DefaultDeleteHandler() );
}
if ( getExtendedHandler() == null )
{
setExtendedHandler( new DefaultExtendedHandler() );
}
if ( getModifyHandler() == null )
{
setModifyHandler( new DefaultModifyHandler() );
}
if ( getModifyDnHandler() == null )
{
setModifyDnHandler( new DefaultModifyDnHandler() );
}
if ( getSearchHandler() == null )
{
setSearchHandler( new DefaultSearchHandler() );
}
if ( getUnbindHandler() == null )
{
setUnbindHandler( new DefaultUnbindHandler() );
}
}
/**
* @throws IOException if we cannot bind to the specified port
* @throws NamingException if the LDAP server cannot be started
*/
public void start() throws NamingException, IOException
{
if ( ! isEnabled() )
{
return;
}
IoFilterChainBuilder chain;
if ( isEnableLdaps() )
{
Provider provider = Security.getProvider( "SUN" );
LOG.debug( "provider = {}", provider );
CoreKeyStoreSpi coreKeyStoreSpi = new CoreKeyStoreSpi( getDirectoryService() );
KeyStore keyStore = new KeyStore( coreKeyStoreSpi, provider, "JKS" ) {};
try
{
keyStore.load( null, null );
}
catch ( Exception e )
{
// nothing really happens with this keystore
}
chain = LdapsInitializer.init( keyStore );
}
else
{
chain = new DefaultIoFilterChainBuilder();
}
/*
* The serveur is now initialized, we can
* install the default requests handlers, which need
* access to the DirectoryServer instance.
*/
installDefaultHandlers();
startLDAP0( getIpPort(), chain );
started = true;
}
public void stop()
{
try
{
// we should unbind the service before we begin sending the notice
// of disconnect so new connections are not formed while we process
List<WriteFuture> writeFutures = new ArrayList<WriteFuture>();
// 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<IoSession> sessions;
try
{
sessions = new ArrayList<IoSession>(
getSocketAcceptor().getManagedSessions( new InetSocketAddress( getIpPort() ) ) );
}
catch ( IllegalArgumentException e )
{
LOG.warn( "Seems like the LDAP service (" + getIpPort() + ") has already been unbound." );
return;
}
getSocketAcceptor().unbind( new InetSocketAddress( getIpPort() ) );
if ( LOG.isInfoEnabled() )
{
LOG.info( "Unbind of an LDAP service (" + getIpPort() + ") 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 ( IoSession session:sessions )
{
writeFutures.add( session.write( NoticeOfDisconnect.UNAVAILABLE ) );
}
}
// And close the connections when the NoDs are sent.
Iterator<IoSession> sessionIt = sessions.iterator();
for ( WriteFuture future:writeFutures )
{
future.join( 1000 );
sessionIt.next().close();
}
}
catch ( Exception e )
{
LOG.warn( "Failed to sent NoD.", e );
}
}
private void startLDAP0( int port, IoFilterChainBuilder chainBuilder )
throws NamingException
{
PartitionNexus nexus = getDirectoryService().getPartitionNexus();
for ( ExtendedOperationHandler h : extendedOperationHandlers )
{
extendedHandler.addHandler( h );
LOG.info( "Added Extended Request Handler: " + h.getOid() );
h.setLdapProvider( this );
nexus.registerSupportedExtensions( h.getExtensionOids() );
}
nexus.registerSupportedSaslMechanisms( saslMechanismHandlers.keySet() );
try
{
SocketAcceptorConfig acceptorCfg = new SocketAcceptorConfig();
// Disable the disconnection of the clients on unbind
acceptorCfg.setDisconnectOnUnbind( false );
acceptorCfg.setReuseAddress( true );
acceptorCfg.setFilterChainBuilder( chainBuilder );
acceptorCfg.setThreadModel( ThreadModel.MANUAL );
acceptorCfg.getSessionConfig().setTcpNoDelay( true );
getSocketAcceptor().bind( new InetSocketAddress( port ), getHandler(), acceptorCfg );
started = 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;
}
}
public String getName()
{
return SERVICE_NAME;
}
public ProtocolCodecFactory getCodecFactory()
{
return codecFactory;
}
public IoHandler getHandler()
{
return handler;
}
// ------------------------------------------------------------------------
// Configuration Methods
// ------------------------------------------------------------------------
/**
* Registeres the specified {@link ExtendedOperationHandler} to this
* protocol provider to provide a specific LDAP extended operation.
*
* @param eoh an extended operation handler
* @throws NamingException on failure to add the handler
*/
public void addExtendedOperationHandler( ExtendedOperationHandler eoh ) throws NamingException
{
if ( started )
{
extendedHandler.addHandler( eoh );
eoh.setLdapProvider( this );
PartitionNexus nexus = getDirectoryService().getPartitionNexus();
nexus.registerSupportedExtensions( eoh.getExtensionOids() );
}
else
{
extendedOperationHandlers.add( eoh );
}
}
/**
* Deregisteres an {@link ExtendedOperationHandler} with the specified <tt>oid</tt>
* from this protocol provider.
*
* @param oid the numeric identifier for the extended operation associated with
* the handler to remove
*/
public void removeExtendedOperationHandler( String oid )
{
if ( started )
{
extendedHandler.removeHandler( oid );
// need to do something like this to make this work right
// PartitionNexus nexus = getDirectoryService().getPartitionNexus();
// nexus.unregisterSupportedExtensions( eoh.getExtensionOids() );
}
else
{
ExtendedOperationHandler handler = null;
for ( ExtendedOperationHandler h : extendedOperationHandlers )
{
if ( h.getOid().equals( oid ) )
{
handler = h;
break;
}
}
extendedOperationHandlers.remove( handler );
}
}
/**
* Returns an {@link ExtendedOperationHandler} with the specified <tt>oid</tt>
* which is registered to this protocol provider.
*
* @param oid the oid of the extended request of associated with the extended
* request handler
* @return the exnteded operation handler
*/
public ExtendedOperationHandler getExtendedOperationHandler( String oid )
{
if ( started )
{
return extendedHandler.getHandler( oid );
}
else
{
for ( ExtendedOperationHandler h : extendedOperationHandlers )
{
if ( h.getOid().equals( oid ) )
{
return h;
}
}
}
return null;
}
/**
* Returns a {@link Map} of all registered OID-{@link ExtendedOperationHandler}
* pairs.
*
* @return map of all extended operation handlers
*/
public Map<String,ExtendedOperationHandler> getExtendedOperationHandlerMap()
{
return extendedHandler.getHandlerMap();
}
/**
* Returns <tt>true</tt> if LDAPS is enabled.
*
* @return True if LDAPS is enabled.
*/
public boolean isEnableLdaps()
{
return enableLdaps;
}
/**
* Sets if LDAPS is enabled or not.
*
* @param enableLdaps Whether LDAPS is enabled.
*/
public void setEnableLdaps( boolean enableLdaps )
{
this.enableLdaps = enableLdaps;
}
/**
* Returns <code>true</code> if anonymous access is allowed.
*
* @return True if anonymous access is allowed.
*/
public boolean isAllowAnonymousAccess()
{
return allowAnonymousAccess;
}
/**
* Sets whether to allow anonymous access or not.
*
* @param enableAnonymousAccess Set <code>true</code> to allow anonymous access.
*/
public void setAllowAnonymousAccess( boolean enableAnonymousAccess )
{
this.allowAnonymousAccess = enableAnonymousAccess;
}
/**
* Sets the maximum size limit in number of entries to return for search.
*
* @param maxSizeLimit the maximum number of entries to return for search
*/
public void setMaxSizeLimit( int maxSizeLimit )
{
this.maxSizeLimit = maxSizeLimit;
}
/**
* Returns the maximum size limit in number of entries to return for search.
*
* @return The maximum size limit.
*/
public int getMaxSizeLimit()
{
return maxSizeLimit;
}
/**
* Sets the maximum time limit in miliseconds to conduct a search.
*
* @param maxTimeLimit the maximum length of time in milliseconds for search
*/
public void setMaxTimeLimit( int maxTimeLimit )
{
this.maxTimeLimit = maxTimeLimit;
}
/**
* Returns the maximum time limit in milliseonds to conduct a search.
*
* @return The maximum time limit in milliseconds for search
*/
public int getMaxTimeLimit()
{
return maxTimeLimit;
}
/**
* Gets the {@link ExtendedOperationHandler}s.
*
* @return A collection of {@link ExtendedOperationHandler}s.
*/
public Collection<ExtendedOperationHandler> getExtendedOperationHandlers()
{
return new ArrayList<ExtendedOperationHandler>( extendedOperationHandlers );
}
/**
* Sets the {@link ExtendedOperationHandler}s.
*
* @org.apache.xbean.Property nestedType="org.apache.directory.server.ldap.ExtendedOperationHandler"
*
* @param handlers A collection of {@link ExtendedOperationHandler}s.
*/
public void setExtendedOperationHandlers( Collection<ExtendedOperationHandler> handlers )
{
this.extendedOperationHandlers.clear();
this.extendedOperationHandlers.addAll( handlers );
}
/**
* Returns the FQDN of this SASL host, validated during SASL negotiation.
*
* @return The FQDN of this SASL host, validated during SASL negotiation.
*/
public String getSaslHost()
{
return saslHost;
}
/**
* Sets the FQDN of this SASL host, validated during SASL negotiation.
*
* @param saslHost The FQDN of this SASL host, validated during SASL negotiation.
*/
public void setSaslHost( String saslHost )
{
this.saslHost = saslHost;
}
/**
* Returns the Kerberos principal name for this LDAP service, used by GSSAPI.
*
* @return The Kerberos principal name for this LDAP service, used by GSSAPI.
*/
public String getSaslPrincipal()
{
return saslPrincipal;
}
/**
* Sets the Kerberos principal name for this LDAP service, used by GSSAPI.
*
* @param saslPrincipal The Kerberos principal name for this LDAP service, used by GSSAPI.
*/
public void setSaslPrincipal( String saslPrincipal )
{
this.saslPrincipal = saslPrincipal;
}
/**
* Returns the quality-of-protection, used by DIGEST-MD5 and GSSAPI.
*
* @return The quality-of-protection, used by DIGEST-MD5 and GSSAPI.
*/
public String getSaslQopString()
{
return saslQopString;
}
/**
* Returns the Set of quality-of-protection, used by DIGEST-MD5 and GSSAPI.
*
* @return The quality-of-protection, used by DIGEST-MD5 and GSSAPI.
*/
public Set<String> getSaslQop()
{
return saslQop;
}
/**
* Sets the desired quality-of-protection, used by DIGEST-MD5 and GSSAPI.
*
* We build a string from this list, where QoP are comma delimited
*
* @org.apache.xbean.Property nestedType="java.lang.String"
*
* @param saslQop The desired quality-of-protection, used by DIGEST-MD5 and GSSAPI.
*/
public void setSaslQop( Set<String> saslQop )
{
StringBuilder qopList = new StringBuilder();
boolean isFirst = true;
for ( String qop:saslQop )
{
if ( isFirst )
{
isFirst = false;
}
else
{
qopList.append( ',' );
}
qopList.append( qop );
}
this.saslQopString = qopList.toString();
this.saslQop = saslQop;
}
/**
* Returns the realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
*
* @return The realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
*/
public List<String> getSaslRealms()
{
return saslRealms;
}
/**
* Sets the realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
*
* @org.apache.xbean.Property nestedType="java.lang.String"
*
* @param saslRealms The realms serviced by this SASL host, used by DIGEST-MD5 and GSSAPI.
*/
public void setSaslRealms( List<String> saslRealms )
{
this.saslRealms = saslRealms;
}
/**
* @org.apache.xbean.Map flat="true" dups="replace" keyName="mech-name"
*/
public Map<String, MechanismHandler> getSaslMechanismHandlers()
{
return saslMechanismHandlers;
}
public void setSaslMechanismHandlers( Map<String, MechanismHandler> saslMechanismHandlers )
{
this.saslMechanismHandlers = saslMechanismHandlers;
}
public MechanismHandler addSaslMechanismHandler( String mechanism, MechanismHandler handler )
{
return this.saslMechanismHandlers.put( mechanism, handler );
}
public MechanismHandler removeSaslMechanismHandler( String mechanism )
{
return this.saslMechanismHandlers.remove( mechanism );
}
public MechanismHandler getMechanismHandler( String mechanism )
{
return this.saslMechanismHandlers.get( mechanism );
}
public Set<String> getSupportedMechanisms()
{
return saslMechanismHandlers.keySet();
}
public void setDirectoryService( DirectoryService directoryService )
{
super.setDirectoryService( directoryService );
this.codecFactory = new ProtocolCodecFactoryImpl( directoryService );
Hashtable<String,Object> copy = new Hashtable<String,Object>();
copy.put( Context.PROVIDER_URL, "" );
copy.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.core.jndi.CoreContextFactory" );
copy.put( DirectoryService.JNDI_KEY, directoryService );
this.registry = new SessionRegistry( this, copy );
}
public Set<String> getSupportedControls()
{
return supportedControls;
}
public void setSupportedControls( Set<String> supportedControls )
{
this.supportedControls = supportedControls;
}
public AbandonHandler getAbandonHandler()
{
return abandonHandler;
}
public void setAbandonHandler( AbandonHandler abandonHandler )
{
this.handler.removeMessageHandler( AbandonRequest.class );
this.abandonHandler = abandonHandler;
this.abandonHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( AbandonRequest.class, this.abandonHandler );
}
public AddHandler getAddHandler()
{
return addHandler;
}
public void setAddHandler( AddHandler addHandler )
{
this.handler.removeMessageHandler( AddRequest.class );
this.addHandler = addHandler;
this.addHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( AddRequest.class, this.addHandler );
}
public BindHandler getBindHandler()
{
return bindHandler;
}
public void setBindHandler( BindHandler bindHandler )
{
this.handler.removeMessageHandler( BindRequest.class );
this.bindHandler = bindHandler;
this.bindHandler.setProtocolProvider( this );
this.bindHandler.setDirectoryService( getDirectoryService() );
//noinspection unchecked
this.handler.addMessageHandler( BindRequest.class, this.bindHandler );
}
public CompareHandler getCompareHandler()
{
return compareHandler;
}
public void setCompareHandler( CompareHandler compareHandler )
{
this.handler.removeMessageHandler( CompareRequest.class );
this.compareHandler = compareHandler;
this.compareHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( CompareRequest.class, this.compareHandler );
}
public DeleteHandler getDeleteHandler()
{
return deleteHandler;
}
public void setDeleteHandler( DeleteHandler deleteHandler )
{
this.handler.removeMessageHandler( DeleteRequest.class );
this.deleteHandler = deleteHandler;
this.deleteHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( DeleteRequest.class, this.deleteHandler );
}
public ExtendedHandler getExtendedHandler()
{
return extendedHandler;
}
public void setExtendedHandler( ExtendedHandler extendedHandler )
{
this.handler.removeMessageHandler( ExtendedRequest.class );
this.extendedHandler = extendedHandler;
this.extendedHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( ExtendedRequest.class, this.extendedHandler );
}
public ModifyHandler getModifyHandler()
{
return modifyHandler;
}
public void setModifyHandler( ModifyHandler modifyHandler )
{
this.handler.removeMessageHandler( ModifyRequest.class );
this.modifyHandler = modifyHandler;
this.modifyHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( ModifyRequest.class, this.modifyHandler );
}
public ModifyDnHandler getModifyDnHandler()
{
return modifyDnHandler;
}
public void setModifyDnHandler( ModifyDnHandler modifyDnHandler )
{
this.handler.removeMessageHandler( ModifyDnRequest.class );
this.modifyDnHandler = modifyDnHandler;
this.modifyDnHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( ModifyDnRequest.class, this.modifyDnHandler );
}
public SearchHandler getSearchHandler()
{
return searchHandler;
}
public void setSearchHandler( SearchHandler searchHandler )
{
this.handler.removeMessageHandler( SearchRequest.class );
this.searchHandler = searchHandler;
this.searchHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( SearchRequest.class, this.searchHandler );
}
public UnbindHandler getUnbindHandler()
{
return unbindHandler;
}
public void setUnbindHandler( UnbindHandler unbindHandler )
{
this.handler.removeMessageHandler( UnbindRequest.class );
this.unbindHandler = unbindHandler;
this.unbindHandler.setProtocolProvider( this );
//noinspection unchecked
this.handler.addMessageHandler( UnbindRequest.class, this.unbindHandler );
}
public SessionRegistry getRegistry()
{
return registry;
}
public boolean isStarted()
{
return started;
}
public void setStarted( boolean started )
{
this.started = started;
}
/**
* Sets the mode for this LdapServer to accept requests with or without a
* TLS secured connection via either StartTLS extended operations or using
* LDAPS.
*
* @param confidentialityRequired true to require confidentiality
*/
public void setConfidentialityRequired( boolean confidentialityRequired )
{
this.confidentialityRequired = confidentialityRequired;
}
/**
* Gets whether or not TLS secured connections are required to perform
* operations on this LdapServer.
*
* @return true if TLS secured connections are required, false otherwise
*/
public boolean isConfidentialityRequired()
{
return confidentialityRequired;
}
/**
* A snickers based BER Decoder factory.
*/
private static final class ProtocolCodecFactoryImpl implements ProtocolCodecFactory
{
final DirectoryService directoryService;
public ProtocolCodecFactoryImpl( DirectoryService directoryService )
{
this.directoryService = directoryService;
}
public ProtocolEncoder getEncoder()
{
return new Asn1CodecEncoder( new MessageEncoder() );
}
public ProtocolDecoder getDecoder()
{
return new Asn1CodecDecoder( new MessageDecoder( new BinaryAttributeDetector()
{
public boolean isBinary( String id )
{
AttributeTypeRegistry attrRegistry = directoryService.getRegistries().getAttributeTypeRegistry();
try
{
AttributeType type = attrRegistry.lookup( id );
return ! type.getSyntax().isHumanReadable();
}
catch ( NamingException e )
{
return false;
}
}
}) );
}
}
private class LdapProtocolHandler extends DemuxingIoHandler
{
public void sessionCreated( IoSession session ) throws Exception
{
session.setAttribute( LdapServer.class.toString(), LdapServer.this );
IoFilterChain filters = session.getFilterChain();
filters.addLast( "codec", new ProtocolCodecFilter( codecFactory ) );
}
public void sessionClosed( IoSession session )
{
registry.remove( session );
}
public void messageReceived( IoSession session, Object message ) throws Exception
{
// Translate SSLFilter messages into LDAP extended request
// defined in RFC #2830, 'Lightweight Directory Access Protocol (v3):
// Extension for Transport Layer Security'.
//
// The RFC specifies the payload should be empty, but we use
// it to notify the TLS state changes. This hack should be
// OK from the viewpointd of security because StartTLS
// handler should react to only SESSION_UNSECURED message
// and degrade authentication level to 'anonymous' as specified
// in the RFC, and this is no threat.
if ( message == SSLFilter.SESSION_SECURED )
{
ExtendedRequest req = new ExtendedRequestImpl( 0 );
req.setOid( "1.3.6.1.4.1.1466.20037" );
req.setPayload( "SECURED".getBytes( "ISO-8859-1" ) );
message = req;
}
else if ( message == SSLFilter.SESSION_UNSECURED )
{
ExtendedRequest req = new ExtendedRequestImpl( 0 );
req.setOid( "1.3.6.1.4.1.1466.20037" );
req.setPayload( "UNSECURED".getBytes( "ISO-8859-1" ) );
message = req;
}
if ( ( ( Request ) message ).getControls().size() > 0 && message instanceof ResultResponseRequest )
{
ResultResponseRequest req = ( ResultResponseRequest ) message;
for ( Control control1 : req.getControls().values() )
{
MutableControl control = ( MutableControl ) control1;
if ( control.isCritical() && !supportedControls.contains( control.getID() ) )
{
ResultResponse resp = req.getResultResponse();
resp.getLdapResult().setErrorMessage( "Unsupport critical control: " + control.getID() );
resp.getLdapResult().setResultCode( ResultCodeEnum.UNAVAILABLE_CRITICAL_EXTENSION );
session.write( resp );
return;
}
}
}
super.messageReceived( session, message );
}
public void exceptionCaught( IoSession session, Throwable cause )
{
if ( cause.getCause() instanceof ResponseCarryingMessageException )
{
ResponseCarryingMessageException rcme = ( ResponseCarryingMessageException ) cause.getCause();
if ( rcme.getResponse() != null )
{
session.write( rcme.getResponse() );
return;
}
}
SessionLog.warn( session,
"Unexpected exception forcing session to close: sending disconnect notice to client.", cause );
session.write( NoticeOfDisconnect.PROTOCOLERROR );
registry.remove( session );
session.close();
}
}
}