/* * 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 implements ConfigurationChangeListener, 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 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 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 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. *

* 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 connectionFinalizerActiveJobQueue; private List 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 true if LDAPv2 is allowed, or false * if not. */ public boolean allowLDAPv2() { return currentConfig.isAllowLDAPV2(); } /** * Indicates whether this connection handler should allow the use of * the StartTLS extended operation. * * @return true if StartTLS is allowed, or false * 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 messages = new ArrayList(); // 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 getAlerts() { LinkedHashMap alerts = new LinkedHashMap(); 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 getClientConnections() { LinkedList connectionList = new LinkedList(); 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 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(); } /** * {@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(); 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(); connectionFinalizerPendingJobQueue = new ArrayList(); 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 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 unacceptableReasons) { // All validation is performed by the admin framework. return true; } /** * Indicates whether this connection handler should maintain usage * statistics. * * @return true if this connection handler should * maintain usage statistics, or false 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 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 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(); } } } }