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 68384c181a08c5f1ce4dbd82d748eb7acfacf904 Author: Quan Tran <[email protected]> AuthorDate: Fri Jul 24 16:49:13 2026 +0700 JAMES-4215 Make SMTP maximum line length configurable Kerberos GSSAPI SASL tokens can exceed SMTP's existing 8,192-byte line limit. Allow administrators to increase the SMTP command and SASL continuation line limit while preserving the existing default. Restrict configured values to between 1,000 bytes and 1 MiB to limit memory exposure. --- docs/modules/servers/partials/configure/smtp.adoc | 5 ++++ .../apache/james/smtpserver/netty/SMTPServer.java | 10 +++++++- .../apache/james/smtpserver/SMTPServerTest.java | 27 ++++++++++++++++++++++ .../james/smtpserver/SMTPTestConfiguration.java | 8 +++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/modules/servers/partials/configure/smtp.adoc b/docs/modules/servers/partials/configure/smtp.adoc index eb687975e2..92721f56fc 100644 --- a/docs/modules/servers/partials/configure/smtp.adoc +++ b/docs/modules/servers/partials/configure/smtp.adoc @@ -51,6 +51,11 @@ The last character of the value may be a unit. Supported units are `B` for bytes, `K` for kibibyte (1,024 bytes), `M` for mebibyte (1,024 kibibytes), and `G` for gibibyte (1,024 mebibytes). If no unit is given, the value is interpreted as kibibytes. +| `maxLineLength` +| Sets the maximum SMTP command or SASL continuation line length in bytes. +It is an optional integer that defaults to 8,192 and must be between 1,000 and 1,048,576. +Increase it only when required by measured SASL token sizes, as the limit applies to every SMTP input line and larger values increase memory exposure. + | `heloEhloEnforcement` | Configures whether to enforce the use of the HELO / EHLO salutation before a MAIL command is accepted. It is an optional boolean that defaults to `true`. diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java index b60abaa58c..0fb5efb631 100644 --- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java +++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java @@ -77,6 +77,8 @@ import io.netty.util.concurrent.GlobalEventExecutor; * NIO SMTPServer which use Netty */ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServerMBean, Disconnector, ConnectionDescriptionSupplier { + private static final int MIN_LINE_LENGTH = 1000; + private static final int MAX_LINE_LENGTH = 1024 * 1024; private static final Logger LOGGER = LoggerFactory.getLogger(SMTPServer.class); private SMTPProtocol transport; @@ -166,6 +168,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe * 0, means no limit. */ private long maxMessageSize = 0; + private int maxLineLength = AbstractChannelPipelineFactory.MAX_LINE_LENGTH; /** * The configuration data to be passed to the handler @@ -244,6 +247,11 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe LOGGER.info("No maximum message size is enforced for this server."); } + maxLineLength = configuration.getInt("maxLineLength", AbstractChannelPipelineFactory.MAX_LINE_LENGTH); + if (maxLineLength < MIN_LINE_LENGTH || maxLineLength > MAX_LINE_LENGTH) { + throw new ConfigurationException(String.format("maxLineLength must be between %d and %d", MIN_LINE_LENGTH, MAX_LINE_LENGTH)); + } + heloEhloEnforcement = configuration.getBoolean("heloEhloEnforcement", true); // get the smtpGreeting @@ -399,7 +407,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe @Override protected ChannelHandlerFactory createFrameHandlerFactory() { - return new AllButStartTlsLineChannelHandlerFactory("starttls", AbstractChannelPipelineFactory.MAX_LINE_LENGTH); + return new AllButStartTlsLineChannelHandlerFactory("starttls", maxLineLength); } public AuthenticationAnnounceMode getAuthRequired() { diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java index 15943f6863..a62bea29d1 100644 --- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java +++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java @@ -20,6 +20,7 @@ package org.apache.james.smtpserver; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Fail.fail; import java.io.BufferedReader; @@ -190,6 +191,32 @@ public class SMTPServerTest { smtpProtocol.disconnect(); } + @Test + void configuredMaxLineLengthShouldAcceptLargerCommand() throws Exception { + smtpConfiguration.setMaxLineLength(65536); + init(smtpConfiguration); + + SMTPClient smtpProtocol = new SMTPClient(); + InetSocketAddress bindedAddress = testSystem.getBindedAddress(); + smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort()); + + smtpProtocol.sendCommand("EHLO " + "A".repeat(AbstractChannelPipelineFactory.MAX_LINE_LENGTH * 2)); + + assertThat(smtpProtocol.getReplyCode()).isEqualTo(250); + smtpProtocol.disconnect(); + } + + @ParameterizedTest + @ValueSource(ints = { 999, 1048577 }) + void maxLineLengthShouldBeBounded(int maxLineLength) { + smtpConfiguration.setMaxLineLength(maxLineLength); + smtpConfiguration.init(); + + assertThatThrownBy(() -> initSMTPServer(smtpConfiguration)) + .isInstanceOf(org.apache.commons.configuration2.ex.ConfigurationException.class) + .hasMessage("maxLineLength must be between 1000 and 1048576"); + } + @Test public void testConnectionLimit() throws Exception { smtpConfiguration.setConnectionLimit(2); diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPTestConfiguration.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPTestConfiguration.java index 2aea182b69..5e989bf8c2 100644 --- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPTestConfiguration.java +++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPTestConfiguration.java @@ -29,6 +29,7 @@ import org.apache.james.smtpserver.fastfail.ValidSenderDomainHandler; public class SMTPTestConfiguration extends BaseHierarchicalConfiguration { private int maxMessageSizeKB = 0; + private Integer maxLineLength; private String authorizedAddresses = "127.0.0.0/8"; private String authorizingMode = "false"; private boolean verifyIdentity = false; @@ -54,6 +55,10 @@ public class SMTPTestConfiguration extends BaseHierarchicalConfiguration { maxMessageSizeKB = kilobytes; } + public void setMaxLineLength(int maxLineLength) { + this.maxLineLength = maxLineLength; + } + public void setAuthorizedAddresses(String authorizedAddresses) { this.authorizedAddresses = authorizedAddresses; } @@ -128,6 +133,9 @@ public class SMTPTestConfiguration extends BaseHierarchicalConfiguration { addProperty("connectiontimeout", 360000); addProperty("authorizedAddresses", authorizedAddresses); addProperty("maxmessagesize", maxMessageSizeKB); + if (maxLineLength != null) { + addProperty("maxLineLength", maxLineLength); + } addProperty("authRequired", authorizingMode); addProperty("heloEhloEnforcement", heloEhloEnforcement); addProperty("addressBracketsEnforcement", addressBracketsEnforcement); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
