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]

Reply via email to