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]
