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

btellier 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 a706c662ef [FIX] EhloCommandHandler should handle domain label 
starting by numbers (#3006)
a706c662ef is described below

commit a706c662ef465fd042d43ad806e8c7664a94a1d8
Author: Benoit TELLIER <[email protected]>
AuthorDate: Mon Apr 13 07:27:52 2026 +0200

    [FIX] EhloCommandHandler should handle domain label starting by numbers 
(#3006)
---
 .../protocols/smtp/core/esmtp/EhloCmdHandler.java  | 34 +++++++++-
 .../smtp/core/esmtp/EhloCmdHandlerTest.java        | 74 ++++++++++++++++++++++
 2 files changed, 106 insertions(+), 2 deletions(-)

diff --git 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/EhloCmdHandler.java
 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/EhloCmdHandler.java
index dfe322b447..8dca4f313f 100644
--- 
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/EhloCmdHandler.java
+++ 
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/esmtp/EhloCmdHandler.java
@@ -40,6 +40,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -63,6 +64,10 @@ public class EhloCmdHandler extends 
AbstractHookableCmdHandler<HeloHook> impleme
     private static final CharMatcher ALPHANUMERIC_MATCHER = 
CharMatcher.inRange('a', 'z')
         .or(CharMatcher.inRange('A', 'Z'))
         .or(CharMatcher.inRange('0', '9'));
+    private static final CharMatcher LABEL_CHAR_MATCHER = 
ALPHANUMERIC_MATCHER.or(CharMatcher.is('-'));
+    private static final Splitter LABEL_SPLITTER = Splitter.on('.');
+    private static final int MAX_HOSTNAME_LENGTH = 253;
+    private static final int MAX_LABEL_LENGTH = 63;
 
     private List<EhloExtension> ehloExtensions;
 
@@ -102,16 +107,41 @@ public class EhloCmdHandler extends 
AbstractHookableCmdHandler<HeloHook> impleme
         return resp;
     }
 
-    private boolean isValid(String argument) {
+    boolean isValid(String argument) {
         String hostname = unquote(argument);
 
         // Without [] Guava attempt to parse IPV4
         return InetAddresses.isUriInetAddress(hostname)
             // Guava tries parsing IPv6 if and only if wrapped by []
             || InetAddresses.isUriInetAddress("[" + 
removeEmIPV6Prefix(hostname) + "]")
+            // Guava's InternetDomainName requires labels to start with a 
letter (RFC 1034),
+            // but RFC 1123 / RFC 5321 allow labels to start with a digit 
(e.g. "2s1n").
+            // We keep Guava as a fast path and fall back to our own RFC 5321 
validator.
             || InternetDomainName.isValid(hostname)
             || emClientCompatibility(hostname)
-            || isAlphanumeric(hostname);
+            || isAlphanumeric(hostname)
+            || isRfc5321Hostname(hostname);
+    }
+
+    // RFC 5321 §4.1.2 / RFC 1123 §2.1: labels may start with a letter or 
digit,
+    // contain letters/digits/hyphens, and must not start or end with a hyphen.
+    // Validated character-by-character to avoid any regex backtracking 
(ReDoS).
+    private boolean isRfc5321Hostname(String hostname) {
+        if (hostname.isEmpty() || hostname.length() > MAX_HOSTNAME_LENGTH) {
+            return false;
+        }
+        return LABEL_SPLITTER.splitToStream(hostname)
+            .allMatch(this::isValidRfc1123Label);
+    }
+
+    private boolean isValidRfc1123Label(String label) {
+        if (label.isEmpty() || label.length() > MAX_LABEL_LENGTH) {
+            return false;
+        }
+        if (label.charAt(0) == '-' || label.charAt(label.length() - 1) == '-') 
{
+            return false;
+        }
+        return LABEL_CHAR_MATCHER.matchesAllOf(label);
     }
 
     // CF JAMES-4046 
https://issues.apache.org/jira/projects/JAMES/issues/JAMES-4066
diff --git 
a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/EhloCmdHandlerTest.java
 
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/EhloCmdHandlerTest.java
new file mode 100644
index 0000000000..11752db707
--- /dev/null
+++ 
b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/core/esmtp/EhloCmdHandlerTest.java
@@ -0,0 +1,74 @@
+/****************************************************************
+ * 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.core.esmtp;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class EhloCmdHandlerTest {
+
+    private final EhloCmdHandler handler = new EhloCmdHandler(new 
RecordingMetricFactory());
+
+    @ParameterizedTest
+    @ValueSource(strings = {
+        // Standard domain names
+        "example.com",
+        "mail.example.com",
+        "sub.domain.example.org",
+        // RFC 1123: labels may start with a digit
+        "mx-ll-110-164-x-x.2s1n",
+        "1mail.example.com",
+        "123.example.com",
+        // Pure alphanumeric (single label, no dot)
+        "localhost",
+        "mailserver",
+        // IPv4
+        "192.168.1.1",
+        "10.0.0.1",
+        // IPv6 with brackets
+        "[::1]",
+        "[2001:db8::1]",
+    })
+    void isValidShouldAccept(String argument) {
+        assertThat(handler.isValid(argument)).isTrue();
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {
+        // Empty or blank
+        "",
+        // Labels starting or ending with hyphen
+        "-example.com",
+        "example-.com",
+        "example.-com",
+        "example.com-",
+        // Empty label (double dot)
+        "example..com",
+        // Invalid characters
+        "exam ple.com",
+        "example.com!",
+    })
+    void isValidShouldReject(String argument) {
+        assertThat(handler.isValid(argument)).isFalse();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to