This is an automated email from the ASF dual-hosted git repository. quantranhong1999 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-jspf.git
commit 60410f3d8f25afbaf35a7d903726438dcf0c82d1 Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Oct 28 22:11:22 2026 +0100 [FIX] Look up the address of no more than 10 MX names RFC 7208 4.6.4: there must be a limit of no more than 10 address lookups per mx mechanism. One "mx" term with a large MX answer made a single SPF check send hundreds of A/AAAA queries. Truncate the MX list to the DNS service record limit (10 by default), as PTRMechanism does: this keeps the #mx-limit rfc4408 test result. Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../org/apache/james/jspf/terms/MXMechanism.java | 19 +++++++- .../org/apache/james/jspf/SPFExecutorBaseTest.java | 54 ++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java b/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java index 1e54ad9..20cfe34 100644 --- a/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java +++ b/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java @@ -26,6 +26,8 @@ import java.util.List; import org.apache.james.jspf.core.DNSLookupContinuation; import org.apache.james.jspf.core.DNSRequest; import org.apache.james.jspf.core.DNSResponse; +import org.apache.james.jspf.core.DNSService; +import org.apache.james.jspf.core.DNSServiceEnabled; import org.apache.james.jspf.core.IPAddr; import org.apache.james.jspf.core.MacroExpand; import org.apache.james.jspf.core.SPFChecker; @@ -44,7 +46,7 @@ import org.slf4j.LoggerFactory; * This class represent the mx mechanism * */ -public class MXMechanism extends AMechanism implements SPFCheckerDNSResponseListener { +public class MXMechanism extends AMechanism implements DNSServiceEnabled, SPFCheckerDNSResponseListener { private static final Logger LOGGER = LoggerFactory.getLogger(MXMechanism.class); private final class ExpandedChecker implements SPFChecker { @@ -72,6 +74,15 @@ public class MXMechanism extends AMechanism implements SPFCheckerDNSResponseList + DUAL_CIDR_LENGTH_REGEX + ")?"; private SPFChecker expandedChecker = new ExpandedChecker(); + + private DNSService dnsService; + + /** + * @see org.apache.james.jspf.core.DNSServiceEnabled#enableDNSService(org.apache.james.jspf.core.DNSService) + */ + public void enableDNSService(DNSService service) { + this.dnsService = service; + } /** * @see org.apache.james.jspf.terms.AMechanism#checkSPF(org.apache.james.jspf.core.SPFSession) @@ -106,6 +117,12 @@ public class MXMechanism extends AMechanism implements SPFCheckerDNSResponseList spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.FALSE); return null; } + + // RFC 7208 4.6.4: no more than 10 MX names get an address lookup. + // Truncate rather than permerror, see #mx-limit rfc4408 test + if (dnsService != null && dnsService.getRecordLimit() > 0 && records.size() > dnsService.getRecordLimit()) { + records = new ArrayList<String>(records.subList(0, dnsService.getRecordLimit())); + } spfSession.setAttribute(ATTRIBUTE_CHECK_RECORDS, records); 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 8042c22..4369ee3 100644 --- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java +++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java @@ -1,5 +1,6 @@ package org.apache.james.jspf; +import org.apache.james.jspf.core.DNSRequest; import org.apache.james.jspf.core.DNSService; import org.apache.james.jspf.core.exceptions.SPFErrorConstants; import org.apache.james.jspf.executor.SPFResult; @@ -20,7 +21,12 @@ import org.xbill.DNS.TextParseException; import java.io.IOException; import java.net.UnknownHostException; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -280,4 +286,52 @@ public abstract class SPFExecutorBaseTest { ip, hostname, SPFErrorConstants.FAIL_CONV), result.getHeader()); } + + /** + * Test the limit described in RFC7208 section "4.6.4. DNS Lookup Limits" + */ + @Test + public void shouldLookUpNoMoreThan10MxNames() { + AtomicInteger addressLookups = new AtomicInteger(); + DNSService dns = new DNSService() { + public List<String> getRecords(DNSRequest request) { + switch (request.getRecordType()) { + case DNSRequest.TXT: + return request.getHostname().equals("example.com") + ? new ArrayList<>(List.of("v=spf1 mx -all")) : null; + case DNSRequest.MX: + return IntStream.range(0, 400).mapToObj(i -> "m" + i + ".example.net") + .collect(Collectors.toCollection(ArrayList::new)); + case DNSRequest.A: + addressLookups.incrementAndGet(); + return null; + default: + return null; + } + } + + public CompletionStage<List<String>> getRecordsAsync(DNSRequest request) { + return CompletableFuture.completedFuture(getRecords(request)); + } + + public List<String> getLocalDomainNames() { + return List.of("localhost"); + } + + public void setTimeOut(int timeOut) { + } + + public int getRecordLimit() { + return 10; + } + + public void setRecordLimit(int recordLimit) { + } + }; + + SPFResult result = createCustomSPF(dns).checkSPF("1.2.3.4", "[email protected]", "example.com"); + + assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult()); + assertEquals(10, addressLookups.get()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
