/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions copyright 2011-2013 ForgeRock AS
*/
/**
* This class defines a connection handler that will be used for communicating
* with clients over LDAP. It is actually implemented in two parts: as a
* connection handler and one or more request handlers. The connection handler
* is responsible for accepting new connections and registering each of them
* with a request handler. The request handlers then are responsible for reading
* requests from the clients and parsing them as operations. A single request
* handler may be used, but having multiple handlers might provide better
* performance in a multi-CPU system.
*/
public final class LDAPConnectionHandler extends
ConnectionHandler<LDAPConnectionHandlerCfg> implements
{
/**
* Task run periodically by the connection finalizer.
*/
{
public void run()
{
{
for (Runnable r : connectionFinalizerActiveJobQueue)
{
r.run();
}
}
// Switch the lists.
synchronized (connectionFinalizerLock)
{
}
}
}
/**
* The tracer object for the debug logger.
*/
/**
* Default friendly name for the LDAP connection handler.
*/
/** SSL instance name used in context creation. */
/** The current configuration state. */
/* Properties that cannot be modified dynamically */
/** The set of addresses on which to listen for new connections. */
/** The port on which this connection handler should listen for requests. */
private int listenPort;
/** The SSL client auth policy used by this connection handler. */
/** The backlog that will be used for the accept queue. */
private int backlog;
/** Indicates whether to allow the reuse address socket option. */
private boolean allowReuseAddress;
/**
* The number of request handlers that should be used for this connection
* handler.
*/
private int numRequestHandlers;
/**
* Indicates whether the Directory Server is in the process of shutting down.
*/
private volatile boolean shutdownRequested;
/* Internal LDAP connection handler state */
/** Indicates whether this connection handler is enabled. */
private boolean enabled;
/** The set of clients that are explicitly allowed access to the server. */
/**
* The set of clients that have been explicitly denied access to the server.
*/
/**
* The index to the request handler that will be used for the next connection
* accepted by the server.
*/
private int requestHandlerIndex;
/** The set of listeners for this connection handler. */
/**
* The set of request handlers that are associated with this connection
* handler.
*/
/** The set of statistics collected for this connection handler. */
/**
* The client connection monitor provider associated with this connection
* handler.
*/
/**
* The selector that will be used to multiplex connection acceptance across
* multiple sockets by a single thread.
*/
/** The unique name assigned to this connection handler. */
/** The protocol used by this connection handler. */
/** Queueing strategy. */
/**
* The condition variable that will be used by the start method to wait for
* the socket port to be opened and ready to process requests before
* returning.
*/
/** The friendly name of this connection handler. */
/**
* SSL context.
*
* @see LDAPConnectionHandler#sslEngine
*/
/** The SSL engine is used for obtaining default SSL parameters. */
/**
* Connection finalizer thread.
* <p>
* This thread is defers closing clients for approximately 100ms. This gives
* the client a chance to close the connection themselves before the server
* thus avoiding leaving the server side in the TIME WAIT state.
*/
/**
* Creates a new instance of this LDAP connection handler. It must be
* initialized before it may be used.
*/
public LDAPConnectionHandler()
{
}
/**
* Creates a new instance of this LDAP connection handler, using a queueing
* strategy. It must be initialized before it may be used.
*
* @param strategy
* Request handling strategy.
* @param friendlyName
* The name of of this connection handler, or {@code null} if the
* name should be taken from the configuration.
*/
{
+ " Thread");
this.friendlyName = friendlyName;
this.queueingStrategy = strategy;
// No real implementation is required. Do all the work in the
// initializeConnectionHandler method.
}
/**
* Indicates whether this connection handler should allow interaction with
* LDAPv2 clients.
*
* @return <CODE>true</CODE> if LDAPv2 is allowed, or <CODE>false</CODE> if
* not.
*/
public boolean allowLDAPv2()
{
return currentConfig.isAllowLDAPV2();
}
/**
* Indicates whether this connection handler should allow the use of the
* StartTLS extended operation.
*
* @return <CODE>true</CODE> if StartTLS is allowed, or <CODE>false</CODE> if
* not.
*/
public boolean allowStartTLS()
{
}
/**
* {@inheritDoc}
*/
{
// Create variables to include in the response.
boolean adminActionRequired = false;
// Note that the following properties cannot be modified:
//
// * listen port and addresses
// * use ssl
// * ssl policy
// * ssl cert nickname
// * accept backlog
// * tcp reuse address
// * num request handler
// Clear the stat tracker if LDAPv2 is being enabled.
{
if (config.isAllowLDAPV2())
{
}
}
// Apply the changes.
// Reconfigure SSL if needed.
try
{
}
catch (DirectoryException e)
{
if (debugEnabled())
{
}
messages);
}
if (config.isAllowLDAPV2())
{
}
else
{
}
}
throws DirectoryException
{
{
}
else
{
sslContext = null;
}
}
/**
* {@inheritDoc}
*/
{
shutdownRequested = true;
if (connMonitor != null)
{
}
if (statTracker != null)
{
}
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
}
{
}
// Shutdown the connection finalizer and ensure that any pending
// unclosed connections are closed.
synchronized (connectionFinalizerLock)
{
Runnable r = new ConnectionFinalizerRunnable();
r.run(); // Flush active queue.
r.run(); // Flush pending queue.
}
}
/**
* Retrieves information about the set of alerts that this generator may
* produce. The map returned should be between the notification type for a
* particular notification and the human-readable description for that
* notification. This alert generator must not generate any alerts with types
* that are not contained in this list.
*
* @return Information about the set of alerts that this generator may
* produce.
*/
{
return alerts;
}
/**
* Retrieves the fully-qualified name of the Java class for this alert
* generator implementation.
*
* @return The fully-qualified name of the Java class for this alert generator
* implementation.
*/
{
return LDAPConnectionHandler.class.getName();
}
/**
* Retrieves the set of active client connections that have been established
* through this connection handler.
*
* @return The set of active client connections that have been established
* through this connection handler.
*/
{
{
}
return connectionList;
}
/**
* Retrieves the DN of the configuration entry with which this alert generator
* is associated.
*
* @return The DN of the configuration entry with which this alert generator
* is associated.
*/
{
return currentConfig.dn();
}
/**
* {@inheritDoc}
*/
{
return handlerName;
}
/**
* {@inheritDoc}
*/
{
{
}
return super.getEnabledSSLCipherSuites();
}
/**
* {@inheritDoc}
*/
{
{
}
return super.getEnabledSSLProtocols();
}
/**
* {@inheritDoc}
*/
{
return listeners;
}
/**
* Retrieves the port on which this connection handler is listening for client
* connections.
*
* @return The port on which this connection handler is listening for client
* connections.
*/
public int getListenPort()
{
return listenPort;
}
/**
* Retrieves the maximum length of time in milliseconds that attempts to write
* to LDAP client connections should be allowed to block.
*
* @return The maximum length of time in milliseconds that attempts to write
* to LDAP client connections should be allowed to block, or zero if
* there should not be any limit imposed.
*/
public long getMaxBlockedWriteTimeLimit()
{
return currentConfig.getMaxBlockedWriteTimeLimit();
}
/**
* Retrieves the maximum ASN.1 element value length that will be allowed by
* this connection handler.
*
* @return The maximum ASN.1 element value length that will be allowed by this
* connection handler.
*/
public int getMaxRequestSize()
{
return (int) currentConfig.getMaxRequestSize();
}
/**
* Retrieves the size in bytes of the LDAP response message write buffer
* defined for this connection handler.
*
* @return The size in bytes of the LDAP response message write buffer.
*/
public int getBufferSize()
{
return (int) currentConfig.getBufferSize();
}
/**
* {@inheritDoc}
*/
{
return protocol;
}
/**
* {@inheritDoc}
*/
{
return handlerName;
}
/**
* Retrieves the SSL client authentication policy for this connection handler.
*
* @return The SSL client authentication policy for this connection handler.
*/
{
return sslClientAuthPolicy;
}
/**
* Retrieves the set of statistics maintained by this connection handler.
*
* @return The set of statistics maintained by this connection handler.
*/
{
return statTracker;
}
/**
* {@inheritDoc}
*/
{
if (friendlyName == null)
{
}
// Open the selector.
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
throw new InitializationException(message, e);
}
// Save this configuration for future reference.
requestHandlerIndex = 0;
// Configure SSL if needed.
try
{
}
catch (DirectoryException e)
{
if (debugEnabled())
{
}
throw new InitializationException(e.getMessageObject());
}
// Save properties that cannot be dynamically modified.
// Construct a unique name for this connection handler, and put
// together the set of listeners.
for (InetAddress a : listenAddresses)
{
}
// Attempt to bind to the listen port on all configured addresses to
// verify whether the connection handler will be able to start.
if (errorMessage != null)
{
throw new InitializationException(errorMessage);
}
// Create a system property to store the LDAP(S) port the server is
// listening to. This information can be displayed with jinfo.
// Create and start a connection finalizer thread for this
// connection handler.
"LDAP Connection Finalizer for connection handler " + toString()));
// Create and start the request handlers.
for (int i = 0; i < numRequestHandlers; i++)
{
requestHandlers[i] = new LDAPRequestHandler(this, i);
}
for (int i = 0; i < numRequestHandlers; i++)
{
requestHandlers[i].start();
}
// Register the set of supported LDAP versions.
if (config.isAllowLDAPV2())
{
}
// Create and register monitors.
connMonitor = new ClientConnectionMonitorProvider(this);
// Register this as a change listener.
config.addLDAPChangeListener(this);
}
/**
* {@inheritDoc}
*/
@Override()
{
if ((currentConfig == null)
{
// Attempt to bind to the listen port on all configured addresses to
// verify whether the connection handler will be able to start.
if (errorMessage != null)
{
return false;
}
}
{
// Check that the SSL configuration is valid.
{
try
{
}
catch (DirectoryException e)
{
if (debugEnabled())
{
}
return false;
}
}
}
return true;
}
/**
* Checks whether any listen address is in use for the given port. The check
* is performed by binding to each address and port.
*
* @param listenAddresses
* the listen {@link InetAddress} to test
* @param listenPort
* the listen port to test
* @param allowReuseAddress
* whether addresses can be reused
* @param configEntryDN
* the configuration entry DN
* @return an error message if at least one of the address is already in use,
* null otherwise.
*/
{
for (InetAddress a : listenAddresses)
{
try
{
{
}
}
catch (IOException e)
{
if (debugEnabled())
{
}
getExceptionMessage(e));
}
}
return null;
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationChangeAcceptable(
{
}
/**
* Indicates whether this connection handler should maintain usage statistics.
*
* @return <CODE>true</CODE> if this connection handler should maintain usage
* statistics, or <CODE>false</CODE> if not.
*/
public boolean keepStats()
{
return currentConfig.isKeepStats();
}
/**
* {@inheritDoc}
*/
{
shutdownRequested = true;
try
{
{
try
{
}
{
}
}
}
{
}
}
/**
* {@inheritDoc}
*/
public void start()
{
// The Directory Server start process should only return
// when the connection handlers port are fully opened
// and working. The start method therefore needs to wait for
// the created thread to
synchronized (waitListen)
{
super.start();
try
{
waitListen.wait();
}
catch (InterruptedException e)
{
// If something interrupted the start its probably better
// to return ASAP.
}
}
}
/**
* Operates in a loop, accepting new connections and ensuring that requests on
* those connections are handled properly.
*/
public void run()
{
boolean listening = false;
while (!shutdownRequested)
{
// If this connection handler is not enabled, then just sleep
// for a bit and check again.
if (!enabled)
{
if (listening)
{
listening = false;
}
continue;
}
// If we have gotten here, then we are about to start listening
// for the first time since startup or since we were previously
// disabled. Make sure to start with a clean selector and then
// create all the listeners.
try
{
int numRegistered = registerChannels();
// At this point, the connection Handler either started
// correctly or failed to start but the start process
// should be notified and resume its work in any cases.
synchronized (waitListen)
{
waitListen.notify();
}
// If none of the listeners were created successfully, then
// consider the connection handler disabled and require
// administrative action before trying again.
if (numRegistered == 0)
{
enabled = false;
continue;
}
listening = true;
// Enter a loop, waiting for new connections to arrive and
// then accepting them as they come in.
boolean lastIterationFailed = false;
while (enabled && (!shutdownRequested))
{
try
{
lastIterationFailed = false;
}
catch (Exception e)
{
if (debugEnabled())
{
}
if (lastIterationFailed)
{
// The last time through the accept loop we also
// encountered a failure. Rather than enter a potential
// infinite loop of failures, disable this acceptor and
// log an error.
message);
enabled = false;
}
else
{
lastIterationFailed = true;
}
}
}
if (shutdownRequested)
{
listening = false;
enabled = false;
}
}
catch (Exception e)
{
if (debugEnabled())
{
}
// This is very bad because we failed outside the loop. The
// only thing we can do here is log a message, send an alert,
// and disable the selector until an administrator can figure
// out what's going on.
enabled = false;
}
}
}
/**
* Serves the incoming connections.
*
* @throws IOException
* @throws DirectoryException
*/
{
// We can't rely on return value of select to determine if any keys
// are ready.
{
if (key.isAcceptable())
{
// Accept the new client connection.
.channel();
if (clientChannel != null)
{
}
}
&& debugEnabled())
{
// Selected keys was non empty but select() returned 0.
// Log warning and hope it blocks on the next select() call.
+ "Selected Keys: %d, Interest Ops: %d, Ready Ops: %d ",
}
}
}
/**
* Open channels for each listen address and register them against this
* ConnectionHandler's {@link Selector}.
*
* @return the number of successfully registered channel
*/
private int registerChannels()
{
int numRegistered = 0;
for (InetAddress a : listenAddresses)
{
try
{
channel.configureBlocking(false);
}
catch (Exception e)
{
if (debugEnabled())
{
}
}
}
return numRegistered;
}
throws DirectoryException
{
try
{
}
catch (SocketException se)
{
// just close it and ignore.
}
// Check to see if the core server rejected the
// connection (e.g., already too many connections
// established).
clientChannel, getProtocol());
{
return;
}
// Check to see if the client is on the denied list.
// If so, then reject it immediately.
if ((!deniedClients.isEmpty())
{
.getServerHostPort()));
return;
}
// Check to see if there is an allowed list and if
// there is whether the client is on that list. If
// not, then reject the connection.
if ((!allowedClients.isEmpty())
{
return;
}
// If we've gotten here, then we'll take the
// connection so invoke the post-connect plugins and
// register the client connection with a request
// handler.
try
{
if (!pluginResult.continueProcessing())
{
return;
}
{
requestHandlerIndex = 0;
}
}
catch (Exception e)
{
if (debugEnabled())
{
}
getExceptionMessage(e));
}
}
/**
* Appends a string representation of this connection handler to the provided
* buffer.
*
* @param buffer
* The buffer to which the information should be appended.
*/
{
}
/**
* Indicates whether this connection handler should use SSL to communicate
* with clients.
*
* @return {@code true} if this connection handler should use SSL to
* communicate with clients, or {@code false} if not.
*/
public boolean useSSL()
{
return currentConfig.isUseSSL();
}
/**
* Cleans up the contents of the selector, closing any server socket channels
* that might be associated with it. Any connections that might have been
* established through those channels should not be impacted.
*/
private void cleanUpSelector()
{
try
{
{
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
}
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
}
}
}
catch (Exception e)
{
if (debugEnabled())
{
}
}
}
/**
* Get the queueing strategy.
*
* @return The queueing strategy.
*/
{
return queueingStrategy;
}
/**
* Creates a TLS Byte Channel instance using the specified socket channel.
*
* @param channel
* The socket channel to use in the creation.
* @return A TLS Byte Channel instance.
* @throws DirectoryException
* If the channel cannot be created.
*/
throws DirectoryException
{
}
{
try
{
sslEngine.setUseClientMode(false);
{
}
{
}
switch (config.getSSLClientAuthPolicy())
{
case DISABLED:
sslEngine.setNeedClientAuth(false);
sslEngine.setWantClientAuth(false);
break;
case REQUIRED:
sslEngine.setWantClientAuth(true);
sslEngine.setNeedClientAuth(true);
break;
case OPTIONAL:
default:
sslEngine.setNeedClientAuth(false);
sslEngine.setWantClientAuth(true);
break;
}
return sslEngine;
}
catch (Exception e)
{
if (debugEnabled())
{
}
.get(getExceptionMessage(e));
}
}
throws DirectoryException
{
try
{
if (keyManagerProvider == null)
{
keyManagerProvider = new NullKeyManagerProvider();
}
{
}
else
{
}
if (trustManagerProvider == null)
{
}
null);
return sslContext;
}
catch (Exception e)
{
if (debugEnabled())
{
}
.get(getExceptionMessage(e));
}
}
/**
* Enqueue a connection finalizer which will be invoked after a short delay.
*
* @param r
* The connection finalizer runnable.
*/
{
synchronized (connectionFinalizerLock)
{
if (connectionFinalizer != null)
{
}
else
{
// Already finalized - invoked immediately.
r.run();
}
}
}
}