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]
