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 caa3e51be9d2b1a66bbf591b932355f418da67e2
Author: Quan Tran <[email protected]>
AuthorDate: Tue Nov 3 10:02:00 2026 +0700

    [FIX] DefaultExplanationPolicy: not for an included record
    
    The policies run on the included records too, and the default
    explanation was computed for each one that failed, with its domain as
    %{d}. ExpModifier skips the explanation inside an include, this policy
    did not, and its explanation stayed for the rest of the check. With the
    default explanation "Denied %{d}" and "v=spf1 include:other.example
    -all", where other.example has "v=spf1 -all":
    - the check failed with "Denied other.example", not the checked domain;
    - with "+all" in place of "-all", it passed with that explanation.
    setUseTrustedForwarder(true) includes the trusted forwarder host in
    every record ending with -all or ?all, so a fail named that host when
    its own record failed.
    
    Skip it when the session ignores the explanation, as ExpModifier does:
    RFC 7208 6.2, during the recursion into an include an exp of the target
    MUST NOT be used, and the explanation is for a fail of the checked
    domain.
---
 .../policies/local/DefaultExplanationPolicy.java   |  5 ++++
 .../org/apache/james/jspf/SPFExecutorBaseTest.java | 35 ++++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git 
a/resolver/src/main/java/org/apache/james/jspf/policies/local/DefaultExplanationPolicy.java
 
b/resolver/src/main/java/org/apache/james/jspf/policies/local/DefaultExplanationPolicy.java
index aa73b48..73a8336 100644
--- 
a/resolver/src/main/java/org/apache/james/jspf/policies/local/DefaultExplanationPolicy.java
+++ 
b/resolver/src/main/java/org/apache/james/jspf/policies/local/DefaultExplanationPolicy.java
@@ -73,6 +73,11 @@ public final class DefaultExplanationPolicy implements 
PolicyPostFilter {
          */
         public DNSLookupContinuation checkSPF(SPFSession spfData) throws 
PermErrorException, NoneException, TempErrorException, NeutralException {
             
+            // as for exp=, not the explanation of an included record
+            if (spfData.ignoreExplanation()) {
+                return null;
+            }
+
             if (SPF1Constants.FAIL.equals(spfData.getCurrentResult())) {  
                 if (spfData.getExplanation()==null || 
spfData.getExplanation().equals("")) {
                     String explanation;
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 4e66f42..122505a 100644
--- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
@@ -51,6 +51,16 @@ public abstract class SPFExecutorBaseTest {
         return new TXTRecord(Name.fromString(hostname + "."), DClass.IN, 30L, 
strings);
     }
 
+    // hostname includes target.<hostname>, whose record fails, then ends with 
lastTerm
+    private static FakeResolver includeOfAFailingRecord(String hostname, 
String lastTerm) throws IOException {
+        String target = "target." + hostname;
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), 
DClass.IN, 30L,
+            "v=spf1 include:" + target + " " + lastTerm));
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(target + "."), 
DClass.IN, 30L, "v=spf1 -all"));
+        return fakeResolver;
+    }
+
     protected abstract SPF createSPF();
 
     protected abstract SPF createCustomSPF(DNSService dnsService);
@@ -671,4 +681,29 @@ public abstract class SPFExecutorBaseTest {
 
         assertEquals(SPFErrorConstants.PERM_ERROR_CONV, result.getResult());
     }
+
+    @Test(timeout = 10000)
+    public void 
defaultExplanationShouldNameTheCheckedDomainAfterAFailingInclude() throws 
IOException {
+        String hostname = 
"defaultExplanationAfterFailingInclude.spfexecutor.fake";
+        SPF spf = createCustomSPF(new 
DNSServiceXBillImpl(includeOfAFailingRecord(hostname, "-all")));
+        spf.setDefaultExplanation("Denied %{d}");
+
+        SPFResult result = spf.checkSPF("192.0.2.10", "a_user@" + hostname, 
hostname);
+
+        assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult());
+        assertEquals("Denied " + hostname, result.getExplanation());
+    }
+
+    @Test(timeout = 10000)
+    public void passShouldHaveNoExplanationAfterAFailingInclude() throws 
IOException {
+        String hostname = "passAfterFailingInclude.spfexecutor.fake";
+        SPF spf = createCustomSPF(new 
DNSServiceXBillImpl(includeOfAFailingRecord(hostname, "+all")));
+        spf.setDefaultExplanation("Denied %{d}");
+
+        SPFResult result = spf.checkSPF("192.0.2.10", "a_user@" + hostname, 
hostname);
+
+        assertEquals(SPFErrorConstants.PASS_CONV, result.getResult());
+        // SPFResult gives "" when there is no explanation
+        assertEquals("", result.getExplanation());
+    }
 }


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

Reply via email to