git » spf » commit c059c43

Fix "i" macro expasion of IPv6 addresses

author James Lamb
2021-07-01 07:20:20 UTC
committer Alberto Bertogli
2021-07-12 17:22:31 UTC
parent 96969802d81434e420b610b47230a683e425a8df

Fix "i" macro expasion of IPv6 addresses

The standard says that the "i" macro when used on ipv6 addresses must
expand using dot notation, which enables the result to be used in DNS
lookups.

https://datatracker.ietf.org/doc/html/rfc7208#section-7.3

Currently, the library uses the ":" notation, which is incorrect. This
patch fixes it by using the "." notation for IPv6 addresses.

Amended-by: Alberto Bertogli <albertito@blitiri.com.ar>
  Updated commit message, moved the printing logic to a separate
  function, removed the extra ".", changed int literal to go 1.11
  compatible form.

spf.go +17 -1
spf_test.go +2 -0

diff --git a/spf.go b/spf.go
index 0cf1319..9b535ed 100644
--- a/spf.go
+++ b/spf.go
@@ -831,7 +831,7 @@ func (r *resolution) expandMacros(s, domain string) (string, error) {
 			case "d":
 				str = domain
 			case "i":
-				str = r.ip.String()
+				str = ipToMacroStr(r.ip)
 			case "p":
 				// This shouldn't be used, we don't want to support it, it's
 				// risky. "unknown" is a safe value.
@@ -899,3 +899,19 @@ func reverseStrings(a []string) {
 		a[left], a[right] = a[right], a[left]
 	}
 }
+
+func ipToMacroStr(ip net.IP) string {
+	if ip.To4() != nil {
+		return ip.String()
+	}
+
+	// For IPv6 addresses, the "i" macro expands to a dot-format address.
+	// https://datatracker.ietf.org/doc/html/rfc7208#section-7.3
+	sb := strings.Builder{}
+	sb.Grow(64)
+	for _, b := range ip.To16() {
+		fmt.Fprintf(&sb, "%x.%x.", b>>4, b&0xf)
+	}
+	// Return the string without the trailing ".".
+	return sb.String()[:sb.Len()-1]
+}
diff --git a/spf_test.go b/spf_test.go
index 9d7a9ea..022a06c 100644
--- a/spf_test.go
+++ b/spf_test.go
@@ -350,12 +350,14 @@ func TestMacros(t *testing.T) {
 		{"v=spf1 +a:vvv-%{v}-vvv", Pass, errMatchedA},
 		{"v=spf1 a:%{x}", PermError, errInvalidMacro},
 		{"v=spf1 +a:ooo-%{o7}-ooo", Pass, errMatchedA},
+		{"v=spf1 exists:%{ir}.vvv -all", Pass, errMatchedExists},
 	}
 
 	dns.Ip["sss-user@domain-sss"] = []net.IP{ip6666}
 	dns.Ip["ooo-domain-ooo"] = []net.IP{ip6666}
 	dns.Ip["ppp-unknown-ppp"] = []net.IP{ip6666}
 	dns.Ip["vvv-ip6-vvv"] = []net.IP{ip6666}
+	dns.Ip["8.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.vvv"] = []net.IP{ip1111}
 
 	for _, c := range cases {
 		dns.Txt["domain"] = []string{c.txt}