/** {@inheritDoc} */ @Override public boolean isConfigurationAcceptable( ConnectionHandlerCfg configuration, List<LocalizableMessage> unacceptableReasons) { LDAPConnectionHandlerCfg config = (LDAPConnectionHandlerCfg) configuration; if (currentConfig == null || (!currentConfig.isEnabled() && config.isEnabled())) { // Attempt to bind to the listen port on all configured addresses to // verify whether the connection handler will be able to start. LocalizableMessage errorMessage = checkAnyListenAddressInUse( config.getListenAddress(), config.getListenPort(), config.isAllowTCPReuseAddress(), config.dn()); if (errorMessage != null) { unacceptableReasons.add(errorMessage); return false; } } if (config.isEnabled() // Check that the SSL configuration is valid. && (config.isUseSSL() || config.isAllowStartTLS())) { try { createSSLEngine(config, createSSLContext(config)); } catch (DirectoryException e) { logger.traceException(e); unacceptableReasons.add(e.getMessageObject()); return false; } } return true; }
private ConnectionHandlerDescriptor getConnectionHandler( ConnectionHandlerCfg connHandler, String name) throws OpenDsException { SortedSet<InetAddress> addresses = new TreeSet<InetAddress>(getInetAddressComparator()); int port; ConnectionHandlerDescriptor.Protocol protocol; ConnectionHandlerDescriptor.State state = connHandler.isEnabled() ? ConnectionHandlerDescriptor.State.ENABLED : ConnectionHandlerDescriptor.State.DISABLED; if (connHandler instanceof LDAPConnectionHandlerCfg) { LDAPConnectionHandlerCfg ldap = (LDAPConnectionHandlerCfg) connHandler; if (ldap.isUseSSL()) { protocol = ConnectionHandlerDescriptor.Protocol.LDAPS; } else if (ldap.isAllowStartTLS()) { protocol = ConnectionHandlerDescriptor.Protocol.LDAP_STARTTLS; } else { protocol = ConnectionHandlerDescriptor.Protocol.LDAP; } addAll(addresses, ldap.getListenAddress()); port = ldap.getListenPort(); } else if (connHandler instanceof HTTPConnectionHandlerCfg) { HTTPConnectionHandlerCfg http = (HTTPConnectionHandlerCfg) connHandler; if (http.isUseSSL()) { protocol = ConnectionHandlerDescriptor.Protocol.HTTPS; } else { protocol = ConnectionHandlerDescriptor.Protocol.HTTP; } addAll(addresses, http.getListenAddress()); port = http.getListenPort(); } else if (connHandler instanceof JMXConnectionHandlerCfg) { JMXConnectionHandlerCfg jmx = (JMXConnectionHandlerCfg) connHandler; if (jmx.isUseSSL()) { protocol = ConnectionHandlerDescriptor.Protocol.JMXS; } else { protocol = ConnectionHandlerDescriptor.Protocol.JMX; } addAll(addresses, jmx.getListenAddress()); port = jmx.getListenPort(); } else if (connHandler instanceof LDIFConnectionHandlerCfg) { protocol = ConnectionHandlerDescriptor.Protocol.LDIF; port = -1; } else if (connHandler instanceof SNMPConnectionHandlerCfg) { protocol = ConnectionHandlerDescriptor.Protocol.SNMP; SNMPConnectionHandlerCfg snmp = (SNMPConnectionHandlerCfg) connHandler; addAll(addresses, snmp.getListenAddress()); port = snmp.getListenPort(); } else { protocol = ConnectionHandlerDescriptor.Protocol.OTHER; port = -1; } Set<CustomSearchResult> emptySet = Collections.emptySet(); return new ConnectionHandlerDescriptor(addresses, port, protocol, state, name, emptySet); }
private void configureSSL(LDAPConnectionHandlerCfg config) throws DirectoryException { protocol = config.isUseSSL() ? "LDAPS" : "LDAP"; if (config.isUseSSL() || config.isAllowStartTLS()) { sslContext = createSSLContext(config); sslEngine = createSSLEngine(config, sslContext); } else { sslContext = null; sslEngine = null; } }
private SSLContext createSSLContext(LDAPConnectionHandlerCfg config) throws DirectoryException { try { DN keyMgrDN = config.getKeyManagerProviderDN(); KeyManagerProvider<?> keyManagerProvider = DirectoryServer.getKeyManagerProvider(keyMgrDN); if (keyManagerProvider == null) { logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName); disableAndWarnIfUseSSL(config); keyManagerProvider = new NullKeyManagerProvider(); // The SSL connection is unusable without a key manager provider } else if (!keyManagerProvider.containsAtLeastOneKey()) { logger.error(ERR_INVALID_KEYSTORE, friendlyName); disableAndWarnIfUseSSL(config); } final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname()); final KeyManager[] keyManagers; if (aliases.isEmpty()) { keyManagers = keyManagerProvider.getKeyManagers(); } else { final Iterator<String> it = aliases.iterator(); while (it.hasNext()) { if (!keyManagerProvider.containsKeyWithAlias(it.next())) { logger.error(ERR_KEYSTORE_DOES_NOT_CONTAIN_ALIAS, aliases, friendlyName); it.remove(); } } if (aliases.isEmpty()) { disableAndWarnIfUseSSL(config); } keyManagers = SelectableCertificateKeyManager.wrap( keyManagerProvider.getKeyManagers(), aliases, friendlyName); } DN trustMgrDN = config.getTrustManagerProviderDN(); TrustManagerProvider<?> trustManagerProvider = DirectoryServer.getTrustManagerProvider(trustMgrDN); if (trustManagerProvider == null) { trustManagerProvider = new NullTrustManagerProvider(); } SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME); sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(), null); return sslContext; } catch (Exception e) { logger.traceException(e); ResultCode resCode = DirectoryServer.getServerErrorResultCode(); LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.get(getExceptionMessage(e)); throw new DirectoryException(resCode, message, e); } }
/** * Open channels for each listen address and register them against this ConnectionHandler's {@link * Selector}. * * @return the number of successfully registered channel */ private int registerChannels() { int numRegistered = 0; for (InetAddress a : listenAddresses) { try { ServerSocketChannel channel = ServerSocketChannel.open(); channel.socket().setReuseAddress(allowReuseAddress); channel.socket().bind(new InetSocketAddress(a, listenPort), backlog); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_ACCEPT); numRegistered++; logger.info(NOTE_CONNHANDLER_STARTED_LISTENING, handlerName); } catch (Exception e) { logger.traceException(e); logger.error( ERR_LDAP_CONNHANDLER_CREATE_CHANNEL_FAILED, currentConfig.dn(), a.getHostAddress(), listenPort, stackTraceToSingleLineString(e)); } } return numRegistered; }
private SSLEngine createSSLEngine(LDAPConnectionHandlerCfg config, SSLContext sslContext) throws DirectoryException { try { SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(false); final Set<String> protocols = config.getSSLProtocol(); if (!protocols.isEmpty()) { sslEngine.setEnabledProtocols(protocols.toArray(new String[0])); } final Set<String> ciphers = config.getSSLCipherSuite(); if (!ciphers.isEmpty()) { sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0])); } switch (config.getSSLClientAuthPolicy()) { case DISABLED: sslEngine.setNeedClientAuth(false); sslEngine.setWantClientAuth(false); break; case REQUIRED: sslEngine.setWantClientAuth(true); sslEngine.setNeedClientAuth(true); break; case OPTIONAL: default: sslEngine.setNeedClientAuth(false); sslEngine.setWantClientAuth(true); break; } return sslEngine; } catch (Exception e) { logger.traceException(e); ResultCode resCode = DirectoryServer.getServerErrorResultCode(); LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.get(getExceptionMessage(e)); throw new DirectoryException(resCode, message, e); } }
/** {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange(LDAPConnectionHandlerCfg config) { final ConfigChangeResult ccr = new ConfigChangeResult(); // Note that the following properties cannot be modified: // // * listen port and addresses // * use ssl // * ssl policy // * ssl cert nickname // * accept backlog // * tcp reuse address // * num request handler // Clear the stat tracker if LDAPv2 is being enabled. if (currentConfig.isAllowLDAPV2() != config.isAllowLDAPV2() && config.isAllowLDAPV2()) { statTracker.clearStatistics(); } // Apply the changes. currentConfig = config; enabled = config.isEnabled(); allowedClients = config.getAllowedClient(); deniedClients = config.getDeniedClient(); // Reconfigure SSL if needed. try { configureSSL(config); } catch (DirectoryException e) { logger.traceException(e); ccr.setResultCode(e.getResultCode()); ccr.addMessage(e.getMessageObject()); return ccr; } if (config.isAllowLDAPV2()) { DirectoryServer.registerSupportedLDAPVersion(2, this); } else { DirectoryServer.deregisterSupportedLDAPVersion(2, this); } return ccr; }
/** {@inheritDoc} */ @Override public void finalizeConnectionHandler(LocalizableMessage finalizeReason) { shutdownRequested = true; currentConfig.removeLDAPChangeListener(this); if (connMonitor != null) { DirectoryServer.deregisterMonitorProvider(connMonitor); } if (statTracker != null) { DirectoryServer.deregisterMonitorProvider(statTracker); } DirectoryServer.deregisterSupportedLDAPVersion(2, this); DirectoryServer.deregisterSupportedLDAPVersion(3, this); try { selector.wakeup(); } catch (Exception e) { logger.traceException(e); } for (LDAPRequestHandler requestHandler : requestHandlers) { requestHandler.processServerShutdown(finalizeReason); } // Shutdown the connection finalizer and ensure that any pending // unclosed connections are closed. synchronized (connectionFinalizerLock) { connectionFinalizer.shutdown(); connectionFinalizer = null; Runnable r = new ConnectionFinalizerRunnable(); r.run(); // Flush active queue. r.run(); // Flush pending queue. } }
/** * Retrieves 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(); }
/** * 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; boolean starting = true; while (!shutdownRequested) { // If this connection handler is not enabled, then just sleep // for a bit and check again. if (!enabled) { if (listening) { cleanUpSelector(); listening = false; logger.info(NOTE_CONNHANDLER_STOPPED_LISTENING, handlerName); } if (starting) { // This may happen if there was an initialisation error // which led to disable the connector. // The main thread is waiting for the connector to listen // on its port, which will not occur yet, // so notify here to allow the server startup to complete. synchronized (waitListen) { starting = false; waitListen.notify(); } } StaticUtils.sleep(1000); continue; } // If we have gotten here, then we are about to start listening // for the first time since startup or since we were previously // disabled. Make sure to start with a clean selector and then // create all the listeners. try { cleanUpSelector(); int numRegistered = registerChannels(); // At this point, the connection Handler either started // correctly or failed to start but the start process // should be notified and resume its work in any cases. synchronized (waitListen) { waitListen.notify(); } // If none of the listeners were created successfully, then // consider the connection handler disabled and require // administrative action before trying again. if (numRegistered == 0) { logger.error(ERR_LDAP_CONNHANDLER_NO_ACCEPTORS, currentConfig.dn()); enabled = false; continue; } listening = true; // Enter a loop, waiting for new connections to arrive and // then accepting them as they come in. boolean lastIterationFailed = false; while (enabled && !shutdownRequested) { try { serveIncomingConnections(); lastIterationFailed = false; } catch (Exception e) { logger.traceException(e); logger.error( ERR_CONNHANDLER_CANNOT_ACCEPT_CONNECTION, friendlyName, currentConfig.dn(), getExceptionMessage(e)); if (lastIterationFailed) { // The last time through the accept loop we also // encountered a failure. Rather than enter a potential // infinite loop of failures, disable this acceptor and // log an error. LocalizableMessage message = ERR_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.get( friendlyName, currentConfig.dn(), stackTraceToSingleLineString(e)); logger.error(message); DirectoryServer.sendAlertNotification( this, ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, message); cleanUpSelector(); enabled = false; } else { lastIterationFailed = true; } } } if (shutdownRequested) { cleanUpSelector(); selector.close(); listening = false; enabled = false; } } catch (Exception e) { logger.traceException(e); // This is very bad because we failed outside the loop. The // only thing we can do here is log a message, send an alert, // and disable the selector until an administrator can figure // out what's going on. LocalizableMessage message = ERR_LDAP_CONNHANDLER_UNCAUGHT_ERROR.get( currentConfig.dn(), stackTraceToSingleLineString(e)); logger.error(message); DirectoryServer.sendAlertNotification( this, ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR, message); cleanUpSelector(); enabled = false; } } }
/** * 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(); }
private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config) { if (config.isUseSSL()) { logger.warn(INFO_DISABLE_CONNECTION, friendlyName); enabled = false; } }
/** {@inheritDoc} */ @Override public void initializeConnectionHandler(LDAPConnectionHandlerCfg config) throws ConfigException, InitializationException { if (friendlyName == null) { friendlyName = config.dn().rdn().getAttributeValue(0).toString(); } // Open the selector. try { selector = Selector.open(); } catch (Exception e) { logger.traceException(e); LocalizableMessage message = ERR_LDAP_CONNHANDLER_OPEN_SELECTOR_FAILED.get( config.dn(), stackTraceToSingleLineString(e)); throw new InitializationException(message, e); } // Save this configuration for future reference. currentConfig = config; enabled = config.isEnabled(); requestHandlerIndex = 0; allowedClients = config.getAllowedClient(); deniedClients = config.getDeniedClient(); // Configure SSL if needed. try { // This call may disable the connector if wrong SSL settings configureSSL(config); } catch (DirectoryException e) { logger.traceException(e); throw new InitializationException(e.getMessageObject()); } // Save properties that cannot be dynamically modified. allowReuseAddress = config.isAllowTCPReuseAddress(); backlog = config.getAcceptBacklog(); listenAddresses = config.getListenAddress(); listenPort = config.getListenPort(); numRequestHandlers = getNumRequestHandlers(config.getNumRequestHandlers(), friendlyName); // Construct a unique name for this connection handler, and put // together the set of listeners. listeners = new LinkedList<>(); StringBuilder nameBuffer = new StringBuilder(); nameBuffer.append(friendlyName); for (InetAddress a : listenAddresses) { listeners.add(new HostPort(a.getHostAddress(), listenPort)); nameBuffer.append(" "); nameBuffer.append(a.getHostAddress()); } nameBuffer.append(" port "); nameBuffer.append(listenPort); handlerName = nameBuffer.toString(); // Attempt to bind to the listen port on all configured addresses to // verify whether the connection handler will be able to start. LocalizableMessage errorMessage = checkAnyListenAddressInUse(listenAddresses, listenPort, allowReuseAddress, config.dn()); if (errorMessage != null) { logger.error(errorMessage); throw new InitializationException(errorMessage); } // Create a system property to store the LDAP(S) port the server is // listening to. This information can be displayed with jinfo. System.setProperty(protocol + "_port", String.valueOf(listenPort)); // Create and start a connection finalizer thread for this // connection handler. connectionFinalizer = Executors.newSingleThreadScheduledExecutor( new DirectoryThread.Factory( "LDAP Connection Finalizer for connection handler " + toString())); connectionFinalizerActiveJobQueue = new ArrayList<>(); 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); }
/** * 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(); }
/** * 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(); }
private void acceptConnection(SocketChannel clientChannel) throws DirectoryException { try { clientChannel.socket().setKeepAlive(currentConfig.isUseTCPKeepAlive()); clientChannel.socket().setTcpNoDelay(currentConfig.isUseTCPNoDelay()); } catch (SocketException se) { // TCP error occurred because connection reset/closed? In any case, // just close it and ignore. // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6378870 close(clientChannel); } // Check to see if the core server rejected the // connection (e.g., already too many connections // established). LDAPClientConnection clientConnection = new LDAPClientConnection(this, clientChannel, getProtocol()); if (clientConnection.getConnectionID() < 0) { clientConnection.disconnect( DisconnectReason.ADMIN_LIMIT_EXCEEDED, true, ERR_CONNHANDLER_REJECTED_BY_SERVER.get()); return; } InetAddress clientAddr = clientConnection.getRemoteAddress(); // Check to see if the client is on the denied list. // If so, then reject it immediately. if (!deniedClients.isEmpty() && AddressMask.matchesAny(deniedClients, clientAddr)) { clientConnection.disconnect( DisconnectReason.CONNECTION_REJECTED, currentConfig.isSendRejectionNotice(), ERR_CONNHANDLER_DENIED_CLIENT.get( clientConnection.getClientHostPort(), clientConnection.getServerHostPort())); return; } // Check to see if there is an allowed list and if // there is whether the client is on that list. If // not, then reject the connection. if (!allowedClients.isEmpty() && !AddressMask.matchesAny(allowedClients, clientAddr)) { clientConnection.disconnect( DisconnectReason.CONNECTION_REJECTED, currentConfig.isSendRejectionNotice(), ERR_CONNHANDLER_DISALLOWED_CLIENT.get( clientConnection.getClientHostPort(), clientConnection.getServerHostPort())); return; } // If we've gotten here, then we'll take the // connection so invoke the post-connect plugins and // register the client connection with a request // handler. try { PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager(); PluginResult.PostConnect pluginResult = pluginManager.invokePostConnectPlugins(clientConnection); if (!pluginResult.continueProcessing()) { clientConnection.disconnect( pluginResult.getDisconnectReason(), pluginResult.sendDisconnectNotification(), pluginResult.getErrorMessage()); return; } LDAPRequestHandler requestHandler = requestHandlers[requestHandlerIndex++]; if (requestHandlerIndex >= numRequestHandlers) { requestHandlerIndex = 0; } requestHandler.registerClient(clientConnection); } catch (Exception e) { logger.traceException(e); LocalizableMessage message = INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get( clientConnection.getClientHostPort(), clientConnection.getServerHostPort(), getExceptionMessage(e)); logger.debug(message); clientConnection.disconnect( DisconnectReason.SERVER_ERROR, currentConfig.isSendRejectionNotice(), message); } }
/** * Indicates whether this connection handler should allow interaction with LDAPv2 clients. * * @return <CODE>true</CODE> if LDAPv2 is allowed, or <CODE>false</CODE> if not. */ public boolean allowLDAPv2() { return currentConfig.isAllowLDAPV2(); }
/** * Indicates whether this connection handler should allow the use of the StartTLS extended * operation. * * @return <CODE>true</CODE> if StartTLS is allowed, or <CODE>false</CODE> if not. */ public boolean allowStartTLS() { return currentConfig.isAllowStartTLS() && !currentConfig.isUseSSL(); }
/** * 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(); }
/** * 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(); }