This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 17338ae4d20ebbdb1b8d46ea765777bf4eb2e63f Author: Quan Tran <[email protected]> AuthorDate: Thu Aug 13 13:06:48 2026 +0700 JAMES-4210 [FIX] ManageSieve commands after STARTTLS should work Expose ManageSieve command-detection state through the Netty session attribute expected by the shared STARTTLS decoder. Add a real TLS handshake regression test proving encrypted commands remain accepted after STARTTLS. --- .../james/managesieveserver/netty/NettyConstants.java | 3 ++- .../apache/james/managesieveserver/CapabilityTest.java | 18 ++++++++++++++++++ .../james/managesieveserver/ManageSieveClient.java | 17 +++++++++++++++++ .../managesieveserver/ManageSieveServerTestSystem.java | 17 +++++++++++++++++ .../src/test/resources/managesieveserver-starttls.xml | 15 +++++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/NettyConstants.java b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/NettyConstants.java index d966e796e8..70acfc4f8c 100644 --- a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/NettyConstants.java +++ b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/NettyConstants.java @@ -27,7 +27,8 @@ import io.netty.util.AttributeKey; * Just some constants which are used with the Netty implementation */ public interface NettyConstants { - AttributeKey<ChannelManageSieveResponseWriter> RESPONSE_WRITER_ATTRIBUTE_KEY = AttributeKey.valueOf("ResponseWriter"); + // AllButStartTlsLineBasedChannelHandler reads this shared key to disable command-injection detection after STARTTLS. + AttributeKey<ChannelManageSieveResponseWriter> RESPONSE_WRITER_ATTRIBUTE_KEY = AttributeKey.valueOf("session"); AttributeKey<ProxyInformation> PROXY_INFO = AttributeKey.valueOf("ProxyInfo"); AttributeKey<Session> SESSION_ATTRIBUTE_KEY = AttributeKey.valueOf("Session"); } diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/CapabilityTest.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/CapabilityTest.java index 5ea4003657..1242819ee4 100644 --- a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/CapabilityTest.java +++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/CapabilityTest.java @@ -124,6 +124,24 @@ class CapabilityTest { assertThatThrownBy(client::readResponse).isInstanceOf(EOFException.class); } + @Test + void shouldAcceptCommandsAfterSuccessfulStartTls() throws Exception { + this.testSystem.setUp("managesieveserver-starttls.xml"); + + ManageSieveClient client = new ManageSieveClient(); + client.connect(this.testSystem.getBindedIP(), this.testSystem.getBindedPort()); + client.readResponse(); + + client.sendCommand("STARTTLS"); + assertThat(client.readResponse().responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + client.execTLS(); + assertThat(client.readResponse().responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + + client.sendCommand("CAPABILITY"); + + assertThat(client.readResponse().responseType()).isEqualTo(ManageSieveClient.ResponseType.OK); + } + private String[] getSASLMechanisms(ManageSieveClient.ServerResponse response) { String saslLine = assertThat(response.responseLines()) .filteredOn(line -> line.startsWith("\"SASL\"")) diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java index 104a546b28..770f971cac 100644 --- a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java +++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveClient.java @@ -30,9 +30,12 @@ import java.util.ArrayList; import java.util.Base64; import java.util.Optional; +import javax.net.ssl.SSLSocket; + import org.apache.commons.lang3.EnumUtils; import org.apache.commons.net.SocketClient; import org.apache.commons.net.io.CRLFLineReader; +import org.apache.james.protocols.api.utils.BogusSslContextFactory; public class ManageSieveClient extends SocketClient { private static final String ENCODING = StandardCharsets.UTF_8.name(); @@ -57,6 +60,20 @@ public class ManageSieveClient extends SocketClient { @Override protected void _connectAction_() throws IOException { super._connectAction_(); + resetStreams(); + } + + public void execTLS() throws IOException { + SSLSocket sslSocket = (SSLSocket) BogusSslContextFactory.getClientContext().getSocketFactory() + .createSocket(_socket_, _socket_.getInetAddress().getHostAddress(), _socket_.getPort(), true); + sslSocket.startHandshake(); + _socket_ = sslSocket; + _input_ = sslSocket.getInputStream(); + _output_ = sslSocket.getOutputStream(); + resetStreams(); + } + + private void resetStreams() throws IOException { this.reader = new CRLFLineReader(new InputStreamReader(_input_, ENCODING)); this.writer = new BufferedWriter(new OutputStreamWriter(_output_, ENCODING)); } diff --git a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveServerTestSystem.java b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveServerTestSystem.java index f3b17d7456..926348ffaf 100644 --- a/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveServerTestSystem.java +++ b/server/protocols/protocols-managesieve/src/test/java/org/apache/james/managesieveserver/ManageSieveServerTestSystem.java @@ -19,7 +19,11 @@ package org.apache.james.managesieveserver; +import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import org.apache.commons.configuration2.HierarchicalConfiguration; import org.apache.commons.configuration2.tree.ImmutableNode; @@ -74,6 +78,9 @@ class ManageSieveServerTestSystem { private void setUp(HierarchicalConfiguration<ImmutableNode> configuration, ImmutableList<SaslMechanism> saslMechanisms) throws Exception { this.fileSystem.clear(); + if (configuration.containsKey("tls.keystore")) { + prepareKeystore(); + } Authenticator authenticator = (username, password) -> { try { return usersRepository.test(username, password.toString()); @@ -95,6 +102,16 @@ class ManageSieveServerTestSystem { this.manageSieveServer.init(); } + private void prepareKeystore() throws IOException { + Files.createDirectories(this.fileSystem.getBasedir().toPath()); + try (InputStream keystore = ClassLoader.getSystemResourceAsStream("keystore")) { + if (keystore == null) { + throw new IOException("ManageSieve test keystore is missing"); + } + Files.copy(keystore, this.fileSystem.getFile("file://keystore").toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + public void setUp(String configFilePath) throws Exception { HierarchicalConfiguration<ImmutableNode> configuration = FileConfigurationProvider.getConfig(ClassLoader.getSystemResourceAsStream(configFilePath)); setUp(configuration); diff --git a/server/protocols/protocols-managesieve/src/test/resources/managesieveserver-starttls.xml b/server/protocols/protocols-managesieve/src/test/resources/managesieveserver-starttls.xml new file mode 100644 index 0000000000..3ff3e550ef --- /dev/null +++ b/server/protocols/protocols-managesieve/src/test/resources/managesieveserver-starttls.xml @@ -0,0 +1,15 @@ +<managesieveserver enabled="true"> + <jmxName>managesieveserver</jmxName> + <bind>0.0.0.0:4190</bind> + + <tls socketTLS="false" startTLS="true"> + <keystore>file://keystore</keystore> + <secret>james72laBalle</secret> + <provider>org.bouncycastle.jce.provider.BouncyCastleProvider</provider> + </tls> + + <connectionBacklog>200</connectionBacklog> + <connectiontimeout>360</connectiontimeout> + <connectionLimit>0</connectionLimit> + <connectionLimitPerIP>0</connectionLimitPerIP> +</managesieveserver> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
