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 d3fcc7588305ede0ebf413b75442c90425a78c2b
Author: Quan Tran <[email protected]>
AuthorDate: Fri Oct 30 16:10:31 2026 +0700

    [FIX] AsynchronousSPFExecutor: do not recurse once per checker
    
    execute ran one checker, then called itself again through handleCont
    when that checker needed no DNS lookup. A record of a few thousand
    ip4: terms overflowed the stack: the check ended as neutral, or the
    StackOverflowError reached the caller.
    
    Loop over these checkers, as SynchronousSPFExecutor does, and only go
    asynchronous for a DNS lookup.
---
 .../jspf/executor/AsynchronousSPFExecutor.java     | 29 +++++++++++++---------
 .../org/apache/james/jspf/SPFExecutorBaseTest.java | 18 ++++++++++++++
 2 files changed, 35 insertions(+), 12 deletions(-)

diff --git 
a/resolver/src/main/java/org/apache/james/jspf/executor/AsynchronousSPFExecutor.java
 
b/resolver/src/main/java/org/apache/james/jspf/executor/AsynchronousSPFExecutor.java
index 00e6a11..125785d 100644
--- 
a/resolver/src/main/java/org/apache/james/jspf/executor/AsynchronousSPFExecutor.java
+++ 
b/resolver/src/main/java/org/apache/james/jspf/executor/AsynchronousSPFExecutor.java
@@ -52,19 +52,24 @@ public class AsynchronousSPFExecutor implements SPFExecutor 
{
      * @see SPFExecutor#execute(SPFSession, FutureSPFResult)
      */
     public void execute(SPFSession session, FutureSPFResult result) {
-        SPFChecker checker = session.popChecker();
-        if (checker == null) {
-            result.setSPFResult(session);
-            return;
-        }
-        // only execute checkers we added (better recursivity)
-        LOGGER.debug("Executing checker: {}", checker);
-        try {
-            DNSLookupContinuation cont = checker.checkSPF(session);
-            handleCont(session, result, cont, checker);
-        } catch (Exception e) {
-            handleErrorAndComplete(session, result, e);
+        // Loop over the checkers needing no DNS lookup: recursing once per 
checker overflows the stack
+        // on a record of a few thousand terms
+        SPFChecker checker;
+        while ((checker = session.popChecker()) != null) {
+            // only execute checkers we added (better recursivity)
+            LOGGER.debug("Executing checker: {}", checker);
+            try {
+                DNSLookupContinuation cont = checker.checkSPF(session);
+                if (cont != null) {
+                    handleCont(session, result, cont, checker);
+                    return;
+                }
+            } catch (Exception e) {
+                handleErrorAndComplete(session, result, e);
+                return;
+            }
         }
+        result.setSPFResult(session);
     }
 
     private void handleCont(SPFSession session, FutureSPFResult result, 
DNSLookupContinuation cont, SPFChecker checker) {
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 07f97a2..26f9b80 100644
--- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.net.UnknownHostException;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
@@ -347,4 +348,21 @@ public abstract class SPFExecutorBaseTest {
 
         assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult());
     }
+
+    @Test(timeout = 10000)
+    public void shouldHandleRecordWithManyTerms() throws IOException {
+        String hostname = "shouldHandleRecordWithManyTerms.spfexecutor.fake";
+        String record = "v=spf1 " + String.join(" ", Collections.nCopies(3000, 
"ip4:192.0.2.1")) + " -all";
+        // one TXT record made of 255 byte strings, as DNS carries it
+        List<String> strings = IntStream.range(0, (record.length() + 254) / 
255)
+            .mapToObj(i -> record.substring(i * 255, Math.min(record.length(), 
(i + 1) * 255)))
+            .collect(Collectors.toList());
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), 
DClass.IN, 30L, strings));
+
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(fakeResolver));
+        SPFResult result = spf.checkSPF("1.2.3.4", "a_user@" + hostname, 
hostname);
+
+        assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult());
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to