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 c2f6376772947ab4b3cc003451c73b5e487b94e4
Author: Quan Tran <[email protected]>
AuthorDate: Tue Nov 3 14:27:09 2026 +0700

    [FIX] IPAddr.getProperIpAddress: read every form of an IPv4-mapped address
    
    SPFSession turns an IPv4-mapped client address into the IPv4 address it
    maps, as an IPv4-mapped address "MUST still be considered an IPv4
    address" (RFC 4408 5; RFC 7208 5: implementations "need to handle both
    "AAAA" and "A" records, for clients on IPv4-mapped IPv6 addresses"). It
    did so only for "::FFFF:" followed by four octets from 1 to 999. A
    client written ::ffff:10.0.0.1 (an octet 0), ::ffff:c000:201 (the last
    32 bits in hex) or 0:0:0:0:0:ffff:192.0.2.1 (no "::") stayed an IPv6
    client: a and mx looked up AAAA records, so "v=spf1 a -all" failed the
    IPv4 client its A record lists, and %{i} expanded to nibbles.
    
    Read the address into its bytes instead, as IPAddr does for a network:
    any address of ::ffff:0:0/96 (RFC 4291 2.5.5.2), whatever its form, is
    the IPv4 address of its last 32 bits.
    
    A zoned address (fe80::1%1, RFC 4007) stays as given, as before: it is
    not global, so not IPv4-mapped.
---
 .../java/org/apache/james/jspf/core/IPAddr.java    | 23 +++++---------
 .../org/apache/james/jspf/SPFExecutorBaseTest.java | 15 ++++++++++
 .../org/apache/james/jspf/core/SPFSessionTest.java | 35 ++++++++++++++++++++++
 3 files changed, 57 insertions(+), 16 deletions(-)

diff --git a/resolver/src/main/java/org/apache/james/jspf/core/IPAddr.java 
b/resolver/src/main/java/org/apache/james/jspf/core/IPAddr.java
index 9f8211f..83ec242 100644
--- a/resolver/src/main/java/org/apache/james/jspf/core/IPAddr.java
+++ b/resolver/src/main/java/org/apache/james/jspf/core/IPAddr.java
@@ -43,8 +43,6 @@ public class IPAddr {
     private int ipRun = 4;
 
     private String ipJoiner = ".";
-    
-    private static String ipv4MappedRegex = 
"::FFFF:[1-9][0-9]{0,2}\\.[1-9][0-9]{0,2}\\.[1-9][0-9]{0,2}\\.[1-9][0-9]{0,2}";
 
     // Allow factory creates only
     private IPAddr() {
@@ -494,24 +492,17 @@ public class IPAddr {
      * @throws PermErrorException if the given ipAddress is invalid
      */
     public static String getProperIpAddress(String ip) throws 
PermErrorException {
-        if (isIPV6(ip) && isIPV4MappedIP(ip)) {
-            try {
-                return getConvertedIP(ip);
-            } catch (UnknownHostException e) {
-                throw new PermErrorException("Invalid ipAddress: " + ip);
+        // a zoned address (fe80::1%1, RFC 4007) is not global, so not 
IPv4-mapped: kept as given
+        if (isIPV6(ip) && ip.indexOf('%') < 0) {
+            // read as an IPv4 address when in ::ffff:0:0/96, whatever its 
form: with a 0 octet, its
+            // last 32 bits in hex (::ffff:c000:201), or without "::" 
(0:0:0:0:0:ffff:192.0.2.1)
+            IPAddr address = getAddress(ip);
+            if (address.ipLength == 4) {
+                return address.getIPAddress();
             }
         }
         return ip;
         
     }
-    
-    /**
-     * Return true if the given ipAddress is a ipv4-mapped-address
-     * @param ip
-     * @return
-     */
-    private static boolean isIPV4MappedIP(String ip) {
-        return ip.toUpperCase().matches(ipv4MappedRegex);
-    }
 
 }
\ No newline at end of file
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 13bd9e4..b972144 100644
--- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java
@@ -544,6 +544,21 @@ public abstract class SPFExecutorBaseTest {
         assertEquals(SPFErrorConstants.PASS_CONV, result.getResult());
     }
 
+    @Test(timeout = 10000)
+    public void aShouldMatchAnIpv4MappedClientWithAZeroOctet() throws 
IOException {
+        String hostname = "ipv4MappedClient.spfexecutor.fake";
+        FakeResolver fakeResolver = new FakeResolver();
+        fakeResolver.addRecord(new TXTRecord(Name.fromString(hostname + "."), 
DClass.IN, 30L, "v=spf1 a -all"));
+        fakeResolver.addRecord(new ARecord(Name.fromString(hostname + "."), 
DClass.IN, 30L,
+            InetAddress.getByName("10.0.0.1")));
+
+        SPF spf = createCustomSPF(new DNSServiceXBillImpl(fakeResolver));
+        SPFResult result = spf.checkSPF("::ffff:10.0.0.1", "a_user@" + 
hostname, hostname);
+
+        // RFC 7208 5: an IPv4-mapped address is an IPv4 address
+        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");
diff --git 
a/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java 
b/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java
index d4a697b..bf28c09 100644
--- a/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java
+++ b/resolver/src/test/java/org/apache/james/jspf/core/SPFSessionTest.java
@@ -57,6 +57,41 @@ public class SPFSessionTest {
         assertEquals("ip6", spfSession.getInAddress());
     }
 
+    @Test
+    public void everyFormOfAnIpv4MappedAddressShouldBeAnIpv4Client() {
+        String[][] forms = {
+            {"::ffff:10.0.0.1", "10.0.0.1"},
+            {"::FFFF:192.0.2.200", "192.0.2.200"},
+            {"0:0:0:0:0:ffff:1.2.3.4", "1.2.3.4"},
+            {"::ffff:102:304", "1.2.3.4"},
+            {"::ffff:c000:2c8", "192.0.2.200"}};
+
+        for (String[] form : forms) {
+            SPFSession spfSession = new SPFSession("", "", form[0]);
+
+            assertEquals(form[0], form[1], spfSession.getIpAddress());
+            assertEquals(form[0], form[1], spfSession.getMacroIpAddress());
+        }
+    }
+
+    @Test
+    public void anIpv6AddressOutOfTheIpv4MappedRangeShouldStayIpv6() {
+        for (String address : new String[] {"2001:db8::1", "::1.2.3.4", 
"::fffe:1.2.3.4", "1::ffff:1.2.3.4"}) {
+            SPFSession spfSession = new SPFSession("", "", address);
+
+            assertEquals(address, spfSession.getIpAddress());
+        }
+    }
+
+    @Test
+    public void aZonedIpv6AddressShouldStayAsGiven() {
+        for (String address : new String[] {"fe80::1%1", 
"2001:db8::1.2.3.4%1", "::ffff:102:304%1"}) {
+            SPFSession spfSession = new SPFSession("", "", address);
+
+            assertEquals(address, spfSession.getIpAddress());
+        }
+    }
+
     @Test
     public void shouldReturnIPv4() {
         SPFSession spfSession = new SPFSession("", "", "192.168.1.1");


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

Reply via email to