git » spf » commit 958dedc

mx: Continue on DNS response with invalid names

author Matthias Schneider
2022-08-16 14:01:52 UTC
committer Alberto Bertogli
2022-08-27 16:49:25 UTC
parent 0017db6243c65c80ff4626f4a436958f6d26f4b8

mx: Continue on DNS response with invalid names

On MX lookups, if there is one invalid MX record (for example, pointing
to an IP address instead of a name), Go will return an error and also
the valid records.

Today, we just fail this lookup as if we had a DNS error.

However, the SPF is not clear about this specific case, and all other
popular implementations will just ignore the invalid record and keep
processing using the valid ones.

This patch makes us match common practice.

https://github.com/albertito/spf/pull/8

Amended-by: Alberto Bertogli <albertito@blitiri.com.ar>
  Updated commit message, added code comments, added tests.

spf.go +7 -1
spf_test.go +22 -0

diff --git a/spf.go b/spf.go
index d36826a..5d5784c 100644
--- a/spf.go
+++ b/spf.go
@@ -789,7 +789,13 @@ func (r *resolution) mxField(res Result, field, domain string) (bool, Result, er
 	r.count++
 	mxs, err := r.resolver.LookupMX(r.ctx, mxDomain)
 	r.checkVoidLookup(len(mxs), err)
-	if err != nil {
+
+	// If we get some results, use them even if we get an error alongisde.
+	// This happens when one of the records is invalid, because Go library can
+	// be quite strict about it. The RFC is not clear about this specific
+	// situation, and other SPF libraries and implementations just skip the
+	// invalid value, so we match common practice.
+	if err != nil && len(mxs) == 0 {
 		// https://tools.ietf.org/html/rfc7208#section-5
 		if isNotFound(err) {
 			return false, "", err
diff --git a/spf_test.go b/spf_test.go
index 65d1a86..a23a00f 100644
--- a/spf_test.go
+++ b/spf_test.go
@@ -335,6 +335,28 @@ func TestDNSPermanentErrors(t *testing.T) {
 	}
 }
 
+func TestMXWithInvalidRecord(t *testing.T) {
+	dns := NewDefaultResolver()
+	dnsError := &net.DNSError{
+		Err:         "permanent error for testing",
+		IsTemporary: false,
+	}
+
+	// MX lookup on "dom2" will return an error and also some records.
+	// We expect the resolution to use the valid records, and ignore
+	// the error.
+	dns.Txt["domain"] = []string{"v=spf1 mx:dom2 -all"}
+	dns.Mx["dom2"] = []*net.MX{mx("oneoneoneone", 10)}
+	dns.Errors["dom2"] = dnsError
+	dns.Ip["oneoneoneone"] = []net.IP{ip1111}
+	defaultTrace = t.Logf
+
+	res, err := CheckHost(ip1111, "domain")
+	if res != Pass {
+		t.Errorf("expected pass, got %v (%v)", res, err)
+	}
+}
+
 func TestMacros(t *testing.T) {
 	dns := NewDefaultResolver()
 	defaultTrace = t.Logf