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]
