LDAPConnectionHandler.java revision ea1068c292e9b341af6d6b563cd8988a96be20a9
/*
* 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 legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* 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 legal-notices/CDDLv1_0.txt.
* 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-2015 ForgeRock AS
*/
package org.opends.server.protocols.ldap;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.*;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.AddressMask;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.ConnectionHandlerCfg;
import org.opends.server.admin.std.server.LDAPConnectionHandlerCfg;
import org.opends.server.api.*;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.QueueingStrategy;
import org.opends.server.core.WorkQueueStrategy;
import org.opends.server.extensions.NullKeyManagerProvider;
import org.opends.server.extensions.NullTrustManagerProvider;
import org.opends.server.extensions.TLSByteChannel;
import org.opends.server.monitors.ClientConnectionMonitorProvider;
import org.opends.server.types.*;
import org.opends.server.util.SelectableCertificateKeyManager;
import org.opends.server.util.StaticUtils;
/**
* 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
ConfigurationChangeListener<LDAPConnectionHandlerCfg>,
ServerShutdownListener, AlertGenerator
{
/**
* Task run periodically by the connection finalizer.
*/
private final class ConnectionFinalizerRunnable implements Runnable
{
@Override
public void run()
{
if (!connectionFinalizerActiveJobQueue.isEmpty())
{
for (Runnable r : connectionFinalizerActiveJobQueue)
{
r.run();
}
connectionFinalizerActiveJobQueue.clear();
}
// Switch the lists.
synchronized (connectionFinalizerLock)
{
List<Runnable> tmp = connectionFinalizerActiveJobQueue;
connectionFinalizerActiveJobQueue = connectionFinalizerPendingJobQueue;
connectionFinalizerPendingJobQueue = tmp;
}
}
}
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/**
* Default friendly name for the LDAP connection handler.
*/
private static final String DEFAULT_FRIENDLY_NAME = "LDAP Connection Handler";
/** SSL instance name used in context creation. */
private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS";
/** The current configuration state. */
private LDAPConnectionHandlerCfg currentConfig;
/* Properties that cannot be modified dynamically */
/** The set of addresses on which to listen for new connections. */
private Set<InetAddress> listenAddresses;
/** The port on which this connection handler should listen for requests. */
private int listenPort;
/** The SSL client auth policy used by this connection handler. */
private SSLClientAuthPolicy sslClientAuthPolicy;
/** 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. */
private Collection<AddressMask> allowedClients;
/**
* The set of clients that have been explicitly denied access to the server.
*/
private Collection<AddressMask> deniedClients;
/**
* 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. */
private List<HostPort> listeners;
/**
* The set of request handlers that are associated with this connection
* handler.
*/
private LDAPRequestHandler[] requestHandlers;
/** The set of statistics collected for this connection handler. */
private LDAPStatistics statTracker;
/**
* The client connection monitor provider associated with this connection
* handler.
*/
private ClientConnectionMonitorProvider connMonitor;
/**
* The selector that will be used to multiplex connection acceptance across
* multiple sockets by a single thread.
*/
private Selector selector;
/** The unique name assigned to this connection handler. */
private String handlerName;
/** The protocol used by this connection handler. */
private String protocol;
/** Queueing strategy. */
private final QueueingStrategy queueingStrategy;
/**
* 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.
*/
private final Object waitListen = new Object();
/** The friendly name of this connection handler. */
private String friendlyName;
/**
* SSL context.
*
* @see LDAPConnectionHandler#sslEngine
*/
private SSLContext sslContext;
/** The SSL engine is used for obtaining default SSL parameters. */
private SSLEngine sslEngine;
/**
* 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.
*/
private final Object connectionFinalizerLock = new Object();
private ScheduledExecutorService connectionFinalizer;
private List<Runnable> connectionFinalizerActiveJobQueue;
private List<Runnable> connectionFinalizerPendingJobQueue;
/**
* Creates a new instance of this LDAP connection handler. It must be
* initialized before it may be used.
*/
public LDAPConnectionHandler()
{
this(new WorkQueueStrategy(), null); // Use name from configuration.
}
/**
* 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.
*/
public LDAPConnectionHandler(QueueingStrategy strategy, String friendlyName)
{
super(friendlyName != null ? friendlyName : DEFAULT_FRIENDLY_NAME
+ " 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()
{
return currentConfig.isAllowStartTLS() && !currentConfig.isUseSSL();
}
/**
* {@inheritDoc}
*/
@Override
public ConfigChangeResult applyConfigurationChange(
LDAPConnectionHandlerCfg config)
{
final ConfigChangeResult ccr = new ConfigChangeResult();
// 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 (currentConfig.isAllowLDAPV2() != config.isAllowLDAPV2())
{
if (config.isAllowLDAPV2())
{
statTracker.clearStatistics();
}
}
// Apply the changes.
currentConfig = config;
enabled = config.isEnabled();
allowedClients = config.getAllowedClient();
deniedClients = config.getDeniedClient();
// Reconfigure SSL if needed.
try
{
configureSSL(config);
}
catch (DirectoryException e)
{
logger.traceException(e);
ccr.setResultCode(e.getResultCode());
ccr.addMessage(e.getMessageObject());
return ccr;
}
if (config.isAllowLDAPV2())
{
DirectoryServer.registerSupportedLDAPVersion(2, this);
}
else
{
DirectoryServer.deregisterSupportedLDAPVersion(2, this);
}
return ccr;
}
private void configureSSL(LDAPConnectionHandlerCfg config)
throws DirectoryException
{
protocol = config.isUseSSL() ? "LDAPS" : "LDAP";
if (config.isUseSSL() || config.isAllowStartTLS())
{
sslContext = createSSLContext(config);
sslEngine = createSSLEngine(config, sslContext);
}
else
{
sslContext = null;
sslEngine = null;
}
}
/**
* {@inheritDoc}
*/
@Override
public void finalizeConnectionHandler(LocalizableMessage finalizeReason)
{
shutdownRequested = true;
currentConfig.removeLDAPChangeListener(this);
if (connMonitor != null)
{
DirectoryServer.deregisterMonitorProvider(connMonitor);
}
if (statTracker != null)
{
DirectoryServer.deregisterMonitorProvider(statTracker);
}
DirectoryServer.deregisterSupportedLDAPVersion(2, this);
DirectoryServer.deregisterSupportedLDAPVersion(3, this);
try
{
selector.wakeup();
}
catch (Exception e)
{
logger.traceException(e);
}
for (LDAPRequestHandler requestHandler : requestHandlers)
{
requestHandler.processServerShutdown(finalizeReason);
}
// Shutdown the connection finalizer and ensure that any pending
// unclosed connections are closed.
synchronized (connectionFinalizerLock)
{
connectionFinalizer.shutdown();
connectionFinalizer = null;
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.
*/
@Override
public Map<String, String> getAlerts()
{
Map<String, String> alerts = new LinkedHashMap<String, String>();
alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES);
alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR,
ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR);
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.
*/
@Override
public String getClassName()
{
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.
*/
@Override
public Collection<ClientConnection> getClientConnections()
{
List<ClientConnection> connectionList = new LinkedList<ClientConnection>();
for (LDAPRequestHandler requestHandler : requestHandlers)
{
connectionList.addAll(requestHandler.getClientConnections());
}
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.
*/
@Override
public DN getComponentEntryDN()
{
return currentConfig.dn();
}
/**
* {@inheritDoc}
*/
@Override
public String getConnectionHandlerName()
{
return handlerName;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getEnabledSSLCipherSuites()
{
final SSLEngine engine = sslEngine;
if (engine != null)
{
return Arrays.asList(engine.getEnabledCipherSuites());
}
return super.getEnabledSSLCipherSuites();
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getEnabledSSLProtocols()
{
final SSLEngine engine = sslEngine;
if (engine != null)
{
return Arrays.asList(engine.getEnabledProtocols());
}
return super.getEnabledSSLProtocols();
}
/**
* {@inheritDoc}
*/
@Override
public Collection<HostPort> getListeners()
{
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}
*/
@Override
public String getProtocol()
{
return protocol;
}
/**
* {@inheritDoc}
*/
@Override
public String getShutdownListenerName()
{
return handlerName;
}
/**
* Retrieves the SSL client authentication policy for this connection handler.
*
* @return The SSL client authentication policy for this connection handler.
*/
public SSLClientAuthPolicy getSSLClientAuthPolicy()
{
return sslClientAuthPolicy;
}
/**
* Retrieves the set of statistics maintained by this connection handler.
*
* @return The set of statistics maintained by this connection handler.
*/
public LDAPStatistics getStatTracker()
{
return statTracker;
}
/**
* {@inheritDoc}
*/
@Override
public void initializeConnectionHandler(LDAPConnectionHandlerCfg config)
throws ConfigException, InitializationException
{
if (friendlyName == null)
{
friendlyName = config.dn().rdn().getAttributeValue(0).toString();
}
// Open the selector.
try
{
selector = Selector.open();
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message = ERR_LDAP_CONNHANDLER_OPEN_SELECTOR_FAILED.get(
config.dn(), stackTraceToSingleLineString(e));
throw new InitializationException(message, e);
}
// Save this configuration for future reference.
currentConfig = config;
enabled = config.isEnabled();
requestHandlerIndex = 0;
allowedClients = config.getAllowedClient();
deniedClients = config.getDeniedClient();
// Configure SSL if needed.
try
{
configureSSL(config);
}
catch (DirectoryException e)
{
logger.traceException(e);
throw new InitializationException(e.getMessageObject());
}
// Save properties that cannot be dynamically modified.
allowReuseAddress = config.isAllowTCPReuseAddress();
backlog = config.getAcceptBacklog();
listenAddresses = config.getListenAddress();
listenPort = config.getListenPort();
numRequestHandlers =
getNumRequestHandlers(config.getNumRequestHandlers(), friendlyName);
// Construct a unique name for this connection handler, and put
// together the set of listeners.
listeners = new LinkedList<HostPort>();
StringBuilder nameBuffer = new StringBuilder();
nameBuffer.append(friendlyName);
for (InetAddress a : listenAddresses)
{
listeners.add(new HostPort(a.getHostAddress(), listenPort));
nameBuffer.append(" ");
nameBuffer.append(a.getHostAddress());
}
nameBuffer.append(" port ");
nameBuffer.append(listenPort);
handlerName = nameBuffer.toString();
// Attempt to bind to the listen port on all configured addresses to
// verify whether the connection handler will be able to start.
LocalizableMessage errorMessage =
checkAnyListenAddressInUse(listenAddresses, listenPort,
allowReuseAddress, config.dn());
if (errorMessage != null)
{
logger.error(errorMessage);
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.
System.setProperty(protocol + "_port", String.valueOf(listenPort));
// Create and start a connection finalizer thread for this
// connection handler.
connectionFinalizer = Executors
.newSingleThreadScheduledExecutor(new DirectoryThread.Factory(
"LDAP Connection Finalizer for connection handler " + toString()));
connectionFinalizerActiveJobQueue = new ArrayList<Runnable>();
connectionFinalizerPendingJobQueue = new ArrayList<Runnable>();
connectionFinalizer.scheduleWithFixedDelay(
new ConnectionFinalizerRunnable(), 100, 100, TimeUnit.MILLISECONDS);
// Create and start the request handlers.
requestHandlers = new LDAPRequestHandler[numRequestHandlers];
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.
DirectoryServer.registerSupportedLDAPVersion(3, this);
if (config.isAllowLDAPV2())
{
DirectoryServer.registerSupportedLDAPVersion(2, this);
}
// Create and register monitors.
statTracker = new LDAPStatistics(handlerName + " Statistics");
DirectoryServer.registerMonitorProvider(statTracker);
connMonitor = new ClientConnectionMonitorProvider(this);
DirectoryServer.registerMonitorProvider(connMonitor);
// Register this as a change listener.
config.addLDAPChangeListener(this);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration,
List<LocalizableMessage> unacceptableReasons)
{
LDAPConnectionHandlerCfg config = (LDAPConnectionHandlerCfg) configuration;
if ((currentConfig == null)
|| (!currentConfig.isEnabled() && config.isEnabled()))
{
// Attempt to bind to the listen port on all configured addresses to
// verify whether the connection handler will be able to start.
LocalizableMessage errorMessage =
checkAnyListenAddressInUse(config.getListenAddress(), config
.getListenPort(), config.isAllowTCPReuseAddress(), config.dn());
if (errorMessage != null)
{
unacceptableReasons.add(errorMessage);
return false;
}
}
if (config.isEnabled())
{
// Check that the SSL configuration is valid.
if (config.isUseSSL() || config.isAllowStartTLS())
{
try
{
SSLContext sslContext = createSSLContext(config);
createSSLEngine(config, sslContext);
}
catch (DirectoryException e)
{
logger.traceException(e);
unacceptableReasons.add(e.getMessageObject());
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.
*/
private LocalizableMessage checkAnyListenAddressInUse(
Collection<InetAddress> listenAddresses, int listenPort,
boolean allowReuseAddress, DN configEntryDN)
{
for (InetAddress a : listenAddresses)
{
try
{
if (StaticUtils.isAddressInUse(a, listenPort, allowReuseAddress))
{
throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
}
}
catch (IOException e)
{
logger.traceException(e);
return ERR_CONNHANDLER_CANNOT_BIND.get("LDAP", configEntryDN, a.getHostAddress(), listenPort,
getExceptionMessage(e));
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isConfigurationChangeAcceptable(
LDAPConnectionHandlerCfg config, List<LocalizableMessage> unacceptableReasons)
{
return isConfigurationAcceptable(config, unacceptableReasons);
}
/**
* 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}
*/
@Override
public void processServerShutdown(LocalizableMessage reason)
{
shutdownRequested = true;
try
{
for (LDAPRequestHandler requestHandler : requestHandlers)
{
try
{
requestHandler.processServerShutdown(reason);
}
catch (Exception ignored)
{
}
}
}
catch (Exception ignored)
{
}
}
/**
* {@inheritDoc}
*/
@Override
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.
*/
@Override
public void run()
{
setName(handlerName);
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)
{
cleanUpSelector();
listening = false;
logger.info(NOTE_CONNHANDLER_STOPPED_LISTENING, handlerName);
}
StaticUtils.sleep(1000);
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
{
cleanUpSelector();
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)
{
logger.error(ERR_LDAP_CONNHANDLER_NO_ACCEPTORS, currentConfig.dn());
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
{
serveIncomingConnections();
lastIterationFailed = false;
}
catch (Exception e)
{
logger.traceException(e);
logger.error(ERR_CONNHANDLER_CANNOT_ACCEPT_CONNECTION, friendlyName,
currentConfig.dn(), getExceptionMessage(e));
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.
LocalizableMessage message =
ERR_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.get(friendlyName,
currentConfig.dn(), stackTraceToSingleLineString(e));
logger.error(message);
DirectoryServer.sendAlertNotification(this,
ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
message);
cleanUpSelector();
enabled = false;
}
else
{
lastIterationFailed = true;
}
}
}
if (shutdownRequested)
{
cleanUpSelector();
selector.close();
listening = false;
enabled = false;
}
}
catch (Exception e)
{
logger.traceException(e);
// 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.
LocalizableMessage message =
ERR_LDAP_CONNHANDLER_UNCAUGHT_ERROR.get(currentConfig.dn(), stackTraceToSingleLineString(e));
logger.error(message);
DirectoryServer.sendAlertNotification(this,
ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR, message);
cleanUpSelector();
enabled = false;
}
}
}
/**
* Serves the incoming connections.
*
* @throws IOException
* @throws DirectoryException
*/
private void serveIncomingConnections() throws IOException, DirectoryException
{
int selectorState = selector.select();
// We can't rely on return value of select to determine if any keys
// are ready.
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4850373
for (Iterator<SelectionKey> iterator =
selector.selectedKeys().iterator(); iterator.hasNext();)
{
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable())
{
// Accept the new client connection.
ServerSocketChannel serverChannel = (ServerSocketChannel) key
.channel();
SocketChannel clientChannel = serverChannel.accept();
if (clientChannel != null)
{
acceptConnection(clientChannel);
}
}
if (selectorState == 0 && enabled && (!shutdownRequested)
&& logger.isTraceEnabled())
{
// Selected keys was non empty but select() returned 0.
// Log warning and hope it blocks on the next select() call.
logger.trace("Selector.select() returned 0. "
+ "Selected Keys: %d, Interest Ops: %d, Ready Ops: %d ",
selector.selectedKeys().size(), key.interestOps(),
key.readyOps());
}
}
}
/**
* 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
{
ServerSocketChannel channel = ServerSocketChannel.open();
channel.socket().setReuseAddress(allowReuseAddress);
channel.socket()
.bind(new InetSocketAddress(a, listenPort), backlog);
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
numRegistered++;
logger.info(NOTE_CONNHANDLER_STARTED_LISTENING, handlerName);
}
catch (Exception e)
{
logger.traceException(e);
logger.error(ERR_LDAP_CONNHANDLER_CREATE_CHANNEL_FAILED, currentConfig.dn(), a.getHostAddress(), listenPort,
stackTraceToSingleLineString(e));
}
}
return numRegistered;
}
private void acceptConnection(SocketChannel clientChannel)
throws DirectoryException
{
try
{
clientChannel.socket().setKeepAlive(currentConfig.isUseTCPKeepAlive());
clientChannel.socket().setTcpNoDelay(currentConfig.isUseTCPNoDelay());
}
catch (SocketException se)
{
// TCP error occurred because connection reset/closed? In any case,
// just close it and ignore.
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6378870
close(clientChannel);
}
// Check to see if the core server rejected the
// connection (e.g., already too many connections
// established).
LDAPClientConnection clientConnection = new LDAPClientConnection(this,
clientChannel, getProtocol());
if (clientConnection.getConnectionID() < 0)
{
clientConnection.disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
ERR_CONNHANDLER_REJECTED_BY_SERVER.get());
return;
}
InetAddress clientAddr = clientConnection.getRemoteAddress();
// Check to see if the client is on the denied list.
// If so, then reject it immediately.
if ((!deniedClients.isEmpty())
&& AddressMask.matchesAny(deniedClients, clientAddr))
{
clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
currentConfig.isSendRejectionNotice(), ERR_CONNHANDLER_DENIED_CLIENT
.get(clientConnection.getClientHostPort(), clientConnection
.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())
&& (!AddressMask.matchesAny(allowedClients, clientAddr)))
{
clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
currentConfig.isSendRejectionNotice(),
ERR_CONNHANDLER_DISALLOWED_CLIENT.get(clientConnection
.getClientHostPort(), clientConnection.getServerHostPort()));
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
{
PluginConfigManager pluginManager = DirectoryServer
.getPluginConfigManager();
PluginResult.PostConnect pluginResult = pluginManager
.invokePostConnectPlugins(clientConnection);
if (!pluginResult.continueProcessing())
{
clientConnection.disconnect(pluginResult.getDisconnectReason(),
pluginResult.sendDisconnectNotification(),
pluginResult.getErrorMessage());
return;
}
LDAPRequestHandler requestHandler =
requestHandlers[requestHandlerIndex++];
if (requestHandlerIndex >= numRequestHandlers)
{
requestHandlerIndex = 0;
}
requestHandler.registerClient(clientConnection);
}
catch (Exception e)
{
logger.traceException(e);
LocalizableMessage message =
INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(clientConnection
.getClientHostPort(), clientConnection.getServerHostPort(),
getExceptionMessage(e));
logger.debug(message);
clientConnection.disconnect(DisconnectReason.SERVER_ERROR,
currentConfig.isSendRejectionNotice(), message);
}
}
/**
* Appends a string representation of this connection handler to the provided
* buffer.
*
* @param buffer
* The buffer to which the information should be appended.
*/
@Override
public void toString(StringBuilder buffer)
{
buffer.append(handlerName);
}
/**
* 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
{
for (SelectionKey key : selector.keys())
{
try
{
key.cancel();
}
catch (Exception e)
{
logger.traceException(e);
}
try
{
key.channel().close();
}
catch (Exception e)
{
logger.traceException(e);
}
}
}
catch (Exception e)
{
logger.traceException(e);
}
}
/**
* Get the queueing strategy.
*
* @return The queueing strategy.
*/
public QueueingStrategy getQueueingStrategy()
{
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.
*/
public TLSByteChannel getTLSByteChannel(ByteChannel channel)
throws DirectoryException
{
SSLEngine sslEngine = createSSLEngine(currentConfig, sslContext);
return new TLSByteChannel(channel, sslEngine);
}
private SSLEngine createSSLEngine(LDAPConnectionHandlerCfg config,
SSLContext sslContext) throws DirectoryException
{
try
{
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
final Set<String> protocols = config.getSSLProtocol();
if (!protocols.isEmpty())
{
sslEngine.setEnabledProtocols(protocols.toArray(new String[0]));
}
final Set<String> ciphers = config.getSSLCipherSuite();
if (!ciphers.isEmpty())
{
sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0]));
}
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)
{
logger.traceException(e);
ResultCode resCode = DirectoryServer.getServerErrorResultCode();
LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
.get(getExceptionMessage(e));
throw new DirectoryException(resCode, message, e);
}
}
private SSLContext createSSLContext(LDAPConnectionHandlerCfg config)
throws DirectoryException
{
try
{
DN keyMgrDN = config.getKeyManagerProviderDN();
KeyManagerProvider<?> keyManagerProvider = DirectoryServer
.getKeyManagerProvider(keyMgrDN);
if (keyManagerProvider == null)
{
keyManagerProvider = new NullKeyManagerProvider();
}
String alias = config.getSSLCertNickname();
KeyManager[] keyManagers;
if (alias == null)
{
keyManagers = keyManagerProvider.getKeyManagers();
}
else
{
keyManagers = SelectableCertificateKeyManager.wrap(
keyManagerProvider.getKeyManagers(), alias);
}
DN trustMgrDN = config.getTrustManagerProviderDN();
TrustManagerProvider<?> trustManagerProvider = DirectoryServer
.getTrustManagerProvider(trustMgrDN);
if (trustManagerProvider == null)
{
trustManagerProvider = new NullTrustManagerProvider();
}
SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME);
sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(),
null);
return sslContext;
}
catch (Exception e)
{
logger.traceException(e);
ResultCode resCode = DirectoryServer.getServerErrorResultCode();
LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
.get(getExceptionMessage(e));
throw new DirectoryException(resCode, message, e);
}
}
/**
* Enqueue a connection finalizer which will be invoked after a short delay.
*
* @param r
* The connection finalizer runnable.
*/
void registerConnectionFinalizer(Runnable r)
{
synchronized (connectionFinalizerLock)
{
if (connectionFinalizer != null)
{
connectionFinalizerPendingJobQueue.add(r);
}
else
{
// Already finalized - invoked immediately.
r.run();
}
}
}
}