This is an automated email from the ASF dual-hosted git repository.

hqtran pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new 2582ff5eec JAMES-4132 HeaderLimitations (#2721)
2582ff5eec is described below

commit 2582ff5eec2d63fbe6ee15d3a5771d522c583f05
Author: AlaeMghirbi <amghi...@linagora.com>
AuthorDate: Mon May 19 10:59:48 2025 +0200

    JAMES-4132 HeaderLimitations (#2721)
---
 .../servers/partials/configure/smtp-hooks.adoc     |   4 +-
 .../partials/configure/smtp-limitation-hoot.adoc   |  30 +++++
 .../EnforceHeaderLimitationsMessageHook.java       | 107 ++++++++++++++++
 ...nforcedHeaderLimitationHookIntegrationTest.java | 135 +++++++++++++++++++++
 .../test/resources/smtpserver-EnforceHeader.xml    |  54 +++++++++
 5 files changed, 329 insertions(+), 1 deletion(-)

diff --git a/docs/modules/servers/partials/configure/smtp-hooks.adoc 
b/docs/modules/servers/partials/configure/smtp-hooks.adoc
index e99848a089..f7d8fa7c18 100644
--- a/docs/modules/servers/partials/configure/smtp-hooks.adoc
+++ b/docs/modules/servers/partials/configure/smtp-hooks.adoc
@@ -403,4 +403,6 @@ Example handlerchain configuration for `smtpserver.xml`:
 ....
 
 Would allow emails using `apache.org` as a MAIL FROM or from header domain if, 
and only if they contain a
-valid DKIM signature for the `apache.org` domain.
\ No newline at end of file
+valid DKIM signature for the `apache.org` domain.
+
+include::partial$EnforceHeaderLimitationsMessageHook.adoc[]
\ No newline at end of file
diff --git a/docs/modules/servers/partials/configure/smtp-limitation-hoot.adoc 
b/docs/modules/servers/partials/configure/smtp-limitation-hoot.adoc
new file mode 100644
index 0000000000..6dbd1e9768
--- /dev/null
+++ b/docs/modules/servers/partials/configure/smtp-limitation-hoot.adoc
@@ -0,0 +1,30 @@
+=== EnforceHeaderLimitationsMessageHook
+
+The `EnforceHeaderLimitationsMessageHook` is used to enforce limitations on 
the headers of incoming emails. It ensures that emails comply with configurable 
restrictions on the number of header lines and the total size of headers.
+
+To configure this hook, add it to the `<handlerchain>` section of your SMTP 
server configuration:
+
+* `maxLines`: The maximum number of header lines allowed (default: 500).
+* `maxSize`: The maximum total size of headers in kilobytes (default: 64 KB).
+
+- If the number of header lines exceeds the `maxLines` limit, the email is 
rejected with the SMTP error code `552 Too many header lines`.
+- If the total size of headers exceeds the `maxSize` limit, the email is 
rejected with the SMTP error code `552 Header size too large`.
+- If both limits are respected, the hook declines further processing, allowing 
the email to proceed.
+
+==== Example
+
+Here is an example of a SetUp using this hook:
+[source,xml]
+....
+<smtpserver enabled="true">
+    <handlerchain>
+        <handler 
class="org.apache.james.smtpserver.EnforceHeaderLimitationsMessageHook">
+            <maxLines>500</maxLines>
+            <maxSize>64</maxSize>
+        </handler>
+        <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
+    </handlerchain>
+    <gracefulShutdown>false</gracefulShutdown>
+</smtpserver>
+....
+----
\ No newline at end of file
diff --git 
a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/EnforceHeaderLimitationsMessageHook.java
 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/EnforceHeaderLimitationsMessageHook.java
new file mode 100644
index 0000000000..d57bc83e0f
--- /dev/null
+++ 
b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/EnforceHeaderLimitationsMessageHook.java
@@ -0,0 +1,107 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import java.util.Enumeration;
+
+import jakarta.mail.Header;
+
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.james.protocols.smtp.SMTPRetCode;
+import org.apache.james.protocols.smtp.SMTPSession;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.apache.james.protocols.smtp.hook.HookReturnCode;
+import org.apache.james.util.Size;
+import org.apache.mailet.Mail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class implements an SMTP hook to enforce limitations on the headers of 
incoming emails.
+ *
+ * It allows configuring and enforcing two types of restrictions:
+ * - A maximum number of header lines (default: 500).
+ * - A maximum total size of headers in bytes (default: 64 KB).
+ * If any of these thresholds are exceeded, the message is rejected with an 
SMTP error code:
+ *   <code>552 Too many header lines</code> if the number of lines exceeds the 
limit.
+ *   <code>552 Header size too large</code> if the total size exceeds the 
limit.
+ *
+ * Example XML configuration:
+ * <pre>{
+ * <handler 
class="org.apache.james.smtpserver.EnforceHeaderLimitationsMessageHook">
+ *     <maxLines>500</maxLines>
+ *     <maxSize>64KB</maxSize>
+ * </handler>
+ * }</pre>
+ *
+ */
+
+public class EnforceHeaderLimitationsMessageHook implements JamesMessageHook {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(EnforceHeaderLimitationsMessageHook.class);
+    private static final int DEFAULT_MAX_LINES = 500;
+    private static final int DEFAULT_MAX_SIZE = 1024 * 64;
+
+    private int maxLines;
+    private long maxSize;
+
+    @Override
+    public HookResult onMessage(SMTPSession session, Mail mail) {
+        try {
+            int actualLines = 0;
+            int actualSize = 0;
+            Enumeration<Header> headers = mail.getMessage().getAllHeaders();
+            while (headers.hasMoreElements()) {
+               Header header = headers.nextElement();
+               actualLines += 1;
+               actualSize += header.getName().length() + 
header.getValue().length() + 4;
+               if (actualLines > maxLines) {
+                   LOGGER.warn("Email rejected: too many header lines");
+                   return HookResult.builder()
+                       .hookReturnCode(HookReturnCode.denySoft())
+                       .smtpReturnCode(SMTPRetCode.QUOTA_EXCEEDED)
+                       .smtpDescription("Header Lines are too many")
+                       .build();
+               }
+               if (actualSize > maxSize) {
+                    LOGGER.warn("Email rejected: header size too large");
+                    return HookResult.builder()
+                        .hookReturnCode(HookReturnCode.denySoft())
+                        .smtpReturnCode(SMTPRetCode.QUOTA_EXCEEDED)
+                        .smtpDescription("Header size is too large")
+                        .build();
+               }
+           }
+           return HookResult.DECLINED;
+        } catch (Exception e) {
+            LOGGER.warn("Error while checking header size", e);
+            return HookResult.DENY;
+        }
+    }
+
+    @Override
+    public void init(Configuration config) throws ConfigurationException {
+        this.maxLines = config.getInt("maxLines") <= 0 ? DEFAULT_MAX_LINES : 
config.getInt("maxLines", DEFAULT_MAX_LINES);
+        long size = Size.parse(config.getString("maxSize")).asBytes();
+        this.maxSize = size > 0 ? size : DEFAULT_MAX_SIZE;
+    }
+}
+
diff --git 
a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/EnforcedHeaderLimitationHookIntegrationTest.java
 
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/EnforcedHeaderLimitationHookIntegrationTest.java
new file mode 100644
index 0000000000..6c406bdd9a
--- /dev/null
+++ 
b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/EnforcedHeaderLimitationHookIntegrationTest.java
@@ -0,0 +1,135 @@
+/****************************************************************
+ * 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.smtpserver;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.james.smtpserver.SMTPServerTestSystem.BOB;
+import static org.apache.james.smtpserver.SMTPServerTestSystem.PASSWORD;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Base64;
+
+import org.apache.commons.net.smtp.SMTPClient;
+import org.apache.james.server.core.configuration.Configuration;
+import org.apache.james.server.core.configuration.FileConfigurationProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class EnforcedHeaderLimitationHookIntegrationTest {
+    protected Configuration configuration;
+
+    private final SMTPServerTestSystem testSystem = new SMTPServerTestSystem();
+
+    @BeforeEach
+    void setUp() throws Exception {
+        testSystem.preSetUp();
+    }
+
+    @AfterEach
+    void tearDown() {
+        testSystem.smtpServer.destroy();
+    }
+
+    @Test
+    void shouldRejectWhenTooManyHeaderLines() throws Exception {
+        testSystem.smtpServer.configure(FileConfigurationProvider.getConfig(
+            
ClassLoader.getSystemResourceAsStream("smtpserver-EnforceHeader.xml")));
+        testSystem.smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = testSystem.getBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), 
bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bob@localhost> RET=HDRS");
+        smtpProtocol.sendCommand("RCPT TO:<rcpt@localhost>");
+
+        StringBuilder headers = new StringBuilder();
+        headers.append("From: bob@localhost\r\n");
+        headers.append("To: rcpt@localhost\r\n");
+        headers.append("HEADER1: value1\r\n");
+        headers.append("HEADER2: value2\r\n");
+        headers.append("HEADER3: value3\r\n");
+        smtpProtocol.sendShortMessageData(headers.toString() + "Subject: test 
mail\r\n\r\nTest body testSimpleMailSendWithDSN\r\n.\r\n\"");
+
+        assertThat(smtpProtocol.getReplyString())
+            .as("expected 552 error")
+            .isEqualTo("552 Header Lines are too many\r\n");
+    }
+
+    @Test
+    void shouldRejectWhenSizeTooLarge() throws Exception {
+        testSystem.smtpServer.configure(FileConfigurationProvider.getConfig(
+            
ClassLoader.getSystemResourceAsStream("smtpserver-EnforceHeader.xml")));
+        testSystem.smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = testSystem.getBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), 
bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bob@localhost> RET=HDRS");
+        smtpProtocol.sendCommand("RCPT TO:<rcpt@localhost>");
+
+        StringBuilder headers = new StringBuilder();
+        headers.append("From: bob@localhost\r\n");
+        headers.append("HEADER1: 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazertyuytgyuetgfrugreyryutgryuryfguyrtgfruyrftguyyurgryegyugfreyurgiopqsdfghjklwxfyhgyuguygftfytfytfytftfteffsrzfztzzftztdtfcvbnzertyuiosdfgh
 [...]
+        headers.append("HEADER2: 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaazertyuighgghgguygjygyugyuguyyfuftyfytfyfythfytfryefopqsdfghjklwxfyhgyuguygftfytfytfytftfteffsrzfztzzftztdtfcvbnzertyuiosdfghjkjnnzaesfrvfdt\r\n");
+
+        smtpProtocol.sendShortMessageData(headers.toString() + "Subject: test 
mail for the smtp sever to check if the hook that controls the limits of lines 
and size allowed is correctly stopping the server from excepting ecxeeded quota 
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii \r\n\r\nTest body 
testSimpleMailSendWithDSN\r\n.\r\n\"");
+
+        assertThat(smtpProtocol.getReplyString())
+            .as("expected 552 error")
+            .isEqualTo("552 Header size is too large\r\n");
+    }
+
+    @Test
+    void shouldSendMessageWhenWithinLimits() throws Exception {
+        testSystem.smtpServer.configure(FileConfigurationProvider.getConfig(
+            
ClassLoader.getSystemResourceAsStream("smtpserver-EnforceHeader.xml")));
+        testSystem.smtpServer.init();
+
+        SMTPClient smtpProtocol = new SMTPClient();
+        InetSocketAddress bindedAddress = testSystem.getBindedAddress();
+        smtpProtocol.connect(bindedAddress.getAddress().getHostAddress(), 
bindedAddress.getPort());
+        authenticate(smtpProtocol);
+
+        smtpProtocol.sendCommand("EHLO localhost");
+        smtpProtocol.sendCommand("MAIL FROM: <bob@localhost> RET=HDRS");
+        smtpProtocol.sendCommand("RCPT TO:<rcpt@localhost>");
+        smtpProtocol.sendShortMessageData("From: bob@localhost\r\nFrom: 
bob@localhost\r\nto: aziz@localhost\r\n\r\nTest body 
testSimpleMailSendWithDSN\r\n.\r\n");
+
+        assertThat(smtpProtocol.getReplyCode())
+            .as("expected message to be sent")
+            .isEqualTo(250);
+    }
+
+    private void authenticate(SMTPClient smtpProtocol) throws IOException {
+        smtpProtocol.sendCommand("AUTH PLAIN");
+        smtpProtocol.sendCommand(Base64.getEncoder().encodeToString(("\0" + 
BOB.asString() + "\0" + PASSWORD + "\0").getBytes(UTF_8)));
+        assertThat(smtpProtocol.getReplyCode())
+            .as("authenticated")
+            .isEqualTo(235);
+    }
+}
diff --git 
a/server/protocols/protocols-smtp/src/test/resources/smtpserver-EnforceHeader.xml
 
b/server/protocols/protocols-smtp/src/test/resources/smtpserver-EnforceHeader.xml
new file mode 100644
index 0000000000..f81c6f7e63
--- /dev/null
+++ 
b/server/protocols/protocols-smtp/src/test/resources/smtpserver-EnforceHeader.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+
+<!--
+  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.
+ -->
+
+<!-- Read 
https://james.apache.org/server/config-smtp-lmtp.html#SMTP_Configuration for 
further details -->
+
+<smtpserver enabled="true">
+    <bind>0.0.0.0:0</bind>
+    <connectionBacklog>200</connectionBacklog>
+    <tls socketTLS="false" startTLS="false">
+        <keystore>file://conf/keystore</keystore>
+        <secret>james72laBalle</secret>
+        <provider>org.bouncycastle.jce.provider.BouncyCastleProvider</provider>
+        <algorithm>SunX509</algorithm>
+    </tls>
+    <connectiontimeout>360</connectiontimeout>
+    <connectionLimit>0</connectionLimit>
+    <connectionLimitPerIP>0</connectionLimitPerIP>
+    <auth>
+        <announce>forUnauthorizedAddresses</announce>
+        <requireSSL>false</requireSSL>
+    </auth>
+    <verifyIdentity>true</verifyIdentity>
+    <maxmessagesize>0</maxmessagesize>
+    <addressBracketsEnforcement>true</addressBracketsEnforcement>
+    <smtpGreeting>Apache JAMES awesome SMTP Server</smtpGreeting>
+    <handlerchain>
+        <handler 
class="org.apache.james.smtpserver.EnforceHeaderLimitationsMessageHook">
+            <maxLines>5</maxLines>
+            <maxSize>1K</maxSize>
+        </handler>
+        <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
+    </handlerchain>
+    <gracefulShutdown>false</gracefulShutdown>
+</smtpserver>
+
+


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org
For additional commands, e-mail: notifications-h...@james.apache.org

Reply via email to