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 3a195653b2ac0ad60d28705dedd8bfbeb2fa7ba3 Author: Quan Tran <[email protected]> AuthorDate: Sat Oct 31 05:35:08 2026 +0700 [FIX] SynchronousSPFExecutor: end an include on an unexpected error The exception catchers of IncludeMechanism and RedirectModifier restore the session, then rethrow an exception that is no SPF result as is. SynchronousSPFExecutor caught SPFResultException only around them, so an unexpected lookup error under include: or redirect= escaped SPF.checkSPF instead of ending the check. DNSServiceXBillImpl can throw so: dnsjava 3.6.0 fails Lookup.run with ClassCastException on a response of opcode UPDATE holding a CNAME or DNAME of RDLENGTH 0. Hand such an exception to the next catcher, as AsynchronousSPFExecutor does: the check ends as it does for that error at the top level, neutral through the exception catcher of SPF. --- .../jspf/executor/SynchronousSPFExecutor.java | 3 +- .../org/apache/james/jspf/SPFExecutorBaseTest.java | 43 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/resolver/src/main/java/org/apache/james/jspf/executor/SynchronousSPFExecutor.java b/resolver/src/main/java/org/apache/james/jspf/executor/SynchronousSPFExecutor.java index 2ed78a0..d4dce89 100644 --- a/resolver/src/main/java/org/apache/james/jspf/executor/SynchronousSPFExecutor.java +++ b/resolver/src/main/java/org/apache/james/jspf/executor/SynchronousSPFExecutor.java @@ -73,7 +73,8 @@ public class SynchronousSPFExecutor implements SPFExecutor { try { ((SPFCheckerExceptionCatcher) checker).onException(e, session); e = null; - } catch (SPFResultException ex) { + } catch (SPFResultException | RuntimeException ex) { + // IncludeMechanism and RedirectModifier rethrow an unexpected exception: the next catcher handles it e = ex; } } 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 d1dc0a4..10992c6 100644 --- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java +++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java @@ -3,6 +3,7 @@ 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.core.exceptions.TimeoutException; import org.apache.james.jspf.executor.SPFResult; import org.apache.james.jspf.helpers.FakeResolver; import org.apache.james.jspf.impl.DNSServiceXBillImpl; @@ -69,6 +70,31 @@ public abstract class SPFExecutorBaseTest { return createCustomSPF(new DNSServiceXBillImpl(fakeResolver)).checkSPF(ipAddress, "a_user@" + hostname, hostname); } + // the lookups of broken.<hostname> fail with an unchecked exception, as dnsjava may do on an answer it cannot handle + private SPFResult checkRecordWithBrokenHost(String hostname, String record) throws IOException { + FakeResolver fakeResolver = new FakeResolver(); + fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), DClass.IN, 30L, record)); + String brokenHost = "broken." + hostname; + DNSService dns = new DNSServiceXBillImpl(fakeResolver) { + @Override + public List<String> getRecords(DNSRequest request) throws TimeoutException { + if (request.getHostname().equals(brokenHost)) { + throw new IllegalStateException("answer dnsjava cannot handle"); + } + return super.getRecords(request); + } + + @Override + public CompletionStage<List<String>> getRecordsAsync(DNSRequest request) { + if (request.getHostname().equals(brokenHost)) { + return CompletableFuture.failedFuture(new IllegalStateException("answer dnsjava cannot handle")); + } + return super.getRecordsAsync(request); + } + }; + return createCustomSPF(dns).checkSPF("192.0.2.10", "a_user@" + hostname, hostname); + } + @Test public void test() { SPF spf = createSPF(); @@ -581,4 +607,21 @@ public abstract class SPFExecutorBaseTest { assertEquals(SPFErrorConstants.PASS_CONV, result.getResult()); } + + @Test(timeout = 10000) + public void shouldEndAsNeutralOnAnUnexpectedLookupErrorInAnInclude() throws IOException { + String hostname = "unexpectedErrorInInclude.spfexecutor.fake"; + SPFResult result = checkRecordWithBrokenHost(hostname, "v=spf1 include:broken." + hostname + " -all"); + + // as at the top level, the exception catcher of SPF makes the unexpected error neutral + assertEquals(SPFErrorConstants.NEUTRAL_CONV, result.getResult()); + } + + @Test(timeout = 10000) + public void shouldEndAsNeutralOnAnUnexpectedLookupErrorInARedirect() throws IOException { + String hostname = "unexpectedErrorInRedirect.spfexecutor.fake"; + SPFResult result = checkRecordWithBrokenHost(hostname, "v=spf1 redirect=broken." + hostname); + + assertEquals(SPFErrorConstants.NEUTRAL_CONV, result.getResult()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
