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]

Reply via email to