From 0b846fb78e9bf21875b8b9b16d07be4b4beeaba0 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Mon, 28 Sep 2026 12:53:31 +0000
Subject: [PATCH] [#1109] Keep a connection handler listening when a change to it is rejected (#1110)
---
opendj-server-legacy/src/test/java/org/opends/server/protocols/RejectedSSLConfigurationChangeTestCase.java | 402 ++++++++++++++++++++++++++++++++++++++++++++
opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java | 30 ++
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java | 29 ++
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java | 43 +++-
4 files changed, 477 insertions(+), 27 deletions(-)
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
index 482b318..5f40bd1 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
@@ -316,7 +316,7 @@
private void configureSSL(LDAPConnectionHandlerCfg config) throws DirectoryException {
protocol = config.isUseSSL() ? "LDAPS" : "LDAP";
if (config.isUseSSL() || config.isAllowStartTLS()) {
- sslContext = createSSLContext(config);
+ sslContext = createSSLContext(config, true);
sslEngine = createSSLEngine(config, sslContext);
} else {
sslContext = null;
@@ -581,7 +581,7 @@
// Check that the SSL configuration is valid.
&& (config.isUseSSL() || config.isAllowStartTLS())) {
try {
- createSSLEngine(config, createSSLContext(config));
+ createSSLEngine(config, createSSLContext(config, false));
} catch (DirectoryException e) {
logger.traceException(e);
@@ -937,26 +937,39 @@
}
}
- private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config) {
- if (config.isUseSSL()) {
+ private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config, boolean forUse) {
+ if (forUse && config.isUseSSL()) {
logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
enabled = false;
}
}
- private SSLContext createSSLContext(LDAPConnectionHandlerCfg config) throws DirectoryException {
+ /**
+ * Creates the SSL context for the provided configuration.
+ *
+ * @param config
+ * the configuration to create the SSL context for
+ * @param forUse
+ * {@code true} when the handler is going to use the SSL context, at its start or when a change is
+ * applied, so that an SSL handler without a usable key is disabled; {@code false} when the SSL context
+ * only checks a proposed configuration, which must leave the running handler as it is
+ * @return the SSL context
+ * @throws DirectoryException
+ * if the SSL context cannot be created
+ */
+ private SSLContext createSSLContext(LDAPConnectionHandlerCfg config, boolean forUse) throws DirectoryException {
try {
DN keyMgrDN = config.getKeyManagerProviderDN();
final ServerContext serverContext = DirectoryServer.getInstance().getServerContext();
KeyManagerProvider<?> keyManagerProvider = serverContext.getKeyManagerProvider(keyMgrDN);
if (keyManagerProvider == null) {
logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName);
- disableAndWarnIfUseSSL(config);
+ disableAndWarnIfUseSSL(config, forUse);
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);
+ disableAndWarnIfUseSSL(config, forUse);
}
final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname());
@@ -973,7 +986,7 @@
}
if (aliases.isEmpty()) {
- disableAndWarnIfUseSSL(config);
+ disableAndWarnIfUseSSL(config, forUse);
}
keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases,
friendlyName);
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
index 7f9cce5..2b118d5 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
@@ -283,7 +283,7 @@
protocol = config.isUseSSL() ? "HTTPS" : "HTTP";
if (config.isUseSSL())
{
- sslEngineConfigurator = createSSLEngineConfigurator(config);
+ sslEngineConfigurator = createSSLEngineConfigurator(config, true);
}
else
{
@@ -497,7 +497,7 @@
{
try
{
- createSSLEngineConfigurator(config);
+ createSSLEngineConfigurator(config, false);
}
catch (DirectoryException e)
{
@@ -817,7 +817,22 @@
buffer.append(handlerName);
}
- private SSLEngineConfigurator createSSLEngineConfigurator(HTTPConnectionHandlerCfg config) throws DirectoryException
+ /**
+ * Creates the SSL engine configurator for the provided configuration.
+ *
+ * @param config
+ * the configuration to create the SSL engine configurator for
+ * @param forUse
+ * {@code true} when the handler is going to use the configurator: at its start a handler
+ * without a usable key is disabled ({@link #applyConfigurationChange} sets {@code enabled}
+ * from the configuration afterwards); {@code false} when the configurator only checks a
+ * proposed configuration, which must leave the running handler as it is
+ * @return the SSL engine configurator, or {@code null} if the configuration does not use SSL
+ * @throws DirectoryException
+ * if the SSL context cannot be created
+ */
+ private SSLEngineConfigurator createSSLEngineConfigurator(HTTPConnectionHandlerCfg config, boolean forUse)
+ throws DirectoryException
{
if (!config.isUseSSL())
{
@@ -826,7 +841,7 @@
try
{
- SSLContext sslContext = createSSLContext(config);
+ SSLContext sslContext = createSSLContext(config, forUse);
SSLEngineConfigurator configurator = new SSLEngineConfigurator(sslContext);
configurator.setClientMode(false);
@@ -874,7 +889,16 @@
}
}
- private SSLContext createSSLContext(HTTPConnectionHandlerCfg config) throws Exception
+ private void disableAndWarn(boolean forUse)
+ {
+ if (forUse)
+ {
+ logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
+ enabled = false;
+ }
+ }
+
+ private SSLContext createSSLContext(HTTPConnectionHandlerCfg config, boolean forUse) throws Exception
{
if (!config.isUseSSL())
{
@@ -886,15 +910,13 @@
if (keyManagerProvider == null)
{
logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName);
- logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
keyManagerProvider = new NullKeyManagerProvider();
- enabled = false;
+ disableAndWarn(forUse);
}
else if (!keyManagerProvider.containsAtLeastOneKey())
{
logger.error(ERR_INVALID_KEYSTORE, friendlyName);
- logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
- enabled = false;
+ disableAndWarn(forUse);
}
final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname());
@@ -916,8 +938,7 @@
}
if (aliases.isEmpty())
{
- logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
- enabled = false;
+ disableAndWarn(forUse);
}
keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases, friendlyName);
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java
index f404bd9..e80cadc 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java
@@ -331,7 +331,7 @@
protocol = config.isUseSSL() ? "LDAPS" : "LDAP";
if (config.isUseSSL() || config.isAllowStartTLS())
{
- sslContext = createSSLContext(config);
+ sslContext = createSSLContext(config, true);
sslEngine = createSSLEngine(config, sslContext);
}
else
@@ -717,7 +717,7 @@
{
try
{
- createSSLEngine(config, createSSLContext(config));
+ createSSLEngine(config, createSSLContext(config, false));
}
catch (DirectoryException e)
{
@@ -1291,16 +1291,30 @@
}
}
- private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config)
+ private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config, boolean forUse)
{
- if (config.isUseSSL())
+ if (forUse && config.isUseSSL())
{
logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
enabled = false;
}
}
- private SSLContext createSSLContext(LDAPConnectionHandlerCfg config)
+ /**
+ * Creates the SSL context for the provided configuration.
+ *
+ * @param config
+ * the configuration to create the SSL context for
+ * @param forUse
+ * {@code true} when the handler is going to use the SSL context, at its start or when
+ * a change is applied, so that an SSL handler without a usable key is disabled;
+ * {@code false} when the SSL context only checks a proposed configuration, which must
+ * leave the running handler as it is
+ * @return the SSL context
+ * @throws DirectoryException
+ * if the SSL context cannot be created
+ */
+ private SSLContext createSSLContext(LDAPConnectionHandlerCfg config, boolean forUse)
throws DirectoryException
{
try
@@ -1311,14 +1325,14 @@
if (keyManagerProvider == null)
{
logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName);
- disableAndWarnIfUseSSL(config);
+ disableAndWarnIfUseSSL(config, forUse);
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);
+ disableAndWarnIfUseSSL(config, forUse);
}
final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname());
@@ -1341,7 +1355,7 @@
if (aliases.isEmpty())
{
- disableAndWarnIfUseSSL(config);
+ disableAndWarnIfUseSSL(config, forUse);
}
keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases, friendlyName);
}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/protocols/RejectedSSLConfigurationChangeTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/protocols/RejectedSSLConfigurationChangeTestCase.java
new file mode 100644
index 0000000..22ce5c3
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/protocols/RejectedSSLConfigurationChangeTestCase.java
@@ -0,0 +1,402 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2026 3A Systems, LLC.
+ */
+package org.opends.server.protocols;
+
+import static org.opends.messages.ProtocolMessages.ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE;
+import static org.opends.server.protocols.internal.InternalClientConnection.getRootConnection;
+import static org.opends.server.util.StaticUtils.*;
+import static org.testng.Assert.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.Configuration;
+import org.forgerock.opendj.config.server.ConfigChangeResult;
+import org.forgerock.opendj.config.server.ConfigurationChangeListener;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.reactive.LDAPConnectionHandler2;
+import org.forgerock.opendj.server.config.meta.HTTPConnectionHandlerCfgDefn;
+import org.forgerock.opendj.server.config.meta.LDAPConnectionHandlerCfgDefn;
+import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg;
+import org.forgerock.opendj.server.config.server.HTTPConnectionHandlerCfg;
+import org.forgerock.opendj.server.config.server.LDAPConnectionHandlerCfg;
+import org.opends.admin.ads.util.BlindTrustManager;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.api.ConnectionHandler;
+import org.opends.server.api.KeyManagerProvider;
+import org.opends.server.api.ServerShutdownListener;
+import org.opends.server.core.DeleteOperation;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ServerContext;
+import org.opends.server.extensions.InitializationUtils;
+import org.opends.server.protocols.http.HTTPConnectionHandler;
+import org.opends.server.protocols.ldap.LDAPConnectionHandler;
+import org.opends.server.types.Entry;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * A change to an SSL connection handler that is rejected because its key store cannot be loaded
+ * must leave the running handler as it was: listening, with the SSL context it had. The start of a handler, and a
+ * change applied to an LDAP handler, still disable it when it has no key it can present.
+ */
+@SuppressWarnings("javadoc")
+@Test(groups = { "precommit" }, sequential = true)
+public class RejectedSSLConfigurationChangeTestCase extends DirectoryServerTestCase
+{
+ private static final LocalizableMessage STOP_REASON = LocalizableMessage.raw("Don't need a reason.");
+ private static final DN KEY_MANAGER_DN = DN.valueOf("cn=Rejected Change Keys,cn=Key Manager Providers,cn=config");
+
+ /** How long the handler must keep serving TLS after the rejected change: its thread checks every second. */
+ private static final long KEEPS_SERVING_MS = TimeUnit.SECONDS.toMillis(3);
+ /** How long a handler that has been disabled may take to stop listening: its thread checks every second. */
+ private static final long STOPS_LISTENING_MS = TimeUnit.SECONDS.toMillis(10);
+
+ private enum Kind
+ {
+ LDAP2, LDAP_LEGACY, HTTP
+ }
+
+ private static final class NoChanges<C extends Configuration> implements ConfigurationChangeListener<C>
+ {
+ @Override
+ public boolean isConfigurationChangeAcceptable(C configuration, List<LocalizableMessage> unacceptableReasons)
+ {
+ return true;
+ }
+
+ @Override
+ public ConfigChangeResult applyConfigurationChange(C configuration)
+ {
+ return new ConfigChangeResult();
+ }
+ }
+
+ private File keyStore;
+
+ @BeforeClass
+ public void setUp() throws Exception
+ {
+ TestCaseUtils.startServer();
+ keyStore = File.createTempFile("rejected-change", ".keystore");
+ keyStore.deleteOnExit();
+ restoreKeyStore();
+ TestCaseUtils.addEntry(
+ "dn: " + KEY_MANAGER_DN,
+ "objectClass: top",
+ "objectClass: ds-cfg-key-manager-provider",
+ "objectClass: ds-cfg-file-based-key-manager-provider",
+ "cn: Rejected Change Keys",
+ "ds-cfg-java-class: org.opends.server.extensions.FileBasedKeyManagerProvider",
+ "ds-cfg-enabled: true",
+ "ds-cfg-key-store-type: JKS",
+ "ds-cfg-key-store-file: " + keyStore.getAbsolutePath(),
+ "ds-cfg-key-store-pin: password");
+ }
+
+ @AfterClass
+ public void tearDown() throws Exception
+ {
+ // A handler registering for changes to its configuration also registers a reference to its key
+ // manager provider, which keeps the provider from being deleted and outlives the handler.
+ // Registering for changes to the same entry without a key manager provider drops the reference.
+ final LDAPConnectionHandlerCfg ldap =
+ (LDAPConnectionHandlerCfg) configuration(Kind.LDAP2, 1, null, "5 megabytes", false);
+ final NoChanges<LDAPConnectionHandlerCfg> ldapListener = new NoChanges<>();
+ ldap.addLDAPChangeListener(ldapListener);
+ ldap.removeLDAPChangeListener(ldapListener);
+ final HTTPConnectionHandlerCfg http =
+ (HTTPConnectionHandlerCfg) configuration(Kind.HTTP, 1, null, "5 megabytes", false);
+ final NoChanges<HTTPConnectionHandlerCfg> httpListener = new NoChanges<>();
+ http.addHTTPChangeListener(httpListener);
+ http.removeHTTPChangeListener(httpListener);
+
+ final DeleteOperation delete = getRootConnection().processDelete(KEY_MANAGER_DN);
+ assertEquals(delete.getResultCode(), ResultCode.SUCCESS, String.valueOf(delete.getErrorMessage()));
+ Files.deleteIfExists(keyStore.toPath());
+ }
+
+ @BeforeMethod
+ public void restoreKeyStore() throws IOException
+ {
+ Files.copy(getFileForPath("config/server.keystore").toPath(), keyStore.toPath(),
+ StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ @DataProvider
+ public Object[][] handlers()
+ {
+ return new Object[][] {
+ { Kind.LDAP2, null }, { Kind.LDAP2, "server-cert" },
+ { Kind.LDAP_LEGACY, null }, { Kind.LDAP_LEGACY, "server-cert" },
+ { Kind.HTTP, null }, { Kind.HTTP, "server-cert" },
+ };
+ }
+
+ @Test(dataProvider = "handlers")
+ public void rejectedChangeKeepsTheHandlerListening(Kind kind, String certNickname) throws Exception
+ {
+ final int port = TestCaseUtils.findFreePort();
+ final ConnectionHandler<?> handler = start(kind, configuration(kind, port, certNickname, "5 megabytes", true));
+ try
+ {
+ assertServesTLS(port);
+
+ Files.write(keyStore.toPath(), new byte[] { 1, 2, 3, 4 });
+ final List<LocalizableMessage> reasons = new ArrayList<>();
+ assertFalse(handler.isConfigurationAcceptable(configuration(kind, port, certNickname, "6 megabytes", true), reasons),
+ "a change needing a key store that cannot be loaded was accepted");
+ assertFalse(reasons.isEmpty(), "the change was rejected without a reason");
+ assertEquals(reasons.get(0).ordinal(), ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.ordinal(), String.valueOf(reasons));
+
+ assertKeepsServingTLS(port);
+ }
+ finally
+ {
+ stop(handler);
+ }
+ }
+
+ @DataProvider
+ public Object[][] kinds()
+ {
+ return new Object[][] { { Kind.LDAP2 }, { Kind.LDAP_LEGACY }, { Kind.HTTP } };
+ }
+
+ /**
+ * A check that finds no key manager provider registered under the configured DN leaves the running handler as it
+ * is. The configuration entry of an enabled provider whose key store could not be loaded at the start of the
+ * server exists, but the provider is not registered.
+ */
+ @Test(dataProvider = "kinds")
+ public void checkWithoutKeyManagerProviderKeepsTheHandlerListening(Kind kind) throws Exception
+ {
+ final int port = TestCaseUtils.findFreePort();
+ final ConnectionHandler<?> handler = start(kind, configuration(kind, port, null, "5 megabytes", true));
+ try
+ {
+ assertServesTLS(port);
+
+ final KeyManagerProvider<?> provider = DirectoryServer.getKeyManagerProvider(KEY_MANAGER_DN);
+ assertNotNull(provider, "the key manager provider of the test is not registered");
+ DirectoryServer.deregisterKeyManagerProvider(KEY_MANAGER_DN);
+ try
+ {
+ // Whether the change is accepted does not matter here: the running handler must not change either way.
+ handler.isConfigurationAcceptable(configuration(kind, port, null, "6 megabytes", true),
+ new ArrayList<LocalizableMessage>());
+ }
+ finally
+ {
+ DirectoryServer.registerKeyManagerProvider(KEY_MANAGER_DN, provider);
+ }
+
+ assertKeepsServingTLS(port);
+ }
+ finally
+ {
+ stop(handler);
+ }
+ }
+
+ /** The start of a handler still disables it when its key store holds no key it can present. */
+ @Test(dataProvider = "kinds")
+ public void handlerWithoutItsCertificateDoesNotListen(Kind kind) throws Exception
+ {
+ final int port = TestCaseUtils.findFreePort();
+ final ConnectionHandler<?> handler = start(kind, configuration(kind, port, "no-such-cert", "5 megabytes", true));
+ try (Socket socket = new Socket("127.0.0.1", port))
+ {
+ fail("the handler listens on port " + port + " without the certificate it is configured to present");
+ }
+ catch (ConnectException expected)
+ {
+ // disabled at its start
+ }
+ finally
+ {
+ stop(handler);
+ }
+ }
+
+ /**
+ * An applied change that leaves an LDAP handler without the certificate it is configured to present still disables
+ * it. The check accepts such a change, so dsconfig reaches this road. Only {@link LDAPConnectionHandler2} is
+ * covered: the legacy handler keeps accepting TCP connections while it is disabled, so a refused connect cannot
+ * tell, and the HTTP handler sets {@code enabled} from the configuration after the SSL context is built.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void appliedChangeWithoutItsCertificateStopsListening() throws Exception
+ {
+ final int port = TestCaseUtils.findFreePort();
+ final ConnectionHandler<?> handler = start(Kind.LDAP2, configuration(Kind.LDAP2, port, null, "5 megabytes", true));
+ try
+ {
+ assertServesTLS(port);
+
+ final LDAPConnectionHandlerCfg change =
+ (LDAPConnectionHandlerCfg) configuration(Kind.LDAP2, port, "no-such-cert", "5 megabytes", true);
+ final List<LocalizableMessage> reasons = new ArrayList<>();
+ assertTrue(handler.isConfigurationAcceptable(change, reasons), String.valueOf(reasons));
+ ((ConfigurationChangeListener<LDAPConnectionHandlerCfg>) handler).applyConfigurationChange(change);
+
+ final long deadline = System.currentTimeMillis() + STOPS_LISTENING_MS;
+ while (true)
+ {
+ try (Socket socket = new Socket("127.0.0.1", port))
+ {
+ assertTrue(System.currentTimeMillis() < deadline,
+ "the handler still listens on port " + port + " after a change took its certificate away");
+ }
+ catch (ConnectException expected)
+ {
+ break;
+ }
+ Thread.sleep(250);
+ }
+ }
+ finally
+ {
+ stop(handler);
+ }
+ }
+
+ private static ConnectionHandlerCfg configuration(Kind kind, int port, String certNickname, String maxRequestSize,
+ boolean useSSL) throws Exception
+ {
+ final List<String> lines = new ArrayList<>();
+ lines.add("dn: cn=Rejected Change Handler,cn=Connection Handlers,cn=config");
+ lines.add("objectClass: top");
+ lines.add("objectClass: ds-cfg-connection-handler");
+ lines.add("cn: Rejected Change Handler");
+ lines.add("ds-cfg-enabled: true");
+ lines.add("ds-cfg-listen-address: 127.0.0.1");
+ lines.add("ds-cfg-listen-port: " + port);
+ lines.add("ds-cfg-accept-backlog: 128");
+ lines.add("ds-cfg-keep-stats: false");
+ lines.add("ds-cfg-use-tcp-keep-alive: true");
+ lines.add("ds-cfg-use-tcp-no-delay: true");
+ lines.add("ds-cfg-allow-tcp-reuse-address: true");
+ lines.add("ds-cfg-max-request-size: " + maxRequestSize);
+ lines.add("ds-cfg-use-ssl: " + useSSL);
+ lines.add("ds-cfg-ssl-client-auth-policy: disabled");
+ if (useSSL)
+ {
+ lines.add("ds-cfg-key-manager-provider: " + KEY_MANAGER_DN);
+ }
+ if (certNickname != null)
+ {
+ lines.add("ds-cfg-ssl-cert-nickname: " + certNickname);
+ }
+ if (kind == Kind.HTTP)
+ {
+ lines.add("objectClass: ds-cfg-http-connection-handler");
+ lines.add("ds-cfg-java-class: " + HTTPConnectionHandler.class.getName());
+ lines.add("ds-cfg-buffer-size: 4096 bytes");
+ lines.add("ds-cfg-max-blocked-write-time-limit: 2 minutes");
+ final Entry entry = TestCaseUtils.makeEntry(lines.toArray(new String[0]));
+ return InitializationUtils.getConfiguration(HTTPConnectionHandlerCfgDefn.getInstance(), entry);
+ }
+ lines.add("objectClass: ds-cfg-ldap-connection-handler");
+ lines.add("ds-cfg-java-class: "
+ + (kind == Kind.LDAP2 ? LDAPConnectionHandler2.class : LDAPConnectionHandler.class).getName());
+ lines.add("ds-cfg-allow-ldap-v2: false");
+ lines.add("ds-cfg-send-rejection-notice: true");
+ lines.add("ds-cfg-num-request-handlers: 2");
+ lines.add("ds-cfg-allow-start-tls: false");
+ final Entry entry = TestCaseUtils.makeEntry(lines.toArray(new String[0]));
+ return InitializationUtils.getConfiguration(LDAPConnectionHandlerCfgDefn.getInstance(), entry);
+ }
+
+ private static ConnectionHandler<?> start(Kind kind, ConnectionHandlerCfg config) throws Exception
+ {
+ final ServerContext serverContext = DirectoryServer.getInstance().getServerContext();
+ final ConnectionHandler<?> handler;
+ switch (kind)
+ {
+ case LDAP2:
+ final LDAPConnectionHandler2 ldap2 = new LDAPConnectionHandler2();
+ ldap2.initializeConnectionHandler(serverContext, (LDAPConnectionHandlerCfg) config);
+ handler = ldap2;
+ break;
+ case LDAP_LEGACY:
+ final LDAPConnectionHandler legacy = new LDAPConnectionHandler();
+ legacy.initializeConnectionHandler(serverContext, (LDAPConnectionHandlerCfg) config);
+ handler = legacy;
+ break;
+ default:
+ final HTTPConnectionHandler http = new HTTPConnectionHandler();
+ http.initializeConnectionHandler(serverContext, (HTTPConnectionHandlerCfg) config);
+ handler = http;
+ break;
+ }
+ handler.start();
+ return handler;
+ }
+
+ private static void stop(ConnectionHandler<?> handler) throws InterruptedException
+ {
+ ((ServerShutdownListener) handler).processServerShutdown(STOP_REASON);
+ handler.finalizeConnectionHandler(STOP_REASON);
+ handler.join(10000);
+ assertFalse(handler.isAlive(), "the connection handler thread is still running");
+ }
+
+ private static void assertKeepsServingTLS(int port) throws Exception
+ {
+ final long deadline = System.currentTimeMillis() + KEEPS_SERVING_MS;
+ do
+ {
+ assertServesTLS(port);
+ Thread.sleep(250);
+ }
+ while (System.currentTimeMillis() < deadline);
+ }
+
+ private static void assertServesTLS(int port) throws Exception
+ {
+ final SSLContext client = SSLContext.getInstance("TLS");
+ client.init(null, new TrustManager[] { new BlindTrustManager() }, null);
+ try (SSLSocket socket = (SSLSocket) client.getSocketFactory().createSocket("127.0.0.1", port))
+ {
+ socket.setSoTimeout(10000);
+ socket.startHandshake();
+ assertTrue(socket.getSession().getPeerCertificates().length > 0);
+ }
+ catch (IOException e)
+ {
+ fail("the handler does not serve TLS on port " + port + ": " + e, e);
+ }
+ }
+}
--
Gitblit v1.10.0