LDAPConnectionHandler.java revision fe7c5bacba1535f5f2c9b37e09cd9b834ad2e75e
/*
* 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 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.
*/
private final class ConnectionFinalizerRunnable implements Runnable
{
public void run()
{
{
for (Runnable r : connectionFinalizerActiveJobQueue)
{
r.run();
}
}
// Switch the lists.
synchronized (connectionFinalizerLock)
{
}
}
}
/**
* The tracer object for the debug logger.
*/
/**
* 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.
*/
// The current configuration state.
private LDAPConnectionHandlerCfg currentConfig;
/* 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.
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.
// 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.
// 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.
private String handlerName;
// The protocol used by this connection handler.
// 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.
// The friendly name of this connection handler.
private String friendlyName;
//SSL instance name used in context creation.
//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.
*/
/**
* 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() {
if (currentConfig.isAllowStartTLS()) {
if (currentConfig.isUseSSL()) {
return false;
} else {
return true;
}
} else {
return false;
}
}
/**
* {@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.
new AddressMask[0]);
new AddressMask[0]);
//Reconfigure SSL context if needed.
sslConfig = true;
}
if (config.isAllowLDAPV2())
{
}
else
{
}
messages);
}
/**
* {@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.
*/
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.
*/
new LinkedList<ClientConnection>();
}
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.
*/
public DN getComponentEntryDN() {
return currentConfig.dn();
}
/**
* {@inheritDoc}
*/
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}
*/
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}
*/
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}
*/
{
if (friendlyName == null)
{
.toString();
}
// Open the selector.
try {
} catch (Exception e) {
if (debugEnabled())
{
}
throw new InitializationException(message, e);
}
protocol = "LDAP";
// Save this configuration for future reference.
requestHandlerIndex = 0;
new AddressMask[0]);
new AddressMask[0]);
//Setup SSL context if needed.
sslConfig=true;
// 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.
for (InetAddress a : listenAddresses) {
try {
throw new IOException(
}
} catch (IOException e) {
if (debugEnabled()) {
}
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.
// 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()
{
// 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) ||
try {
config.isAllowTCPReuseAddress())) {
throw new IOException(
}
} catch (IOException e) {
if (debugEnabled()) {
}
return false;
}
}
}
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationChangeAcceptable(
// 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}
*/
shutdownRequested = true;
try {
try {
} catch (Exception e) {
}
}
} catch (Exception e) {
}
}
/**
* {@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;
}
try {
} 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 {
int numRegistered = 0;
for (InetAddress a : listenAddresses) {
try {
channel.configureBlocking(false);
} catch (Exception e) {
if (debugEnabled())
{
}
}
}
// 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;
int selectorState;
while (enabled && (!shutdownRequested)) {
try {
// We can't rely on return value of select to deterine if any keys
// are ready.
.selectedKeys().iterator();
if (key.isAcceptable()) {
// Accept the new client connection.
.channel();
.accept();
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 ",
}
}
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.
this,
message);
enabled = false;
try {
}
} 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.
message);
try {
}
enabled = false;
}
}
}
throws DirectoryException
{
try
{
}
catch(SocketException se)
{
// just close it and ignore.
try
{
}
catch(Exception e)
{
// Ignore any exceptions while closing the channel.
}
return;
}
// Check to see if the core server rejected the
// connection (e.g., already too many connections
// established).
// The connection will have already been closed.
return;
}
.getRemoteAddress();
// Check to see if the client is on the denied list.
// If so, then reject it immediately.
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.
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;
}
if (requestHandlerIndex >= numRequestHandlers) {
requestHandlerIndex = 0;
}
} catch (Exception e) {
if (debugEnabled())
{
}
getExceptionMessage(e));
.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.
*/
}
/**
* 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.
*/
public QueueingStrategy getQueueingStrategy() {
return queueingStrategy;
}
private LDAPClientConnection
throws DirectoryException {
if(sslConfig) {
sslConfig=false;
}
getProtocol());
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
throws DirectoryException {
}
throws DirectoryException {
try {
protocol = "LDAPS";
}
if (keyManagerProvider == null)
keyManagerProvider = new NullKeyManagerProvider();
if (trustManagerProvider == null)
} else {
}
} catch (NoSuchAlgorithmException nsae) {
if (debugEnabled())
} catch (KeyManagementException kme) {
if (debugEnabled())
} catch (DirectoryException de) {
if (debugEnabled())
}
}
/**
* 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)
{
}
else
{
// Already finalized - invoked immediately.
r.run();
}
}
}
// Determine the number of request handlers.
{
{
// Automatically choose based on the number of processors.
return value;
}
else
{
return configuration.getNumRequestHandlers();
}
}
}