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 b83aa8e62e03e3e770f3263f0220b61fd96c8402
Author: Quan Tran <[email protected]>
AuthorDate: Wed Nov 4 21:45:10 2026 +0700

    [FIX] InitialChecksPolicy: none for a malformed sender domain
    
    The sender domain was only checked to hold a dot, labels of at most 63
    characters, and to be read by dnsjava as a DNS name in text form,
    whose escapes make it another name: MAIL FROM a_user@escape\d.example.com
    was checked as escaped.example.com, and got its result. A sender
    domain comes from the SMTP session, where it is a host name of letters,
    digits and hyphens (RFC 5321 4.1.2), an internationalized one written
    as A-labels (RFC 7208 4.3), so such a domain is malformed, and the
    result is none (RFC 7208 4.3).
    
    Return none for a sender domain holding a character other than a
    letter, a digit, "-", "." or "_", seen in host names, also when a local
    policy gave the record: an override entry for escaped.example.com, read
    as a DNS name too, matched escape\d.example.com. usage.adoc says that
    the domains are given as host names. A test of the local policies sent
    such a MAIL FROM: its result is now none, still not the pass of the
    override.
---
 docs/modules/ROOT/pages/usage.adoc                 |  4 +++
 .../james/jspf/policies/InitialChecksPolicy.java   | 15 ++++++++
 .../org/apache/james/jspf/SPFExecutorBaseTest.java | 41 +++++++++++++++++++++-
 3 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/docs/modules/ROOT/pages/usage.adoc 
b/docs/modules/ROOT/pages/usage.adoc
index 013d21a..332f9f7 100644
--- a/docs/modules/ROOT/pages/usage.adoc
+++ b/docs/modules/ROOT/pages/usage.adoc
@@ -48,6 +48,10 @@ System.out.println(result.getResult());
 <2> The envelope sender, as provided in `MAIL FROM`.
 <3> The host name provided in `HELO` / `EHLO`.
 
+Give the domains as host names: an internationalized domain in A-labels 
(`xn--`), as RFC 7208
+4.3 requires. A sender domain holding another character (a U-label, a space, a 
backslash) is
+malformed, and the result is `none`.
+
 `SPF` instances are meant to be created once and reused for many checks.
 
 == Interpreting the result
diff --git 
a/resolver/src/main/java/org/apache/james/jspf/policies/InitialChecksPolicy.java
 
b/resolver/src/main/java/org/apache/james/jspf/policies/InitialChecksPolicy.java
index 11424b2..19daf53 100644
--- 
a/resolver/src/main/java/org/apache/james/jspf/policies/InitialChecksPolicy.java
+++ 
b/resolver/src/main/java/org/apache/james/jspf/policies/InitialChecksPolicy.java
@@ -37,6 +37,15 @@ import org.xbill.DNS.TextParseException;
  * so it should be added as the last filter.
  */
 public final class InitialChecksPolicy implements SPFChecker {
+    /**
+     * @param domain a domain of the SMTP session
+     * @return true if it holds only the characters of a host name: letters, 
digits, "-" and ".",
+     *         and "_", seen in names; an internationalized name is given as 
A-labels (RFC 7208 4.3)
+     */
+    private static boolean holdsOnlyHostNameCharacters(String domain) {
+        return domain.chars().allMatch(c -> (c >= 'a' && c <= 'z') || (c >= 
'A' && c <= 'Z')
+            || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_');
+    }
     
     /**
      * @see 
org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
@@ -44,6 +53,12 @@ public final class InitialChecksPolicy implements SPFChecker 
{
     public DNSLookupContinuation checkSPF(SPFSession spfData)
             throws PermErrorException, TempErrorException, NeutralException,
             NoneException {
+        // not read as a DNS name, whose escapes would make it another domain: 
"escape\d" is no "escaped";
+        // checked also when a local policy gave the record, as it matches the 
domain read so
+        if (!holdsOnlyHostNameCharacters(spfData.getSenderDomain())) {
+            throw new NoneException("Sender domain " + 
spfData.getSenderDomain() + " is malformed.");
+        }
+
         SPF1Record res = (SPF1Record) 
spfData.getAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD);
         if (res == 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 c5f050f..bdec8fb 100644
--- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
@@ -661,7 +661,8 @@ public abstract class SPFExecutorBaseTest {
 
         SPFResult result = spf.checkSPF("192.0.2.10", "a_user@" + hostname, 
hostname);
 
-        assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult());
+        // not the pass of the override: the sender domain, holding a 
backslash, is malformed (RFC 7208 4.3)
+        assertEquals(SPFErrorConstants.NONE_CONV, result.getResult());
     }
 
     @Test(timeout = 10000)
@@ -769,6 +770,44 @@ public abstract class SPFExecutorBaseTest {
         assertEquals(SPFErrorConstants.FAIL_CONV, result.getResult());
     }
 
+    @Test(timeout = 10000)
+    public void 
shouldReturnNoneForASenderDomainWithACharacterNoHostNameHolds() throws 
IOException {
+        String hostname = "escaped.spfexecutor.fake";
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), 
DClass.IN, 30L, "v=spf1 +all"));
+
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(fakeResolver));
+        // read as a DNS name, escape\d is escaped
+        SPFResult result = spf.checkSPF("192.0.2.10", 
"a_user@escape\\d.spfexecutor.fake", "mail.spfexecutor.fake");
+
+        // RFC 7208 4.3: a malformed domain gives none
+        assertEquals(SPFErrorConstants.NONE_CONV, result.getResult());
+    }
+
+    @Test(timeout = 10000)
+    public void anOverrideShouldNotApplyToAMalformedSenderDomain() throws 
IOException {
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(new 
FakeResolver(AnsweringResolver.withRcode(Rcode.NXDOMAIN))));
+        spf.getOverridePolicy().addEntry("escaped.spfexecutor.fake", "v=spf1 
+all");
+
+        // read as a DNS name, escape\d is escaped: the domain of the override 
entry
+        SPFResult result = spf.checkSPF("192.0.2.10", 
"a_user@escape\\d.spfexecutor.fake", "mail.spfexecutor.fake");
+
+        // RFC 7208 4.3: a malformed domain gives none
+        assertEquals(SPFErrorConstants.NONE_CONV, result.getResult());
+    }
+
+    @Test(timeout = 10000)
+    public void shouldCheckASenderDomainWithAnUnderscore() throws IOException {
+        String hostname = "_underscore.spfexecutor.fake";
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), 
DClass.IN, 30L, "v=spf1 +all"));
+
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(fakeResolver));
+        SPFResult result = spf.checkSPF("192.0.2.10", "a_user@" + hostname, 
hostname);
+
+        assertEquals(SPFErrorConstants.PASS_CONV, result.getResult());
+    }
+
     @Test(timeout = 10000)
     public void shouldReturnPermErrorOnAIp4CidrOverflowingAnInt() throws 
IOException {
         SPFResult result = checkRecord("aIp4CidrOverflow.spfexecutor.fake", 
"v=spf1 a/4294967296 -all", "1.2.3.4");


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

Reply via email to