This is an automated email from the ASF dual-hosted git repository.
chibenwa pushed a commit to branch 3.9.x
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/3.9.x by this push:
new cf95091d15 JAMES-4219 Allow customizing some SMTP replies (#3133)
(#3137)
cf95091d15 is described below
commit cf95091d156e897cfc328c547f24b065ad2cf71e
Author: Benoit TELLIER <[email protected]>
AuthorDate: Thu Aug 27 10:04:00 2026 +0700
JAMES-4219 Allow customizing some SMTP replies (#3133) (#3137)
---
docs/modules/servers/partials/configure/smtp.adoc | 18 ++++
.../james/protocols/smtp/SMTPConfiguration.java | 11 +++
.../james/protocols/smtp/SMTPErrorMessages.java | 71 ++++++++++++++++
.../smtp/core/esmtp/MailSizeEsmtpExtension.java | 24 ++++--
.../core/fastfail/AbstractValidRcptHandler.java | 9 +-
.../protocols/smtp/SMTPErrorMessagesTest.java | 98 ++++++++++++++++++++++
.../sample-configuration/lmtpserver.xml | 9 ++
.../sample-configuration/smtpserver.xml | 9 ++
.../apache/james/smtp/SmtpSizeLimitationTest.java | 2 +-
.../apache/james/lmtpserver/netty/LMTPServer.java | 13 +++
.../apache/james/smtpserver/netty/SMTPServer.java | 13 +++
.../apache/james/smtpserver/SMTPServerTest.java | 51 ++++++++++-
.../james/smtpserver/SMTPTestConfiguration.java | 16 ++++
.../james/smtpserver/ValidRcptHandlerTest.java | 34 ++++++++
14 files changed, 366 insertions(+), 12 deletions(-)
diff --git a/docs/modules/servers/partials/configure/smtp.adoc
b/docs/modules/servers/partials/configure/smtp.adoc
index 5c794bacdf..1c64c9a560 100644
--- a/docs/modules/servers/partials/configure/smtp.adoc
+++ b/docs/modules/servers/partials/configure/smtp.adoc
@@ -183,6 +183,21 @@ size, in kbytes, of any message that will be transmitted
by this SMTP server. I
a per user, limit. If the value is zero then there is no limit. If the tag
isn't specified, the service will
default to an unlimited message size. Must be a positive integer, optionally
with a unit: B, K, M, G.
+| errorMessages
+| Overrides the human readable texts of some SMTP error responses, which
allows administrators to translate or
+reword the messages their users are exposed to. Return codes and DSN statuses
are never customizable as remote
+servers do rely on them. All entries are optional, and default to the values
shown below:
+
+....
+<errorMessages>
+ <!-- Returned as `552 5.3.4 <text>` when a mail exceeds maxmessagesize -->
+ <oversizedMail>Message size exceeds fixed maximum message
size</oversizedMail>
+ <!-- Returned as `550 5.1.1 <text> <recipient>` when a recipient does not
exist: the rejected
+ recipient is always appended to the text. -->
+ <unknownUser>Unknown user:</unknownUser>
+</errorMessages>
+....
+
| heloEhloEnforcement
| This sets whether to enforce the use of HELO/EHLO salutation before a
MAIL command is accepted. If unspecified, the value defaults to true.
@@ -286,6 +301,9 @@ By default, it is deactivated. You can activate it
alongside SMTP and bind for e
The default LMTP server stores directly emails in user mailboxes, without
further treatment.
+Just like for SMTP, the `errorMessages` element can be used to customize the
human readable texts of some LMTP
+error responses (see above).
+
However we do ship an alternative handler chain allowing to execute the mailet
container, thus achieving a behaviour similar
to the default SMTP protocol. Here is how to achieve this:
diff --git
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
index a0eb940633..694331730d 100644
---
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
+++
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java
@@ -63,6 +63,17 @@ public interface SMTPConfiguration extends
ProtocolConfiguration {
*/
long getMaxMessageSize();
+ /**
+ * Returns the human readable texts returned to the client upon rejection
of a command.
+ *
+ * This allows administrators to customize (translate...) the messages
their users are exposed to.
+ *
+ * @return the customizable parts of the SMTP error responses
+ */
+ default SMTPErrorMessages errorMessages() {
+ return SMTPErrorMessages.DEFAULT;
+ }
+
/**
* Returns whether relaying is allowed for the IP address passed.
*
diff --git
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPErrorMessages.java
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPErrorMessages.java
new file mode 100644
index 0000000000..9ce1851312
--- /dev/null
+++
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPErrorMessages.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.protocols.smtp;
+
+import java.util.Optional;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.james.core.MailAddress;
+
+/**
+ * Human readable texts returned to the client upon rejection of a command.
+ *
+ * Those can be overridden by the administrator - typically in order to
translate them - through the
+ * {@code errorMessages} section of the server configuration:
+ *
+ * <pre>{@code
+ * <errorMessages>
+ * <oversizedMail>Message size exceeds fixed maximum message
size</oversizedMail>
+ * <unknownUser>The recipient does not exist.</unknownUser>
+ * </errorMessages>
+ * }</pre>
+ *
+ * Only the human readable part of the SMTP response is customizable: return
codes and DSN statuses
+ * are left untouched as remote servers do rely on them.
+ */
+public record SMTPErrorMessages(Optional<String> oversizedMail,
Optional<String> unknownUser) {
+ public static final String DEFAULT_OVERSIZED_MAIL = "Message size exceeds
fixed maximum message size";
+ public static final String DEFAULT_UNKNOWN_USER = "Unknown user:";
+
+ public static final SMTPErrorMessages DEFAULT = new
SMTPErrorMessages(Optional.empty(), Optional.empty());
+
+ public static SMTPErrorMessages parse(Configuration configuration) {
+ return new SMTPErrorMessages(
+
Optional.ofNullable(configuration.getString("errorMessages.oversizedMail",
null)),
+
Optional.ofNullable(configuration.getString("errorMessages.unknownUser",
null)));
+ }
+
+ /**
+ * Text of the response rejecting a mail exceeding the maximum message
size.
+ */
+ public String oversizedMailMessage() {
+ return oversizedMail.orElse(DEFAULT_OVERSIZED_MAIL);
+ }
+
+ /**
+ * Text of the response rejecting a recipient that does not exist.
+ *
+ * The rejected recipient is appended to the configured text, so that the
client is always told which
+ * of its recipients got rejected.
+ */
+ public String unknownUserMessage(MailAddress recipient) {
+ return unknownUser.orElse(DEFAULT_UNKNOWN_USER) + " " +
recipient.asString();
+ }
+}
diff --git
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/MailSizeEsmtpExtension.java
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/MailSizeEsmtpExtension.java
index 6e6774acaa..f980cad261 100644
---
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/MailSizeEsmtpExtension.java
+++
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/MailSizeEsmtpExtension.java
@@ -57,14 +57,22 @@ public class MailSizeEsmtpExtension implements
MailParametersHook, EhloExtension
.smtpReturnCode(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS)
.smtpDescription(DSNStatus.getStatus(DSNStatus.PERMANENT,
DSNStatus.DELIVERY_INVALID_ARG) + " Syntactically incorrect value for SIZE
parameter")
.build();
- private static final HookResult QUOTA_EXCEEDED = HookResult.builder()
- .hookReturnCode(HookReturnCode.deny())
- .smtpReturnCode(SMTPRetCode.QUOTA_EXCEEDED)
- .smtpDescription(DSNStatus.getStatus(DSNStatus.PERMANENT,
DSNStatus.SYSTEM_MSG_TOO_BIG) + " Message size exceeds fixed maximum message
size")
- .build();
public static final int SINGLE_CHARACTER_LINE = 3;
public static final int DOT_BYTE = 46;
+ private static HookResult quotaExceeded(SMTPSession session) {
+ return HookResult.builder()
+ .hookReturnCode(HookReturnCode.deny())
+ .smtpReturnCode(SMTPRetCode.QUOTA_EXCEEDED)
+ .smtpDescription(quotaExceededDescription(session))
+ .build();
+ }
+
+ private static String quotaExceededDescription(SMTPSession session) {
+ return DSNStatus.getStatus(DSNStatus.PERMANENT,
DSNStatus.SYSTEM_MSG_TOO_BIG)
+ + " " +
session.getConfiguration().errorMessages().oversizedMailMessage();
+ }
+
@Override
public HookResult doMailParameter(SMTPSession session, String paramName,
String paramValue) {
@@ -123,7 +131,7 @@ public class MailSizeEsmtpExtension implements
MailParametersHook, EhloExtension
size,
maxMessageSize);
- return QUOTA_EXCEEDED;
+ return quotaExceeded(session);
} else {
// put the message size in the message state so it can be used
// later to restrict messages for user quotas, etc.
@@ -141,7 +149,7 @@ public class MailSizeEsmtpExtension implements
MailParametersHook, EhloExtension
if (failed) {
if (isDataTerminated(line)) {
next.onLine(session, line);
- return new SMTPResponse(SMTPRetCode.QUOTA_EXCEEDED, "Quota
exceeded");
+ return new SMTPResponse(SMTPRetCode.QUOTA_EXCEEDED,
quotaExceededDescription(session));
} else {
return null;
}
@@ -181,7 +189,7 @@ public class MailSizeEsmtpExtension implements
MailParametersHook, EhloExtension
LOGGER.info("Rejected message from {} from {} exceeding system
maximum message size of {}",
session.getAttachment(SMTPSession.SENDER,
State.Transaction).orElse(MaybeSender.nullSender()).asPrettyString(),
session.getRemoteAddress().getAddress().getHostAddress(),
session.getConfiguration().getMaxMessageSize());
- return QUOTA_EXCEEDED;
+ return quotaExceeded(session);
} else {
return HookResult.DECLINED;
}
diff --git
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/fastfail/AbstractValidRcptHandler.java
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/fastfail/AbstractValidRcptHandler.java
index b16ea57bbc..898e4d0d0c 100644
---
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/fastfail/AbstractValidRcptHandler.java
+++
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/fastfail/AbstractValidRcptHandler.java
@@ -60,7 +60,7 @@ public abstract class AbstractValidRcptHandler implements
RcptHook {
return HookResult.builder()
.hookReturnCode(HookReturnCode.deny())
.smtpReturnCode(SMTPRetCode.MAILBOX_PERM_UNAVAILABLE)
- .smtpDescription(DSNStatus.getStatus(DSNStatus.PERMANENT,
DSNStatus.ADDRESS_MAILBOX) + " Unknown user: " + rcpt.asString())
+ .smtpDescription(unknownUserDescription(session, rcpt))
.build();
} catch (Exception e) {
LOGGER.error("Encounter an error upon RCPT validation ({}),
deny-soft", rcpt.asString(), e);
@@ -90,10 +90,15 @@ public abstract class AbstractValidRcptHandler implements
RcptHook {
return HookResult.builder()
.hookReturnCode(HookReturnCode.deny())
.smtpReturnCode(SMTPRetCode.MAILBOX_PERM_UNAVAILABLE)
- .smtpDescription(DSNStatus.getStatus(DSNStatus.PERMANENT,
DSNStatus.ADDRESS_MAILBOX) + " Unknown user: " + rcpt.asString())
+ .smtpDescription(unknownUserDescription(session, rcpt))
.build();
}
+ private static String unknownUserDescription(SMTPSession session,
MailAddress rcpt) {
+ return DSNStatus.getStatus(DSNStatus.PERMANENT,
DSNStatus.ADDRESS_MAILBOX)
+ + " " +
session.getConfiguration().errorMessages().unknownUserMessage(rcpt);
+ }
+
/**
* Return true if email for the given recipient should get accepted
*/
diff --git
a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/SMTPErrorMessagesTest.java
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/SMTPErrorMessagesTest.java
new file mode 100644
index 0000000000..09fb3b832c
--- /dev/null
+++
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/SMTPErrorMessagesTest.java
@@ -0,0 +1,98 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.protocols.smtp;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayInputStream;
+
+import jakarta.mail.internet.AddressException;
+
+import org.apache.commons.configuration2.XMLConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.io.FileHandler;
+import org.apache.james.core.MailAddress;
+import org.junit.jupiter.api.Test;
+
+class SMTPErrorMessagesTest {
+ private static final MailAddress RECIPIENT = recipient();
+
+ private static MailAddress recipient() {
+ try {
+ return new MailAddress("[email protected]");
+ } catch (AddressException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static SMTPErrorMessages parse(String xml) throws
ConfigurationException {
+ XMLConfiguration configuration = new XMLConfiguration();
+ new FileHandler(configuration).load(new
ByteArrayInputStream(xml.getBytes(UTF_8)));
+ return SMTPErrorMessages.parse(configuration);
+ }
+
+ @Test
+ void shouldFallBackToDefaultsWhenNotConfigured() throws Exception {
+ SMTPErrorMessages errorMessages = parse("<smtpserver/>");
+
+ assertThat(errorMessages.oversizedMailMessage())
+ .isEqualTo("Message size exceeds fixed maximum message size");
+ assertThat(errorMessages.unknownUserMessage(RECIPIENT))
+ .isEqualTo("Unknown user: [email protected]");
+ }
+
+ @Test
+ void shouldFallBackToDefaultsWhenEmptySection() throws Exception {
+ SMTPErrorMessages errorMessages =
parse("<smtpserver><errorMessages/></smtpserver>");
+
+ assertThat(errorMessages.oversizedMailMessage())
+ .isEqualTo("Message size exceeds fixed maximum message size");
+ assertThat(errorMessages.unknownUserMessage(RECIPIENT))
+ .isEqualTo("Unknown user: [email protected]");
+ }
+
+ @Test
+ void shouldReturnConfiguredMessages() throws Exception {
+ SMTPErrorMessages errorMessages = parse("<smtpserver>" +
+ " <errorMessages>" +
+ " <oversizedMail>Votre message est trop
volumineux</oversizedMail>" +
+ " <unknownUser>Ce destinataire n'existe pas</unknownUser>" +
+ " </errorMessages>" +
+ "</smtpserver>");
+
+ assertThat(errorMessages.oversizedMailMessage())
+ .isEqualTo("Votre message est trop volumineux");
+ assertThat(errorMessages.unknownUserMessage(RECIPIENT))
+ .isEqualTo("Ce destinataire n'existe pas [email protected]");
+ }
+
+ @Test
+ void configuredUnknownUserMessageShouldStillDiscloseTheRecipient() throws
Exception {
+ SMTPErrorMessages errorMessages = parse("<smtpserver>" +
+ " <errorMessages>" +
+ " <unknownUser>The recipient does not exist.</unknownUser>" +
+ " </errorMessages>" +
+ "</smtpserver>");
+
+ assertThat(errorMessages.unknownUserMessage(RECIPIENT))
+ .isEqualTo("The recipient does not exist. [email protected]");
+ }
+}
diff --git a/server/apps/distributed-app/sample-configuration/lmtpserver.xml
b/server/apps/distributed-app/sample-configuration/lmtpserver.xml
index 723da3fb26..b95768464a 100644
--- a/server/apps/distributed-app/sample-configuration/lmtpserver.xml
+++ b/server/apps/distributed-app/sample-configuration/lmtpserver.xml
@@ -35,6 +35,15 @@
<!-- This sets the maximum allowed message size (in kilobytes) for
this -->
<!-- LMTP service. If unspecified, the value defaults to 0, which
means no limit. -->
<maxmessagesize>0</maxmessagesize>
+ <!-- Optional. Overrides the human readable texts of some error
responses, typically in order to
+ translate them. Return codes and DSN statuses are not
customizable.
+ The rejected recipient is always appended to the unknownUser text.
+
+ <errorMessages>
+ <oversizedMail>Message size exceeds fixed maximum message
size</oversizedMail>
+ <unknownUser>Unknown user:</unknownUser>
+ </errorMessages>
+ -->
<handlerchain>
<handler class="org.apache.james.lmtpserver.CoreCmdHandlerLoader"/>
</handlerchain>
diff --git a/server/apps/distributed-app/sample-configuration/smtpserver.xml
b/server/apps/distributed-app/sample-configuration/smtpserver.xml
index 3ad5f2b42f..f1e7ad006f 100644
--- a/server/apps/distributed-app/sample-configuration/smtpserver.xml
+++ b/server/apps/distributed-app/sample-configuration/smtpserver.xml
@@ -54,6 +54,15 @@
<authorizedAddresses>127.0.0.0/8</authorizedAddresses>
<verifyIdentity>true</verifyIdentity>
<maxmessagesize>0</maxmessagesize>
+ <!-- Optional. Overrides the human readable texts of some error
responses, typically in order to
+ translate them. Return codes and DSN statuses are not
customizable.
+ The rejected recipient is always appended to the unknownUser text.
+
+ <errorMessages>
+ <oversizedMail>Message size exceeds fixed maximum message
size</oversizedMail>
+ <unknownUser>Unknown user:</unknownUser>
+ </errorMessages>
+ -->
<addressBracketsEnforcement>true</addressBracketsEnforcement>
<smtpGreeting>Apache JAMES awesome SMTP Server</smtpGreeting>
<handlerchain
coreHandlersPackage="org.apache.james.smtpserver.NoAuthCmdHandlerLoader">
diff --git
a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpSizeLimitationTest.java
b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpSizeLimitationTest.java
index 416d140e48..6779eae892 100644
---
a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpSizeLimitationTest.java
+++
b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpSizeLimitationTest.java
@@ -77,7 +77,7 @@ class SmtpSizeLimitationTest {
messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
.authenticate(USER, PASSWORD)
.sendMessageWithHeaders(USER, USER, Strings.repeat("Long
message\r\n", 1024)))
- .isEqualTo(new SMTPSendingException(SmtpSendingStep.Data, "552
Quota exceeded\n"));
+ .isEqualTo(new SMTPSendingException(SmtpSendingStep.Data, "552
5.3.4 Message size exceeds fixed maximum message size\n"));
}
@Test
diff --git
a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
index 63ac70db6b..c717bd32b0 100644
---
a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
+++
b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
@@ -35,6 +35,7 @@ import
org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
import org.apache.james.protocols.netty.ChannelHandlerFactory;
import org.apache.james.protocols.netty.Encryption;
import
org.apache.james.protocols.netty.LineDelimiterBasedChannelHandlerFactory;
+import org.apache.james.protocols.smtp.SMTPErrorMessages;
import org.apache.james.protocols.smtp.SMTPProtocol;
import org.apache.james.smtpserver.ExtendedSMTPSession;
import org.apache.james.smtpserver.netty.SMTPChannelInboundHandler;
@@ -54,6 +55,11 @@ public class LMTPServer extends AbstractProtocolAsyncServer
implements LMTPServe
* 0, means no limit.
*/
private long maxMessageSize = 0;
+
+ /**
+ * The administrator supplied texts of the error responses.
+ */
+ private SMTPErrorMessages errorMessages = SMTPErrorMessages.DEFAULT;
private final LMTPConfigurationImpl lmtpConfig = new
LMTPConfigurationImpl();
private final LMTPMetricsImpl lmtpMetrics;
private final ChannelGroup lmtpChannelGroup;
@@ -88,6 +94,8 @@ public class LMTPServer extends AbstractProtocolAsyncServer
implements LMTPServe
LOGGER.info("No maximum message size is enforced for this
server.");
}
+ errorMessages = SMTPErrorMessages.parse(configuration);
+
// get the lmtpGreeting
lmtpGreeting = configuration.getString("lmtpGreeting", null);
@@ -119,6 +127,11 @@ public class LMTPServer extends
AbstractProtocolAsyncServer implements LMTPServe
return LMTPServer.this.maxMessageSize;
}
+ @Override
+ public SMTPErrorMessages errorMessages() {
+ return LMTPServer.this.errorMessages;
+ }
+
public String getSMTPGreeting() {
return LMTPServer.this.lmtpGreeting;
}
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 e509ac384c..91db6a1d87 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
@@ -53,6 +53,7 @@ import
org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
import
org.apache.james.protocols.netty.AllButStartTlsLineChannelHandlerFactory;
import org.apache.james.protocols.netty.ChannelHandlerFactory;
import org.apache.james.protocols.smtp.SMTPConfiguration;
+import org.apache.james.protocols.smtp.SMTPErrorMessages;
import org.apache.james.protocols.smtp.SMTPProtocol;
import org.apache.james.protocols.smtp.SMTPSession;
import org.apache.james.smtpserver.CoreCmdHandlerLoader;
@@ -196,6 +197,11 @@ public class SMTPServer extends
AbstractProtocolAsyncServer implements SMTPServe
*/
private long maxMessageSize = 0;
+ /**
+ * The administrator supplied texts of the SMTP error responses.
+ */
+ private SMTPErrorMessages errorMessages = SMTPErrorMessages.DEFAULT;
+
/**
* The configuration data to be passed to the handler
*/
@@ -260,6 +266,8 @@ public class SMTPServer extends AbstractProtocolAsyncServer
implements SMTPServe
LOGGER.info("No maximum message size is enforced for this
server.");
}
+ errorMessages = SMTPErrorMessages.parse(configuration);
+
heloEhloEnforcement =
configuration.getBoolean("heloEhloEnforcement", true);
// get the smtpGreeting
@@ -304,6 +312,11 @@ public class SMTPServer extends
AbstractProtocolAsyncServer implements SMTPServe
return SMTPServer.this.maxMessageSize;
}
+ @Override
+ public SMTPErrorMessages errorMessages() {
+ return SMTPServer.this.errorMessages;
+ }
+
@Override
public boolean isRelayingAllowed(String remoteIP) {
if (authorizedNetworks != null) {
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 c7b82cb41c..55f990d7d5 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
@@ -366,7 +366,39 @@ public class SMTPServerTest {
smtpProtocol.sendShortMessageData(stringBuilder.toString());
// Then
- assertThat(smtpProtocol.getReplyString()).isEqualTo("552 Quota
exceeded\r\n");
+ assertThat(smtpProtocol.getReplyString()).isEqualTo("552 5.3.4 Message
size exceeds fixed maximum message size\r\n");
+
+ // Finally
+ smtpProtocol.quit();
+ smtpProtocol.disconnect();
+ }
+
+ @Test
+ public void
messageExceedingMessageSizeShouldBeRespondedWithConfiguredMessage() throws
Exception {
+ // Given
+ smtpConfiguration.setOversizedMailMessage("Votre message est trop
volumineux");
+ init(smtpConfiguration);
+ int maxSize = 1024;
+ testSystem.smtpServer.setMaximalMessageSize(maxSize);
+
+ //When
+ SMTPClient smtpProtocol = new SMTPClient();
+ InetSocketAddress bindedAddress = testSystem.getBindedAddress();
+ smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(),
bindedAddress.getPort());
+ smtpProtocol.sendCommand("EHLO localhost");
+ smtpProtocol.setSender("mail@localhost");
+ smtpProtocol.addRecipient("mail@localhost");
+ // Create a 1K+ message
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("Subject: test\r\n\r\n");
+ String repeatedString = "This is the repeated body...\r\n";
+ int repeatCount = (maxSize / repeatedString.length()) + 1;
+ stringBuilder.append(repeatedString.repeat(repeatCount));
+ stringBuilder.append("\r\n\r\n.\r\n");
+ smtpProtocol.sendShortMessageData(stringBuilder.toString());
+
+ // Then
+ assertThat(smtpProtocol.getReplyString()).isEqualTo("552 5.3.4 Votre
message est trop volumineux\r\n");
// Finally
smtpProtocol.quit();
@@ -1650,6 +1682,23 @@ public class SMTPServerTest {
.isEqualTo(503);
}
+ @Test
+ public void
announcedMessageSizeLimitExceededShouldBeRespondedWithConfiguredMessage()
throws Exception {
+ smtpConfiguration.setMaxMessageSize(1); // set message limit to 1kb
+ smtpConfiguration.setOversizedMailMessage("Votre message est trop
volumineux");
+ init(smtpConfiguration);
+
+ SMTPClient smtpProtocol = new SMTPClient();
+ InetSocketAddress bindedAddress = testSystem.getBindedAddress();
+ smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(),
bindedAddress.getPort());
+
+ smtpProtocol.sendCommand("ehlo localhost");
+
+ smtpProtocol.sendCommand("MAIL FROM:<mail@localhost> SIZE=1025", null);
+ assertThat(smtpProtocol.getReplyString())
+ .isEqualTo("552 5.3.4 Votre message est trop volumineux\r\n");
+ }
+
@Test
public void testHandleMessageSizeLimitExceeded() throws Exception {
smtpConfiguration.setMaxMessageSize(1); // set message limit to 1kb
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..6d3e37feff 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,8 @@ import
org.apache.james.smtpserver.fastfail.ValidSenderDomainHandler;
public class SMTPTestConfiguration extends BaseHierarchicalConfiguration {
private int maxMessageSizeKB = 0;
+ private String oversizedMailMessage = null;
+ private String unknownUserMessage = null;
private String authorizedAddresses = "127.0.0.0/8";
private String authorizingMode = "false";
private boolean verifyIdentity = false;
@@ -54,6 +56,14 @@ public class SMTPTestConfiguration extends
BaseHierarchicalConfiguration {
maxMessageSizeKB = kilobytes;
}
+ public void setOversizedMailMessage(String oversizedMailMessage) {
+ this.oversizedMailMessage = oversizedMailMessage;
+ }
+
+ public void setUnknownUserMessage(String unknownUserMessage) {
+ this.unknownUserMessage = unknownUserMessage;
+ }
+
public void setAuthorizedAddresses(String authorizedAddresses) {
this.authorizedAddresses = authorizedAddresses;
}
@@ -128,6 +138,12 @@ public class SMTPTestConfiguration extends
BaseHierarchicalConfiguration {
addProperty("connectiontimeout", 360000);
addProperty("authorizedAddresses", authorizedAddresses);
addProperty("maxmessagesize", maxMessageSizeKB);
+ if (oversizedMailMessage != null) {
+ addProperty("errorMessages.oversizedMail", oversizedMailMessage);
+ }
+ if (unknownUserMessage != null) {
+ addProperty("errorMessages.unknownUser", unknownUserMessage);
+ }
addProperty("authRequired", authorizingMode);
addProperty("heloEhloEnforcement", heloEhloEnforcement);
addProperty("addressBracketsEnforcement", addressBracketsEnforcement);
diff --git
a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
index dd0cc8d8bb..d1789ee416 100644
---
a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
+++
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
@@ -34,6 +34,9 @@ import org.apache.james.core.Username;
import org.apache.james.dnsservice.api.DNSService;
import org.apache.james.domainlist.lib.DomainListConfiguration;
import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.protocols.smtp.SMTPConfiguration;
+import org.apache.james.protocols.smtp.SMTPConfigurationImpl;
+import org.apache.james.protocols.smtp.SMTPErrorMessages;
import org.apache.james.protocols.smtp.SMTPSession;
import org.apache.james.protocols.smtp.hook.HookReturnCode;
import org.apache.james.protocols.smtp.utils.BaseFakeSMTPSession;
@@ -89,12 +92,26 @@ class ValidRcptHandlerTest {
}
private SMTPSession setupMockedSMTPSession(boolean relayingAllowed) {
+ return setupMockedSMTPSession(relayingAllowed,
SMTPErrorMessages.DEFAULT);
+ }
+
+ private SMTPSession setupMockedSMTPSession(boolean relayingAllowed,
SMTPErrorMessages errorMessages) {
return new BaseFakeSMTPSession() {
@Override
public boolean isRelayingAllowed() {
return relayingAllowed;
}
+
+ @Override
+ public SMTPConfiguration getConfiguration() {
+ return new SMTPConfigurationImpl() {
+ @Override
+ public SMTPErrorMessages errorMessages() {
+ return errorMessages;
+ }
+ };
+ }
private final HashMap<AttachmentKey<?>, Object> sessionState = new
HashMap<>();
private final HashMap<AttachmentKey<?>, Object> connectionState =
new HashMap<>();
@@ -142,6 +159,23 @@ class ValidRcptHandlerTest {
assertThat(rCode).isEqualTo(HookReturnCode.deny());
}
+ @Test
+ void doRcptShouldRejectNotExistingLocalUsersWithDefaultMessage() {
+ SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+
+ assertThat(handler.doRcpt(session, MAYBE_SENDER,
invalidUserEmail).getSmtpDescription())
+ .isEqualTo("5.1.1 Unknown user: " + invalidUserEmail.asString());
+ }
+
+ @Test
+ void doRcptShouldRejectNotExistingLocalUsersWithConfiguredMessage() {
+ SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED,
+ new SMTPErrorMessages(Optional.empty(), Optional.of("The recipient
does not exist.")));
+
+ assertThat(handler.doRcpt(session, MAYBE_SENDER,
invalidUserEmail).getSmtpDescription())
+ .isEqualTo("5.1.1 The recipient does not exist. " +
invalidUserEmail.asString());
+ }
+
@Test
void doRcptShouldDenyNotExistingLocalUsersWhenRelay() {
SMTPSession session = setupMockedSMTPSession(RELAYING_ALLOWED);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]