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 3953dc4ecbf6563b6bd5b997756f3bdff97a22aa
Author: Quan Tran <[email protected]>
AuthorDate: Sat Oct 31 05:15:18 2026 +0700

    [FIX] mx mechanism: clean up its lookup state when nothing matches
    
    MXMechanism keeps the MX names left to look up and the addresses found
    in session attributes, shared by every mx mechanism of the check. It
    removed them once it compared the addresses, but not when no MX name had
    an address: the next mx mechanism then took its MX answer for the
    addresses of the left names, and the check ended as permerror. "v=spf1
    mx:a.example.com mx -all" did so when the MX hosts of a.example.com have
    no address, and with AsynchronousSPFExecutor when a.example.com has no
    MX record: it delivers no data as an empty list. Such an mx mechanism
    does not match (RFC 7208 5.4).
    
    Remove the attributes when nothing matches too.
---
 .../org/apache/james/jspf/terms/MXMechanism.java   |  3 +
 .../org/apache/james/jspf/SPFExecutorBaseTest.java | 74 ++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git 
a/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java 
b/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java
index 20cfe34..f79e968 100644
--- a/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java
+++ b/resolver/src/main/java/org/apache/james/jspf/terms/MXMechanism.java
@@ -153,6 +153,9 @@ public class MXMechanism extends AMechanism implements 
DNSServiceEnabled, SPFChe
                 
             // no mx record found
             if (mxR == null || mxR.size() == 0) {
+                // clean up attributes: the next mx mechanism would take its 
MX names for the addresses of these
+                spfSession.removeAttribute(ATTRIBUTE_CHECK_RECORDS);
+                spfSession.removeAttribute(ATTRIBUTE_MX_RECORDS);
                 spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, 
Boolean.FALSE);
                 return null;
             }
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 fe7710e..d1dc0a4 100644
--- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
@@ -10,16 +10,21 @@ import org.apache.james.jspf.impl.SPF;
 import org.apache.james.jspf.policies.local.TrustedForwarderPolicy;
 import org.junit.Before;
 import org.junit.Test;
+import org.xbill.DNS.ARecord;
 import org.xbill.DNS.DClass;
 import org.xbill.DNS.Lookup;
+import org.xbill.DNS.MXRecord;
 import org.xbill.DNS.Name;
 import org.xbill.DNS.Resolver;
 import org.xbill.DNS.SPFRecord;
 import org.xbill.DNS.SimpleResolver;
 import org.xbill.DNS.TXTRecord;
 import org.xbill.DNS.TextParseException;
+import org.xbill.DNS.Type;
+import org.xbill.DNS.lookup.NoSuchRRSetException;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -507,4 +512,73 @@ public abstract class SPFExecutorBaseTest {
 
         assertEquals(SPFErrorConstants.PERM_ERROR_CONV, result.getResult());
     }
+
+    @Test(timeout = 10000)
+    public void mxShouldMatchAfterAnMxWhoseHostsHaveNoAddress() throws 
IOException {
+        String hostname = "mxAfterMxWithoutAddress.spfexecutor.fake";
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), 
DClass.IN, 30L,
+            "v=spf1 mx:a." + hostname + " mx:b." + hostname + " -all"));
+        fakeResolver.addRecord(new MXRecord(Name.fromString("a." + hostname + 
"."), DClass.IN, 30L, 10,
+            Name.fromString("mail.a." + hostname + ".")));
+        fakeResolver.addRecord(new MXRecord(Name.fromString("b." + hostname + 
"."), DClass.IN, 30L, 10,
+            Name.fromString("mail.b." + hostname + ".")));
+        fakeResolver.addRecord(new ARecord(Name.fromString("mail.b." + 
hostname + "."), DClass.IN, 30L,
+            InetAddress.getByName("192.0.2.10")));
+
+        SPFResult result = createCustomSPF(new 
DNSServiceXBillImpl(fakeResolver))
+            .checkSPF("192.0.2.10", "a_user@" + hostname, hostname);
+
+        assertEquals(SPFErrorConstants.PASS_CONV, result.getResult());
+    }
+
+    @Test(timeout = 10000)
+    public void mxShouldMatchAfterAnMxOfADomainWithoutMx() {
+        DNSService dns = new DNSService() {
+            public List<String> getRecords(DNSRequest request) {
+                switch (request.getRecordType()) {
+                    case DNSRequest.TXT:
+                        return request.getHostname().equals("example.com")
+                            ? new ArrayList<>(List.of("v=spf1 
mx:nomx.example.com mx -all")) : null;
+                    case DNSRequest.MX:
+                        return request.getHostname().equals("example.com")
+                            ? new ArrayList<>(List.of("mail.example.com")) : 
null;
+                    case DNSRequest.A:
+                        return request.getHostname().equals("mail.example.com")
+                            ? new ArrayList<>(List.of("192.0.2.10")) : null;
+                    default:
+                        return null;
+                }
+            }
+
+            // as DNSServiceXBillImpl: an asynchronous lookup without data 
fails
+            public CompletionStage<List<String>> getRecordsAsync(DNSRequest 
request) {
+                List<String> records = getRecords(request);
+                if (records == null) {
+                    CompletableFuture<List<String>> noData = new 
CompletableFuture<>();
+                    noData.completeExceptionally(new 
NoSuchRRSetException(Name.root, Type.MX));
+                    return noData;
+                }
+                return CompletableFuture.completedFuture(records);
+            }
+
+            public List<String> getLocalDomainNames() {
+                return List.of("localhost");
+            }
+
+            public void setTimeOut(int timeOut) {
+            }
+
+            public int getRecordLimit() {
+                return 10;
+            }
+
+            public void setRecordLimit(int recordLimit) {
+            }
+        };
+
+        SPFResult result = createCustomSPF(dns).checkSPF("192.0.2.10", 
"[email protected]", "example.com");
+
+        assertEquals(SPFErrorConstants.PASS_CONV, result.getResult());
+    }
 }


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

Reply via email to