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

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

commit 384bf512ff4a30ce2ac998152341c42cf723656d
Author: Quan Tran <[email protected]>
AuthorDate: Tue Nov 3 10:48:01 2026 +0700

    [FIX] MacroExpand: truncate an expanded domain to 253 characters
    
    An expanded domain was truncated only above 255 characters, while a
    domain name has 253 at most. A name of 254 or 255 characters was kept
    and could not be queried: an include: of "a." followed by a domain of
    252 characters that has "v=spf1 +all" gave permerror instead of pass.
    
    Truncate above 253 characters, as RFC 7208 7.3 says (and RFC 4408 8.1
    before it), not counting a trailing dot: the limit is for the name "in
    this format", and "<253 characters>." is the same name.
---
 .../org/apache/james/jspf/core/MacroExpand.java    |  9 +++---
 .../org/apache/james/jspf/SPFExecutorBaseTest.java | 34 +++++++++++++++++++++-
 .../apache/james/jspf/core/MacroExpandTest.java    |  9 +++---
 3 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java 
b/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java
index 61edd25..5d1b064 100644
--- a/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java
+++ b/resolver/src/main/java/org/apache/james/jspf/core/MacroExpand.java
@@ -241,10 +241,11 @@ public class MacroExpand {
         if (domainName.indexOf('\r') >= 0 || domainName.indexOf('\n') >= 0) {
             throw new PermErrorException("Macro-expanded domain contains 
illegal CR or LF characters");
         }
-        // reduce to less than 255 characters, deleting subdomains from left: 
at once, after the first
-        // dot that leaves at most 255 characters, or else after the last dot
-        if (domainName.length() > 255) {
-            int split = domainName.indexOf('.', domainName.length() - 256);
+        // reduce to 253 characters at most, not counting a trailing dot, 
deleting subdomains from left
+        // (RFC 7208 7.3): at once, after the first dot that leaves at most 
that many, or else after the last dot
+        int maxLength = domainName.endsWith(".") ? 254 : 253;
+        if (domainName.length() > maxLength) {
+            int split = domainName.indexOf('.', domainName.length() - 
maxLength - 1);
             if (split < 0) {
                 split = domainName.lastIndexOf('.');
             }
diff --git 
a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java 
b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
index 907ce1b..13bd9e4 100644
--- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
@@ -431,7 +431,7 @@ public abstract class SPFExecutorBaseTest {
     public void shouldHandleRecordWithLongDomainSpec() throws IOException {
         String hostname = 
"shouldHandleRecordWithLongDomainSpec.spfexecutor.fake";
         FakeResolver fakeResolver = new FakeResolver();
-        // the include target, truncated to 255 characters from the left, is a 
subdomain of hostname,
+        // the include target, truncated to 253 characters from the left, is a 
subdomain of hostname,
         // with no record: FakeResolver answers it without querying the real 
DNS
         fakeResolver.addRecord(longTxtRecord(hostname, "v=spf1 include:" + 
"%{d}.".repeat(5000) + hostname + " -all"));
 
@@ -720,4 +720,36 @@ public abstract class SPFExecutorBaseTest {
 
         assertEquals(SPFErrorConstants.PERM_ERROR_CONV, result.getResult());
     }
+
+    @Test(timeout = 10000)
+    public void shouldTruncateAnIncludeTargetOf254CharactersTo253() throws 
IOException {
+        String hostname = "includeTargetOf254Characters.spfexecutor.fake";
+        // 252 characters, the include target without its first label, under 
hostname: FakeResolver answers it
+        String target = "bb." + "b.".repeat(102) + hostname;
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(longTxtRecord(hostname, "v=spf1 include:a." + 
target + " -all"));
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(target + "."), 
DClass.IN, 30L, "v=spf1 +all"));
+
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(fakeResolver));
+        SPFResult result = spf.checkSPF("1.2.3.4", "a_user@" + hostname, 
hostname);
+
+        // RFC 7208 7.3: the left side is truncated to 253 characters, the 
longest domain name
+        assertEquals(SPFErrorConstants.PASS_CONV, result.getResult());
+    }
+
+    @Test(timeout = 10000)
+    public void shouldKeepAnIncludeTargetOf253CharactersAndATrailingDot() 
throws IOException {
+        String hostname = "includeTargetWithTrailingDot.spfexecutor.fake";
+        // 253 characters, the longest domain name, under hostname: 
FakeResolver answers it, cut or not
+        String target = "ccc." + "b.".repeat(102) + hostname;
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(longTxtRecord(hostname, "v=spf1 include:" + 
target + ". -all"));
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(target + "."), 
DClass.IN, 30L, "v=spf1 +all"));
+
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(fakeResolver));
+        SPFResult result = spf.checkSPF("1.2.3.4", "a_user@" + hostname, 
hostname);
+
+        // RFC 7208 7.3: 253 characters "in this format", without the trailing 
dot of a fully qualified name
+        assertEquals(SPFErrorConstants.PASS_CONV, result.getResult());
+    }
 }
diff --git 
a/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java 
b/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java
index 753354f..dd6d58c 100644
--- a/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/core/MacroExpandTest.java
@@ -161,14 +161,15 @@ public class MacroExpandTest extends TestCase {
     public void testLongDomainSpecIsTruncatedWithoutOverflowingTheStack() 
throws PermErrorException {
         String domain = defIp4me.expand("%{d}.".repeat(5000) + "com", new 
rfcIP4MacroData(), MacroExpand.DOMAIN);
 
-        assertTrue(domain.length() <= 255);
+        assertTrue(domain.length() <= 253);
         assertTrue(domain.endsWith(".email.example.com.com"));
     }
 
-    public void 
testLongDomainIsCutAfterTheFirstDotThatLeavesAtMost255Characters() throws 
PermErrorException {
+    public void 
testLongDomainIsCutAfterTheFirstDotThatLeavesAtMost253Characters() throws 
PermErrorException {
         String domain = defIp4me.expand("a.".repeat(200) + "com", new 
rfcIP4MacroData(), MacroExpand.DOMAIN);
 
-        assertEquals("a.".repeat(126) + "com", domain);
+        // RFC 7208 7.3
+        assertEquals("a.".repeat(125) + "com", domain);
     }
 
     public void testLongLastLabelOfALongDomainIsKeptWhole() throws 
PermErrorException {
@@ -180,7 +181,7 @@ public class MacroExpandTest extends TestCase {
     public void 
testLongLiteralDomainSpecIsTruncatedWithoutOverflowingTheStack() throws 
PermErrorException {
         String domain = defIp4me.expand("a.".repeat(5000) + "com", new 
rfcIP4MacroData(), MacroExpand.DOMAIN);
 
-        assertTrue(domain.length() <= 255);
+        assertTrue(domain.length() <= 253);
         assertTrue(domain.endsWith(".a.com"));
     }
 


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

Reply via email to