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 9fb0612f9277f13a4b9441a1bffc95aadffcb6c1 Author: Quan Tran <[email protected]> AuthorDate: Wed Nov 4 22:31:12 2026 +0700 [FIX] SPFRetriever: look up TXT when the SPF type lookup fails SPFRetriever looks up the record of a domain as the obsolete SPF type first, then as TXT when there is none. A DNS error of that first lookup, as from a server that does not handle the SPF type, made the check a temperror, without the TXT record being looked up. Since RFC 7208, the record is published as TXT only, and looked up as "type TXT only" (RFC 7208 3.1, 4.4): "v=spf1 -all" published as TXT failed no client of such a server. On a DNS error of the SPF type lookup, look up the TXT record, as when there is no SPF type record. The same with setSPFMustEqualsTXT, which compares the records of both types: there too a DNS error of the SPF type lookup gave temperror. --- .../apache/james/jspf/policies/SPFRetriever.java | 4 +- .../jspf/policies/SPFStrictCheckerRetriever.java | 5 ++- .../org/apache/james/jspf/SPFExecutorBaseTest.java | 44 ++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/resolver/src/main/java/org/apache/james/jspf/policies/SPFRetriever.java b/resolver/src/main/java/org/apache/james/jspf/policies/SPFRetriever.java index 84a130a..0c6629e 100644 --- a/resolver/src/main/java/org/apache/james/jspf/policies/SPFRetriever.java +++ b/resolver/src/main/java/org/apache/james/jspf/policies/SPFRetriever.java @@ -98,7 +98,9 @@ public class SPFRetriever implements SPFChecker { return null; } catch (TimeoutException e) { - throw new TempErrorException("Timeout querying dns"); + // RFC 7208 4.4: the record is looked up as "type TXT only": a DNS error of the obsolete SPF type is not one + String currentDomain = session.getCurrentDomain(); + return new DNSLookupContinuation(new DNSRequest(currentDomain, DNSRequest.TXT), new SPFRecordHandlerDNSResponseListener()); } } diff --git a/resolver/src/main/java/org/apache/james/jspf/policies/SPFStrictCheckerRetriever.java b/resolver/src/main/java/org/apache/james/jspf/policies/SPFStrictCheckerRetriever.java index b077911..24234b7 100644 --- a/resolver/src/main/java/org/apache/james/jspf/policies/SPFStrictCheckerRetriever.java +++ b/resolver/src/main/java/org/apache/james/jspf/policies/SPFStrictCheckerRetriever.java @@ -93,7 +93,10 @@ public class SPFStrictCheckerRetriever extends SPFRetriever { return new DNSLookupContinuation(new DNSRequest(currentDomain, DNSRequest.TXT), new SPFStrictSPFRecordsDNSResponseListener()); } catch (TimeoutException e) { - throw new TempErrorException("Timeout querying dns"); + // RFC 7208 4.4: the record is looked up as "type TXT only": a DNS error of the obsolete SPF type is not one + session.removeAttribute(ATTRIBUTE_SPFSTRICT_CHECK_SPFRECORDS); + String currentDomain = session.getCurrentDomain(); + return new DNSLookupContinuation(new DNSRequest(currentDomain, DNSRequest.TXT), new SPFStrictSPFRecordsDNSResponseListener()); } } 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 bcc20c4..bee20fc 100644 --- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java +++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java @@ -82,6 +82,29 @@ public abstract class SPFExecutorBaseTest { return fakeResolver; } + // hostname publishes "v=spf1 -all" as TXT, on a server failing the lookups of the obsolete SPF type + private static DNSService dnsFailingTheSpfType(String hostname) throws IOException { + FakeResolver fakeResolver = new FakeResolver(); + fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), DClass.IN, 30L, "v=spf1 -all")); + return new DNSServiceXBillImpl(fakeResolver) { + @Override + public List<String> getRecords(DNSRequest request) throws TimeoutException { + if (request.getRecordType() == DNSRequest.SPF) { + throw new TimeoutException("SERVFAIL"); + } + return super.getRecords(request); + } + + @Override + public CompletionStage<List<String>> getRecordsAsync(DNSRequest request) { + if (request.getRecordType() == DNSRequest.SPF) { + return CompletableFuture.failedFuture(new TimeoutException("SERVFAIL")); + } + return super.getRecordsAsync(request); + } + }; + } + protected abstract SPF createSPF(); protected abstract SPF createCustomSPF(DNSService dnsService); @@ -843,6 +866,27 @@ public abstract class SPFExecutorBaseTest { assertEquals(SPFErrorConstants.PASS_CONV, result.getResult()); } + @Test(timeout = 10000) + public void shouldCheckTheTxtRecordWhenTheLookupOfTheSpfTypeFails() throws IOException { + String hostname = "spftypeservfail.spfexecutor.fake"; + + SPFResult result = createCustomSPF(dnsFailingTheSpfType(hostname)).checkSPF("192.0.2.10", "a_user@" + hostname, hostname); + + // RFC 7208 4.4: the record is looked up as "type TXT only" + assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult()); + } + + @Test(timeout = 10000) + public void shouldCheckTheTxtRecordWhenTheLookupOfTheSpfTypeFailsInStrictMode() throws IOException { + String hostname = "spftypeservfailstrict.spfexecutor.fake"; + SPF spf = createCustomSPF(dnsFailingTheSpfType(hostname)); + spf.setSPFMustEqualsTXT(true); + + SPFResult result = spf.checkSPF("192.0.2.10", "a_user@" + hostname, hostname); + + assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult()); + } + @Test(timeout = 10000) public void shouldReturnPermErrorOnAIp4CidrOverflowingAnInt() throws IOException { SPFResult result = checkRecord("aIp4CidrOverflow.spfexecutor.fake", "v=spf1 a/4294967296 -all", "1.2.3.4"); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
