/*
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
|
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
|
* 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-2009 Sun Microsystems, Inc.
|
*/
|
package org.opends.server.protocols.ldap;
|
import org.opends.messages.Message;
|
|
|
|
import static org.opends.server.loggers.AccessLogger.logConnect;
|
import static org.opends.server.loggers.ErrorLogger.logError;
|
import static org.opends.server.loggers.debug.DebugLogger.*;
|
import org.opends.server.loggers.debug.DebugTracer;
|
import org.opends.server.monitors.ClientConnectionMonitorProvider;
|
|
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.SelectionKey;
|
import java.nio.channels.Selector;
|
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.SocketChannel;
|
import java.security.KeyManagementException;
|
import java.security.NoSuchAlgorithmException;
|
import java.util.ArrayList;
|
import java.util.Collection;
|
import java.util.Iterator;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedList;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.concurrent.Executors;
|
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.TimeUnit;
|
|
import javax.net.ssl.SSLContext;
|
|
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.AlertGenerator;
|
import org.opends.server.api.ClientConnection;
|
import org.opends.server.api.ConnectionHandler;
|
import org.opends.server.api.DirectoryThread;
|
import org.opends.server.api.KeyManagerProvider;
|
import org.opends.server.api.ServerShutdownListener;
|
import org.opends.server.api.TrustManagerProvider;
|
import org.opends.server.api.plugin.PluginResult;
|
import org.opends.server.config.ConfigException;
|
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.types.AddressMask;
|
import org.opends.server.types.ConfigChangeResult;
|
import org.opends.server.types.DN;
|
import org.opends.server.types.DebugLogLevel;
|
import org.opends.server.types.DirectoryException;
|
import org.opends.server.types.DisconnectReason;
|
|
|
import org.opends.server.types.HostPort;
|
import org.opends.server.types.InitializationException;
|
import org.opends.server.types.ResultCode;
|
import org.opends.server.types.SSLClientAuthPolicy;
|
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
|
{
|
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;
|
}
|
|
}
|
}
|
|
/**
|
* The tracer object for the debug logger.
|
*/
|
private static final DebugTracer TRACER = getTracer();
|
|
/**
|
* The fully-qualified name of this class.
|
*/
|
private static final String CLASS_NAME =
|
"org.opends.server.protocols.ldap.LDAPConnectionHandler";
|
|
/**
|
* Default friendly name for the LDAP connection handler.
|
*/
|
private static final String DEFAULT_FRIENDLY_NAME = "LDAP Connection Handler";
|
|
// 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 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 AddressMask[] allowedClients;
|
|
// The set of clients that have been explicitly denied access to the
|
// server.
|
private AddressMask[] deniedClients;
|
|
// The set of SSL cipher suites that should be allowed.
|
private String[] enabledSSLCipherSuites;
|
|
// The set of SSL protocols that should be allowed.
|
private String[] enabledSSLProtocols;
|
|
// 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 LinkedList<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 instance name used in context creation.
|
private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS";
|
|
//SSL context.
|
private SSLContext sslContext;
|
private boolean sslConfig = false;
|
|
/**
|
* 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(), DEFAULT_FRIENDLY_NAME);
|
}
|
|
|
/**
|
* 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 Friendly name to use in this connector.
|
* If null, the default one is used.
|
*/
|
public LDAPConnectionHandler(QueueingStrategy strategy, String friendlyName) {
|
super(DEFAULT_FRIENDLY_NAME + " Thread");
|
|
if (friendlyName == null) {
|
this.friendlyName = DEFAULT_FRIENDLY_NAME;
|
} else {
|
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() {
|
if (currentConfig.isAllowStartTLS()) {
|
if (currentConfig.isUseSSL()) {
|
return false;
|
} else {
|
return true;
|
}
|
} else {
|
return false;
|
}
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public ConfigChangeResult applyConfigurationChange(
|
LDAPConnectionHandlerCfg config) {
|
// Create variables to include in the response.
|
ResultCode resultCode = ResultCode.SUCCESS;
|
boolean adminActionRequired = false;
|
ArrayList<Message> messages = new ArrayList<Message>();
|
|
// 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().toArray(
|
new AddressMask[0]);
|
deniedClients = config.getDeniedClient().toArray(
|
new AddressMask[0]);
|
//Reconfigure SSL context if needed.
|
if (config.isUseSSL() || config.isAllowStartTLS()) {
|
sslConfig = true;
|
|
}
|
|
if (config.isAllowLDAPV2())
|
{
|
DirectoryServer.registerSupportedLDAPVersion(2, this);
|
}
|
else
|
{
|
DirectoryServer.deregisterSupportedLDAPVersion(2, this);
|
}
|
|
return new ConfigChangeResult(resultCode, adminActionRequired,
|
messages);
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void finalizeConnectionHandler(Message finalizeReason) {
|
shutdownRequested = true;
|
currentConfig.removeLDAPChangeListener(this);
|
|
if (connMonitor != null)
|
{
|
String lowerName =
|
toLowerCase(connMonitor.getMonitorInstanceName());
|
DirectoryServer.deregisterMonitorProvider(lowerName);
|
}
|
|
if (statTracker != null) {
|
String lowerName =
|
toLowerCase(statTracker.getMonitorInstanceName());
|
DirectoryServer.deregisterMonitorProvider(lowerName);
|
}
|
|
DirectoryServer.deregisterSupportedLDAPVersion(2, this);
|
DirectoryServer.deregisterSupportedLDAPVersion(3, this);
|
|
try {
|
selector.wakeup();
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, 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.
|
*/
|
public LinkedHashMap<String, String> getAlerts() {
|
LinkedHashMap<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.
|
*/
|
public String getClassName() {
|
return CLASS_NAME;
|
}
|
|
|
|
/**
|
* 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() {
|
LinkedList<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;
|
}
|
|
|
|
/**
|
* Retrieves the set of enabled SSL cipher suites configured for
|
* this connection handler.
|
*
|
* @return The set of enabled SSL cipher suites configured for this
|
* connection handler.
|
*/
|
public String[] getEnabledSSLCipherSuites() {
|
return enabledSSLCipherSuites;
|
}
|
|
|
|
/**
|
* Retrieves the set of enabled SSL protocols configured for this
|
* connection handler.
|
*
|
* @return The set of enabled SSL protocols configured for this
|
* connection handler.
|
*/
|
public String[] getEnabledSSLProtocols() {
|
return enabledSSLProtocols;
|
}
|
|
|
|
|
|
/**
|
* {@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}
|
*/
|
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
|
{
|
// Open the selector.
|
try {
|
selector = Selector.open();
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
Message message = ERR_LDAP_CONNHANDLER_OPEN_SELECTOR_FAILED.get(
|
String.valueOf(config.dn()), stackTraceToSingleLineString(e));
|
throw new InitializationException(message, e);
|
}
|
|
protocol = "LDAP";
|
|
// Save this configuration for future reference.
|
currentConfig = config;
|
enabled = config.isEnabled();
|
requestHandlerIndex = 0;
|
allowedClients = config.getAllowedClient().toArray(
|
new AddressMask[0]);
|
deniedClients = config.getDeniedClient().toArray(
|
new AddressMask[0]);
|
//Setup SSL context if needed.
|
if (config.isUseSSL() || config.isAllowStartTLS())
|
sslConfig=true;
|
|
// Save properties that cannot be dynamically modified.
|
allowReuseAddress = config.isAllowTCPReuseAddress();
|
backlog = config.getAcceptBacklog();
|
listenAddresses = config.getListenAddress();
|
listenPort = config.getListenPort();
|
numRequestHandlers = config.getNumRequestHandlers();
|
|
// 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.
|
for (InetAddress a : listenAddresses) {
|
try {
|
if (StaticUtils.isAddressInUse(a, listenPort, allowReuseAddress)) {
|
throw new IOException(
|
ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
|
}
|
} catch (IOException e) {
|
if (debugEnabled()) {
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
Message message = ERR_LDAP_CONNHANDLER_CANNOT_BIND.get(
|
String.valueOf(config.dn()), a.getHostAddress(),
|
listenPort, getExceptionMessage(e));
|
logError(message);
|
throw new InitializationException(message);
|
}
|
}
|
|
// 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<Message> unacceptableReasons)
|
{
|
LDAPConnectionHandlerCfg config = (LDAPConnectionHandlerCfg) configuration;
|
|
// Attempt to bind to the listen port on all configured addresses to
|
// verify whether the connection handler will be able to start.
|
if ((currentConfig == null) ||
|
(!currentConfig.isEnabled() && config.isEnabled())) {
|
for (InetAddress a : config.getListenAddress()) {
|
try {
|
if (StaticUtils.isAddressInUse(a, config.getListenPort(),
|
config.isAllowTCPReuseAddress())) {
|
throw new IOException(
|
ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
|
}
|
} catch (IOException e) {
|
if (debugEnabled()) {
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
Message message = ERR_LDAP_CONNHANDLER_CANNOT_BIND.get(
|
String.valueOf(config.dn()), a.getHostAddress(),
|
config.getListenPort(), getExceptionMessage(e));
|
unacceptableReasons.add(message);
|
return false;
|
}
|
}
|
}
|
return isConfigurationChangeAcceptable(config, unacceptableReasons);
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isConfigurationChangeAcceptable(
|
LDAPConnectionHandlerCfg config,
|
List<Message> unacceptableReasons) {
|
// All validation is performed by the admin framework.
|
return true;
|
}
|
|
|
|
/**
|
* 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}
|
*/
|
public void processServerShutdown(Message reason) {
|
shutdownRequested = true;
|
|
try {
|
for (LDAPRequestHandler requestHandler : requestHandlers) {
|
try {
|
requestHandler.processServerShutdown(reason);
|
} catch (Exception e) {
|
}
|
}
|
} catch (Exception e) {
|
}
|
}
|
|
|
|
|
/**
|
* {@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;
|
|
logError(NOTE_LDAP_CONNHANDLER_STOPPED_LISTENING.get(handlerName));
|
}
|
|
try {
|
Thread.sleep(1000);
|
} catch (Exception e) {
|
}
|
|
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 = 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++;
|
|
logError(NOTE_LDAP_CONNHANDLER_STARTED_LISTENING.get(handlerName));
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
logError(ERR_LDAP_CONNHANDLER_CREATE_CHANNEL_FAILED.
|
get(String.valueOf(currentConfig.dn()), a.getHostAddress(),
|
listenPort, stackTraceToSingleLineString(e)));
|
}
|
}
|
|
// 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) {
|
logError(ERR_LDAP_CONNHANDLER_NO_ACCEPTORS.get(
|
String.valueOf(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;
|
int selectorState;
|
while (enabled && (!shutdownRequested)) {
|
try {
|
selectorState = selector.select();
|
|
// We can't rely on return value of select to deterine if any keys
|
// are ready.
|
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4850373
|
Iterator<SelectionKey> iterator = selector
|
.selectedKeys().iterator();
|
|
while (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) &&
|
debugEnabled())
|
{
|
// Selected keys was non empty but select() returned 0.
|
// Log warning and hope it blocks on the next select() call.
|
TRACER.debugWarning("Selector.select() returned 0. " +
|
"Selected Keys: %d, Interest Ops: %d, Ready Ops: %d ",
|
selector.selectedKeys().size(), key.interestOps(),
|
key.readyOps());
|
}
|
}
|
|
lastIterationFailed = false;
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
logError(ERR_LDAP_CONNHANDLER_CANNOT_ACCEPT_CONNECTION.get(
|
String.valueOf(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.
|
Message message =
|
ERR_LDAP_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.
|
get(String.valueOf(currentConfig.dn()),
|
stackTraceToSingleLineString(e));
|
logError(message);
|
|
DirectoryServer
|
.sendAlertNotification(
|
this,
|
ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
|
message);
|
|
enabled = false;
|
|
try {
|
cleanUpSelector();
|
} catch (Exception e2) {
|
}
|
} else {
|
lastIterationFailed = true;
|
}
|
}
|
}
|
|
if (shutdownRequested) {
|
cleanUpSelector();
|
selector.close();
|
listening = false;
|
enabled = false;
|
}
|
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, 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.
|
Message message = ERR_LDAP_CONNHANDLER_UNCAUGHT_ERROR.
|
get(String.valueOf(currentConfig.dn()),
|
stackTraceToSingleLineString(e));
|
logError(message);
|
|
DirectoryServer.sendAlertNotification(this,
|
ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR,
|
message);
|
|
try {
|
cleanUpSelector();
|
} catch (Exception e2) {
|
}
|
|
enabled = false;
|
}
|
}
|
}
|
|
private void acceptConnection(SocketChannel clientChannel)
|
throws DirectoryException
|
{
|
try
|
{
|
clientChannel.socket().setKeepAlive(
|
currentConfig.isUseTCPKeepAlive());
|
clientChannel.socket().setTcpNoDelay(
|
currentConfig.isUseTCPNoDelay());
|
}
|
catch(SocketException se)
|
{
|
// TCP error occured because conneciton reset/closed? In any case,
|
// just close it and ignore.
|
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6378870
|
try
|
{
|
clientChannel.close();
|
}
|
catch(Exception e)
|
{
|
// Ignore any exceptions while closing the channel.
|
}
|
return;
|
}
|
|
LDAPClientConnection clientConnection =
|
createClientConnection(clientChannel);
|
// Check to see if the core server rejected the
|
// connection (e.g., already too many connections
|
// established).
|
if (clientConnection.getConnectionID() < 0) {
|
// The connection will have already been closed.
|
return;
|
}
|
|
InetAddress clientAddr = clientConnection
|
.getRemoteAddress();
|
// Check to see if the client is on the denied list.
|
// If so, then reject it immediately.
|
if ((deniedClients.length > 0)
|
&& AddressMask.maskListContains(clientAddr
|
.getAddress(), clientAddr.getHostName(),
|
deniedClients)) {
|
clientConnection.disconnect(
|
DisconnectReason.CONNECTION_REJECTED,
|
currentConfig.isSendRejectionNotice(),
|
ERR_LDAP_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.length > 0)
|
&& (!AddressMask.maskListContains(clientAddr
|
.getAddress(), clientAddr.getHostName(),
|
allowedClients))) {
|
clientConnection.disconnect(
|
DisconnectReason.CONNECTION_REJECTED,
|
currentConfig.isSendRejectionNotice(),
|
ERR_LDAP_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;
|
}
|
|
if (requestHandler
|
.registerClient(clientConnection)) {
|
logConnect(clientConnection);
|
}
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
|
Message message =
|
INFO_LDAP_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.
|
get(clientConnection.getClientHostPort(),
|
clientConnection.getServerHostPort(),
|
getExceptionMessage(e));
|
logError(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 {
|
Iterator<SelectionKey> iterator = selector.keys().iterator();
|
while (iterator.hasNext()) {
|
SelectionKey key = iterator.next();
|
|
try {
|
key.cancel();
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
}
|
|
try {
|
key.channel().close();
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
}
|
}
|
} catch (Exception e) {
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, e);
|
}
|
}
|
}
|
|
|
|
/**
|
* Get the queueing strategy.
|
*
|
* @return The queueing strategy.
|
*/
|
public QueueingStrategy getQueueingStrategy() {
|
return queueingStrategy;
|
}
|
|
private LDAPClientConnection
|
createClientConnection(SocketChannel socketChannel)
|
throws DirectoryException {
|
if(sslConfig) {
|
configSSL(currentConfig);
|
sslConfig=false;
|
}
|
LDAPClientConnection c = new LDAPClientConnection(this, socketChannel,
|
getProtocol());
|
if(currentConfig.isUseSSL()) {
|
TLSByteChannel tlsByteChannel = getTLSByteChannel(c, socketChannel);
|
c.enableSSL(tlsByteChannel);
|
}
|
return c;
|
}
|
|
/**
|
* Creates a TLS Byte Channel instance using the specified LDAP
|
* client connection and socket channel.
|
*
|
* @param c The client connection to use in the creation.
|
* @param socketChannel 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(LDAPClientConnection c, SocketChannel socketChannel)
|
throws DirectoryException {
|
return(TLSByteChannel.getTLSByteChannel(currentConfig, c,
|
sslContext,
|
socketChannel));
|
}
|
|
private void configSSL(LDAPConnectionHandlerCfg config)
|
throws DirectoryException {
|
ResultCode resCode = DirectoryServer.getServerErrorResultCode();
|
try {
|
String alias = config.getSSLCertNickname();
|
if (config.isUseSSL()) {
|
protocol = "LDAPS";
|
}
|
DN keyMgrDN = config.getKeyManagerProviderDN();
|
DN trustMgrDN = config.getTrustManagerProviderDN();
|
KeyManagerProvider<?> keyManagerProvider =
|
DirectoryServer.getKeyManagerProvider(keyMgrDN);
|
if (keyManagerProvider == null)
|
keyManagerProvider = new NullKeyManagerProvider();
|
TrustManagerProvider<?> trustManagerProvider =
|
DirectoryServer.getTrustManagerProvider(trustMgrDN);
|
if (trustManagerProvider == null)
|
trustManagerProvider = new NullTrustManagerProvider();
|
sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME);
|
if (alias == null) {
|
sslContext.init(keyManagerProvider.getKeyManagers(),
|
trustManagerProvider.getTrustManagers(), null);
|
} else {
|
sslContext.init(SelectableCertificateKeyManager.wrap(
|
keyManagerProvider.getKeyManagers(), alias),
|
trustManagerProvider.getTrustManagers(), null);
|
}
|
} catch (NoSuchAlgorithmException nsae) {
|
if (debugEnabled())
|
TRACER.debugCaught(DebugLogLevel.ERROR, nsae);
|
Message message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.
|
get(getExceptionMessage(nsae));
|
throw new DirectoryException(resCode, message, nsae);
|
} catch (KeyManagementException kme) {
|
if (debugEnabled())
|
TRACER.debugCaught(DebugLogLevel.ERROR, kme);
|
Message message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
|
.get(getExceptionMessage(kme));
|
throw new DirectoryException(resCode, message, kme);
|
} catch (DirectoryException de) {
|
if (debugEnabled())
|
TRACER.debugCaught(DebugLogLevel.ERROR, de);
|
Message message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
|
.get(getExceptionMessage(de));
|
throw new DirectoryException(resCode, message, de);
|
}
|
}
|
|
|
|
/**
|
* 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();
|
}
|
}
|
}
|
|
}
|