git » spf » commit e2d699f

Export error definitions

author Alberto Bertogli
2021-09-03 10:09:54 UTC
committer Alberto Bertogli
2021-10-10 10:28:40 UTC
parent 805c97b5e162fc3b59cf6eb4bd639c1cf0ed22a1

Export error definitions

This patch exports the error definitions, to enable users to
programmaticaly distinguish between the different errors, and handle
them accordingly.

This, however, has to be done with care, as errors can evolve in the
future (new ones added, reasons changed, etc.).

That said, current ones have been stable for a while, so not a lot of
churn is expected.

In the future if there are enough use cases, we may introduce some
additional types and wrapping to allow users to group them. But for now,
that complexity is not justified, and would introduce API limitations
that would be harder to undo later.

spf.go +60 -51
spf_test.go +78 -78

diff --git a/spf.go b/spf.go
index fb7cd83..23aee29 100644
--- a/spf.go
+++ b/spf.go
@@ -28,6 +28,7 @@ package spf // import "blitiri.com.ar/go/spf"
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net"
 	"net/url"
@@ -78,23 +79,31 @@ var qualToResult = map[byte]Result{
 	'?': Neutral,
 }
 
+// Errors returned by the library. Note that the errors returned in different
+// situations may change over time, and new ones may be added. Be careful
+// about over-relying on these.
 var (
-	errLookupLimitReached = fmt.Errorf("lookup limit reached")
-	errUnknownField       = fmt.Errorf("unknown field")
-	errInvalidIP          = fmt.Errorf("invalid ipX value")
-	errInvalidMask        = fmt.Errorf("invalid mask")
-	errInvalidMacro       = fmt.Errorf("invalid macro")
-	errInvalidDomain      = fmt.Errorf("invalid domain")
-	errNoResult           = fmt.Errorf("no DNS record found")
-	errMultipleRecords    = fmt.Errorf("multiple matching DNS records")
-	errTooManyMXRecords   = fmt.Errorf("too many MX records")
-
-	errMatchedAll    = fmt.Errorf("matched 'all'")
-	errMatchedA      = fmt.Errorf("matched 'a'")
-	errMatchedIP     = fmt.Errorf("matched 'ip'")
-	errMatchedMX     = fmt.Errorf("matched 'mx'")
-	errMatchedPTR    = fmt.Errorf("matched 'ptr'")
-	errMatchedExists = fmt.Errorf("matched 'exists'")
+	// Errors related to an invalid SPF record.
+	ErrUnknownField  = errors.New("unknown field")
+	ErrInvalidIP     = errors.New("invalid ipX value")
+	ErrInvalidMask   = errors.New("invalid mask")
+	ErrInvalidMacro  = errors.New("invalid macro")
+	ErrInvalidDomain = errors.New("invalid domain")
+
+	// Errors related to DNS lookups.
+	// Note that the library functions may also return net.DNSError.
+	ErrNoResult           = errors.New("no DNS record found")
+	ErrLookupLimitReached = errors.New("lookup limit reached")
+	ErrTooManyMXRecords   = errors.New("too many MX records")
+	ErrMultipleRecords    = errors.New("multiple matching DNS records")
+
+	// Errors returned on a successful match.
+	ErrMatchedAll    = errors.New("matched all")
+	ErrMatchedA      = errors.New("matched a")
+	ErrMatchedIP     = errors.New("matched ip")
+	ErrMatchedMX     = errors.New("matched mx")
+	ErrMatchedPTR    = errors.New("matched ptr")
+	ErrMatchedExists = errors.New("matched exists")
 )
 
 // Default value for the maximum number of DNS lookups while resolving SPF.
@@ -276,7 +285,7 @@ func (r *resolution) Check(domain string) (Result, error) {
 			r.trace("dns temp error: %v", err)
 			return TempError, err
 		}
-		if err == errMultipleRecords {
+		if err == ErrMultipleRecords {
 			r.trace("multiple dns records")
 			return PermError, err
 		}
@@ -290,7 +299,7 @@ func (r *resolution) Check(domain string) (Result, error) {
 	if txt == "" {
 		// No record => None.
 		// https://tools.ietf.org/html/rfc7208#section-4.5
-		return None, errNoResult
+		return None, ErrNoResult
 	}
 
 	fields := strings.Split(txt, " ")
@@ -308,7 +317,7 @@ func (r *resolution) Check(domain string) (Result, error) {
 	if len(redirects) > 1 {
 		// At most a single redirect is allowed.
 		// https://tools.ietf.org/html/rfc7208#section-6
-		return PermError, errInvalidDomain
+		return PermError, ErrInvalidDomain
 	}
 	fields = append(newfields, redirects...)
 
@@ -328,7 +337,7 @@ func (r *resolution) Check(domain string) (Result, error) {
 		// https://tools.ietf.org/html/rfc7208#section-4.6.4
 		if r.count > r.maxcount {
 			r.trace("lookup limit reached")
-			return PermError, errLookupLimitReached
+			return PermError, ErrLookupLimitReached
 		}
 
 		// See if we have a qualifier, defaulting to + (pass).
@@ -347,7 +356,7 @@ func (r *resolution) Check(domain string) (Result, error) {
 		if lfield == "all" {
 			// https://tools.ietf.org/html/rfc7208#section-5.1
 			r.trace("%v matched all", result)
-			return result, errMatchedAll
+			return result, ErrMatchedAll
 		} else if strings.HasPrefix(lfield, "include:") {
 			if ok, res, err := r.includeField(result, field, domain); ok {
 				r.trace("include ok, %v %v", res, err)
@@ -387,7 +396,7 @@ func (r *resolution) Check(domain string) (Result, error) {
 		} else {
 			// http://www.openspf.org/SPF_Record_Syntax
 			r.trace("permerror, unknown field")
-			return PermError, errUnknownField
+			return PermError, ErrUnknownField
 		}
 	}
 
@@ -434,7 +443,7 @@ func (r *resolution) getDNSRecord(domain string) (string, error) {
 	} else if l == 1 {
 		return records[0], nil
 	}
-	return "", errMultipleRecords
+	return "", ErrMultipleRecords
 }
 
 func isTemporary(err error) bool {
@@ -448,18 +457,18 @@ func (r *resolution) ipField(res Result, field string) (bool, Result, error) {
 	if strings.Contains(fip, "/") {
 		_, ipnet, err := net.ParseCIDR(fip)
 		if err != nil {
-			return true, PermError, errInvalidMask
+			return true, PermError, ErrInvalidMask
 		}
 		if ipnet.Contains(r.ip) {
-			return true, res, errMatchedIP
+			return true, res, ErrMatchedIP
 		}
 	} else {
 		ip := net.ParseIP(fip)
 		if ip == nil {
-			return true, PermError, errInvalidIP
+			return true, PermError, ErrInvalidIP
 		}
 		if ip.Equal(r.ip) {
-			return true, res, errMatchedIP
+			return true, res, ErrMatchedIP
 		}
 	}
 
@@ -476,11 +485,11 @@ func (r *resolution) ptrField(res Result, field, domain string) (bool, Result, e
 	}
 	ptrDomain, err := r.expandMacros(ptrDomain, domain)
 	if err != nil {
-		return true, PermError, errInvalidMacro
+		return true, PermError, ErrInvalidMacro
 	}
 
 	if ptrDomain == "" {
-		return true, PermError, errInvalidDomain
+		return true, PermError, ErrInvalidDomain
 	}
 
 	if r.ipNames == nil {
@@ -499,7 +508,7 @@ func (r *resolution) ptrField(res Result, field, domain string) (bool, Result, e
 			// have some A/AAAA.
 			// https://tools.ietf.org/html/rfc7208#section-5.5
 			if r.count > 10 {
-				return false, "", errLookupLimitReached
+				return false, "", ErrLookupLimitReached
 			}
 			r.count++
 			addrs, err := r.resolver.LookupIPAddr(r.ctx, n)
@@ -520,7 +529,7 @@ func (r *resolution) ptrField(res Result, field, domain string) (bool, Result, e
 	ptrDomain = strings.ToLower(ptrDomain)
 	for _, n := range r.ipNames {
 		if strings.HasSuffix(n, ptrDomain+".") {
-			return true, res, errMatchedPTR
+			return true, res, ErrMatchedPTR
 		}
 	}
 
@@ -534,11 +543,11 @@ func (r *resolution) existsField(res Result, field, domain string) (bool, Result
 	eDomain := field[7:]
 	eDomain, err := r.expandMacros(eDomain, domain)
 	if err != nil {
-		return true, PermError, errInvalidMacro
+		return true, PermError, ErrInvalidMacro
 	}
 
 	if eDomain == "" {
-		return true, PermError, errInvalidDomain
+		return true, PermError, ErrInvalidDomain
 	}
 
 	r.count++
@@ -554,7 +563,7 @@ func (r *resolution) existsField(res Result, field, domain string) (bool, Result
 	// Exists only counts if there are IPv4 matches.
 	for _, ip := range ips {
 		if ip.IP.To4() != nil {
-			return true, res, errMatchedExists
+			return true, res, ErrMatchedExists
 		}
 	}
 	return false, "", nil
@@ -566,7 +575,7 @@ func (r *resolution) includeField(res Result, field, domain string) (bool, Resul
 	incdomain := field[len("include:"):]
 	incdomain, err := r.expandMacros(incdomain, domain)
 	if err != nil {
-		return true, PermError, errInvalidMacro
+		return true, PermError, ErrInvalidMacro
 	}
 	ir, err := r.Check(incdomain)
 	switch ir {
@@ -582,7 +591,7 @@ func (r *resolution) includeField(res Result, field, domain string) (bool, Resul
 		return true, PermError, err
 	}
 
-	return false, "", fmt.Errorf("This should never be reached")
+	return false, "", fmt.Errorf("this should never be reached")
 }
 
 type dualMasks struct {
@@ -620,7 +629,7 @@ func domainAndMask(re *regexp.Regexp, field, domain string) (string, dualMasks,
 			i, err := strconv.Atoi(groups[4])
 			mask4 := net.CIDRMask(i, 32)
 			if err != nil || mask4 == nil {
-				return "", masks, errInvalidMask
+				return "", masks, ErrInvalidMask
 			}
 			masks.v4 = mask4
 		}
@@ -628,7 +637,7 @@ func domainAndMask(re *regexp.Regexp, field, domain string) (string, dualMasks,
 			i, err := strconv.Atoi(groups[6])
 			mask6 := net.CIDRMask(i, 128)
 			if err != nil || mask6 == nil {
-				return "", masks, errInvalidMask
+				return "", masks, ErrInvalidMask
 			}
 			masks.v6 = mask6
 		}
@@ -637,7 +646,7 @@ func domainAndMask(re *regexp.Regexp, field, domain string) (string, dualMasks,
 	// Test to catch malformed entries: if there's a /, there must be at least
 	// one mask.
 	if strings.Contains(field, "/") && masks.v4 == nil && masks.v6 == nil {
-		return "", masks, errInvalidMask
+		return "", masks, ErrInvalidMask
 	}
 
 	return domain, masks, nil
@@ -653,7 +662,7 @@ func (r *resolution) aField(res Result, field, domain string) (bool, Result, err
 	}
 	aDomain, err = r.expandMacros(aDomain, domain)
 	if err != nil {
-		return true, PermError, errInvalidMacro
+		return true, PermError, ErrInvalidMacro
 	}
 
 	r.count++
@@ -668,7 +677,7 @@ func (r *resolution) aField(res Result, field, domain string) (bool, Result, err
 	for _, ip := range ips {
 		if ipMatch(r.ip, ip.IP, masks) {
 			r.trace("a matched %v, %v, %v", r.ip, ip.IP, masks)
-			return true, res, errMatchedA
+			return true, res, ErrMatchedA
 		}
 	}
 
@@ -685,7 +694,7 @@ func (r *resolution) mxField(res Result, field, domain string) (bool, Result, er
 	}
 	mxDomain, err = r.expandMacros(mxDomain, domain)
 	if err != nil {
-		return true, PermError, errInvalidMacro
+		return true, PermError, ErrInvalidMacro
 	}
 
 	r.count++
@@ -701,7 +710,7 @@ func (r *resolution) mxField(res Result, field, domain string) (bool, Result, er
 	// There's an explicit maximum of 10 MX records per match.
 	// https://tools.ietf.org/html/rfc7208#section-4.6.4
 	if len(mxs) > 10 {
-		return true, PermError, errTooManyMXRecords
+		return true, PermError, ErrTooManyMXRecords
 	}
 
 	mxips := []net.IP{}
@@ -722,7 +731,7 @@ func (r *resolution) mxField(res Result, field, domain string) (bool, Result, er
 	for _, ip := range mxips {
 		if ipMatch(r.ip, ip, masks) {
 			r.trace("mx matched %v, %v, %v", r.ip, ip, masks)
-			return true, res, errMatchedMX
+			return true, res, ErrMatchedMX
 		}
 	}
 
@@ -734,11 +743,11 @@ func (r *resolution) redirectField(field, domain string) (Result, error) {
 	rDomain := field[len("redirect="):]
 	rDomain, err := r.expandMacros(rDomain, domain)
 	if err != nil {
-		return PermError, errInvalidMacro
+		return PermError, ErrInvalidMacro
 	}
 
 	if rDomain == "" {
-		return PermError, errInvalidDomain
+		return PermError, ErrInvalidDomain
 	}
 
 	// https://tools.ietf.org/html/rfc7208#section-6.1
@@ -764,7 +773,7 @@ func (r *resolution) expandMacros(s, domain string) (string, error) {
 	// doesn't, prevent them from sneaking through.
 	if strings.Contains(s, "/") {
 		r.trace("macro contains /")
-		return "", errInvalidDomain
+		return "", ErrInvalidDomain
 	}
 
 	// Bypass the complex logic if there are no macros present.
@@ -800,7 +809,7 @@ func (r *resolution) expandMacros(s, domain string) (string, error) {
 				inMacroDefinition = true
 				continue
 			}
-			return "", errInvalidMacro
+			return "", ErrInvalidMacro
 		}
 		if inMacroDefinition {
 			if c != '}' {
@@ -815,7 +824,7 @@ func (r *resolution) expandMacros(s, domain string) (string, error) {
 			r.trace("macro %q: %q", macroS, groups)
 			macroS = ""
 			if groups == nil {
-				return "", errInvalidMacro
+				return "", ErrInvalidMacro
 			}
 			letter := groups[1]
 
@@ -825,7 +834,7 @@ func (r *resolution) expandMacros(s, domain string) (string, error) {
 				// valid.
 				digits, err = strconv.Atoi(groups[2])
 				if err != nil || digits <= 0 {
-					return "", errInvalidMacro
+					return "", ErrInvalidMacro
 				}
 			}
 			reverse := groups[3] == "r" || groups[3] == "R"
@@ -867,7 +876,7 @@ func (r *resolution) expandMacros(s, domain string) (string, error) {
 			default:
 				// c, r, t are allowed in exp only, and we don't expand macros
 				// in exp so they are just as invalid as the rest.
-				return "", errInvalidMacro
+				return "", ErrInvalidMacro
 			}
 
 			// Split str using the given separators.
diff --git a/spf_test.go b/spf_test.go
index c44b12e..5e4a595 100644
--- a/spf_test.go
+++ b/spf_test.go
@@ -36,48 +36,48 @@ func TestBasic(t *testing.T) {
 		res Result
 		err error
 	}{
-		{"", None, errNoResult},
-		{"blah", None, errNoResult},
+		{"", None, ErrNoResult},
+		{"blah", None, ErrNoResult},
 		{"v=spf1", Neutral, nil},
 		{"v=spf1 ", Neutral, nil},
-		{"v=spf1 -", PermError, errUnknownField},
-		{"v=spf1 all", Pass, errMatchedAll},
-		{"v=spf1 exp=blah +all", Pass, errMatchedAll},
-		{"v=spf1  +all", Pass, errMatchedAll},
-		{"v=spf1 -all ", Fail, errMatchedAll},
-		{"v=spf1 ~all", SoftFail, errMatchedAll},
-		{"v=spf1 ?all", Neutral, errMatchedAll},
-		{"v=spf1 a ~all", SoftFail, errMatchedAll},
+		{"v=spf1 -", PermError, ErrUnknownField},
+		{"v=spf1 all", Pass, ErrMatchedAll},
+		{"v=spf1 exp=blah +all", Pass, ErrMatchedAll},
+		{"v=spf1  +all", Pass, ErrMatchedAll},
+		{"v=spf1 -all ", Fail, ErrMatchedAll},
+		{"v=spf1 ~all", SoftFail, ErrMatchedAll},
+		{"v=spf1 ?all", Neutral, ErrMatchedAll},
+		{"v=spf1 a ~all", SoftFail, ErrMatchedAll},
 		{"v=spf1 a/24", Neutral, nil},
-		{"v=spf1 a:d1110/24", Pass, errMatchedA},
-		{"v=spf1 a:d1110/montoto", PermError, errInvalidMask},
-		{"v=spf1 a:d1110/99", PermError, errInvalidMask},
+		{"v=spf1 a:d1110/24", Pass, ErrMatchedA},
+		{"v=spf1 a:d1110/montoto", PermError, ErrInvalidMask},
+		{"v=spf1 a:d1110/99", PermError, ErrInvalidMask},
 		{"v=spf1 a:d1110/32", Neutral, nil},
 		{"v=spf1 a:d1110", Neutral, nil},
-		{"v=spf1 a:d1111", Pass, errMatchedA},
+		{"v=spf1 a:d1111", Pass, ErrMatchedA},
 		{"v=spf1 a:nothing/24", Neutral, nil},
 		{"v=spf1 mx", Neutral, nil},
 		{"v=spf1 mx/24", Neutral, nil},
-		{"v=spf1 mx:a/montoto ~all", PermError, errInvalidMask},
-		{"v=spf1 mx:d1110/24 ~all", Pass, errMatchedMX},
-		{"v=spf1 mx:d1110/24//100 ~all", Pass, errMatchedMX},
-		{"v=spf1 mx:d1110/24//129 ~all", PermError, errInvalidMask},
-		{"v=spf1 mx:d1110/24/100 ~all", PermError, errInvalidMask},
-		{"v=spf1 mx:d1110/99 ~all", PermError, errInvalidMask},
-		{"v=spf1 ip4:1.2.3.4 ~all", SoftFail, errMatchedAll},
-		{"v=spf1 ip6:12 ~all", PermError, errInvalidIP},
-		{"v=spf1 ip4:1.1.1.1 -all", Pass, errMatchedIP},
-		{"v=spf1 ip4:1.1.1.1/24 -all", Pass, errMatchedIP},
-		{"v=spf1 ip4:1.1.1.1/lala -all", PermError, errInvalidMask},
-		{"v=spf1 ip4:1.1.1.1/33 -all", PermError, errInvalidMask},
-		{"v=spf1 include:doesnotexist", PermError, errNoResult},
-		{"v=spf1 ptr -all", Pass, errMatchedPTR},
-		{"v=spf1 ptr:d1111 -all", Pass, errMatchedPTR},
-		{"v=spf1 ptr:lalala -all", Pass, errMatchedPTR},
-		{"v=spf1 ptr:doesnotexist -all", Fail, errMatchedAll},
-		{"v=spf1 blah", PermError, errUnknownField},
-		{"v=spf1 exists:d1111 -all", Pass, errMatchedExists},
-		{"v=spf1 redirect=", PermError, errInvalidDomain},
+		{"v=spf1 mx:a/montoto ~all", PermError, ErrInvalidMask},
+		{"v=spf1 mx:d1110/24 ~all", Pass, ErrMatchedMX},
+		{"v=spf1 mx:d1110/24//100 ~all", Pass, ErrMatchedMX},
+		{"v=spf1 mx:d1110/24//129 ~all", PermError, ErrInvalidMask},
+		{"v=spf1 mx:d1110/24/100 ~all", PermError, ErrInvalidMask},
+		{"v=spf1 mx:d1110/99 ~all", PermError, ErrInvalidMask},
+		{"v=spf1 ip4:1.2.3.4 ~all", SoftFail, ErrMatchedAll},
+		{"v=spf1 ip6:12 ~all", PermError, ErrInvalidIP},
+		{"v=spf1 ip4:1.1.1.1 -all", Pass, ErrMatchedIP},
+		{"v=spf1 ip4:1.1.1.1/24 -all", Pass, ErrMatchedIP},
+		{"v=spf1 ip4:1.1.1.1/lala -all", PermError, ErrInvalidMask},
+		{"v=spf1 ip4:1.1.1.1/33 -all", PermError, ErrInvalidMask},
+		{"v=spf1 include:doesnotexist", PermError, ErrNoResult},
+		{"v=spf1 ptr -all", Pass, ErrMatchedPTR},
+		{"v=spf1 ptr:d1111 -all", Pass, ErrMatchedPTR},
+		{"v=spf1 ptr:lalala -all", Pass, ErrMatchedPTR},
+		{"v=spf1 ptr:doesnotexist -all", Fail, ErrMatchedAll},
+		{"v=spf1 blah", PermError, ErrUnknownField},
+		{"v=spf1 exists:d1111 -all", Pass, ErrMatchedExists},
+		{"v=spf1 redirect=", PermError, ErrInvalidDomain},
 	}
 
 	dns.Ip["d1111"] = []net.IP{ip1111}
@@ -111,24 +111,24 @@ func TestIPv6(t *testing.T) {
 		res Result
 		err error
 	}{
-		{"v=spf1 all", Pass, errMatchedAll},
-		{"v=spf1 a ~all", SoftFail, errMatchedAll},
+		{"v=spf1 all", Pass, ErrMatchedAll},
+		{"v=spf1 a ~all", SoftFail, ErrMatchedAll},
 		{"v=spf1 a/24", Neutral, nil},
-		{"v=spf1 a:d6660//24", Pass, errMatchedA},
-		{"v=spf1 a:d6660/24//100", Pass, errMatchedA},
+		{"v=spf1 a:d6660//24", Pass, ErrMatchedA},
+		{"v=spf1 a:d6660/24//100", Pass, ErrMatchedA},
 		{"v=spf1 a:d6660", Neutral, nil},
-		{"v=spf1 a:d6666", Pass, errMatchedA},
+		{"v=spf1 a:d6666", Pass, ErrMatchedA},
 		{"v=spf1 a:nothing//24", Neutral, nil},
-		{"v=spf1 mx:d6660//24 ~all", Pass, errMatchedMX},
-		{"v=spf1 mx:d6660/24//100 ~all", Pass, errMatchedMX},
-		{"v=spf1 mx:d6660/24/100 ~all", PermError, errInvalidMask},
-		{"v=spf1 ip6:2001:db8::68 ~all", Pass, errMatchedIP},
-		{"v=spf1 ip6:2001:db8::1/24 ~all", Pass, errMatchedIP},
-		{"v=spf1 ip6:2001:db8::1/100 ~all", Pass, errMatchedIP},
-		{"v=spf1 ptr -all", Pass, errMatchedPTR},
-		{"v=spf1 ptr:d6666 -all", Pass, errMatchedPTR},
-		{"v=spf1 ptr:sonlas6 -all", Pass, errMatchedPTR},
-		{"v=spf1 ptr:sonlas7 -all", Fail, errMatchedAll},
+		{"v=spf1 mx:d6660//24 ~all", Pass, ErrMatchedMX},
+		{"v=spf1 mx:d6660/24//100 ~all", Pass, ErrMatchedMX},
+		{"v=spf1 mx:d6660/24/100 ~all", PermError, ErrInvalidMask},
+		{"v=spf1 ip6:2001:db8::68 ~all", Pass, ErrMatchedIP},
+		{"v=spf1 ip6:2001:db8::1/24 ~all", Pass, ErrMatchedIP},
+		{"v=spf1 ip6:2001:db8::1/100 ~all", Pass, ErrMatchedIP},
+		{"v=spf1 ptr -all", Pass, ErrMatchedPTR},
+		{"v=spf1 ptr:d6666 -all", Pass, ErrMatchedPTR},
+		{"v=spf1 ptr:sonlas6 -all", Pass, ErrMatchedPTR},
+		{"v=spf1 ptr:sonlas7 -all", Fail, ErrMatchedAll},
 	}
 
 	dns.Ip["d6666"] = []net.IP{ip6666}
@@ -165,12 +165,12 @@ func TestInclude(t *testing.T) {
 		res Result
 		err error
 	}{
-		{"", PermError, errNoResult},
-		{"v=spf1 all", Pass, errMatchedAll},
+		{"", PermError, ErrNoResult},
+		{"v=spf1 all", Pass, ErrMatchedAll},
 
 		// domain2 did not pass, so continued and matched parent's ip4.
-		{"v=spf1", Pass, errMatchedIP},
-		{"v=spf1 -all", Pass, errMatchedIP},
+		{"v=spf1", Pass, ErrMatchedIP},
+		{"v=spf1 -all", Pass, ErrMatchedIP},
 	}
 
 	for _, c := range cases {
@@ -189,7 +189,7 @@ func TestRecursionLimit(t *testing.T) {
 	defaultTrace = t.Logf
 
 	res, err := CheckHost(ip1111, "domain")
-	if res != PermError || err != errLookupLimitReached {
+	if res != PermError || err != ErrLookupLimitReached {
 		t.Errorf("expected permerror, got %v (%v)", res, err)
 	}
 }
@@ -220,7 +220,7 @@ func TestInvalidRedirect(t *testing.T) {
 	}
 
 	res, err = CheckHost(ip1111, "domain")
-	if res != PermError || err != errNoResult {
+	if res != PermError || err != ErrNoResult {
 		t.Errorf("expected permerror, got %v (%v)", res, err)
 	}
 }
@@ -234,13 +234,13 @@ func TestRedirectOrder(t *testing.T) {
 
 	dns.Txt["domain"] = []string{"v=spf1 redirect=faildom"}
 	res, err := CheckHost(ip1111, "domain")
-	if res != Fail || err != errMatchedAll {
+	if res != Fail || err != ErrMatchedAll {
 		t.Errorf("expected fail, got %v (%v)", res, err)
 	}
 
 	dns.Txt["domain"] = []string{"v=spf1 redirect=faildom all"}
 	res, err = CheckHost(ip1111, "domain")
-	if res != Pass || err != errMatchedAll {
+	if res != Pass || err != ErrMatchedAll {
 		t.Errorf("expected pass, got %v (%v)", res, err)
 	}
 }
@@ -339,18 +339,18 @@ func TestMacros(t *testing.T) {
 		res Result
 		err error
 	}{
-		{"v=spf1 ptr:%{fff} -all", PermError, errInvalidMacro},
-		{"v=spf1 mx:%{fff} -all", PermError, errInvalidMacro},
-		{"v=spf1 redirect=%{fff}", PermError, errInvalidMacro},
-		{"v=spf1 a:%{o0}", PermError, errInvalidMacro},
-		{"v=spf1 +a:sss-%{s}-sss", Pass, errMatchedA},
-		{"v=spf1 +a:ooo-%{o}-ooo", Pass, errMatchedA},
-		{"v=spf1 +a:OOO-%{O}-OOO", Pass, errMatchedA},
-		{"v=spf1 +a:ppp-%{p}-ppp", Pass, errMatchedA},
-		{"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},
+		{"v=spf1 ptr:%{fff} -all", PermError, ErrInvalidMacro},
+		{"v=spf1 mx:%{fff} -all", PermError, ErrInvalidMacro},
+		{"v=spf1 redirect=%{fff}", PermError, ErrInvalidMacro},
+		{"v=spf1 a:%{o0}", PermError, ErrInvalidMacro},
+		{"v=spf1 +a:sss-%{s}-sss", Pass, ErrMatchedA},
+		{"v=spf1 +a:ooo-%{o}-ooo", Pass, ErrMatchedA},
+		{"v=spf1 +a:OOO-%{O}-OOO", Pass, ErrMatchedA},
+		{"v=spf1 +a:ppp-%{p}-ppp", Pass, ErrMatchedA},
+		{"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}
@@ -386,12 +386,12 @@ func TestMacrosV4(t *testing.T) {
 		res Result
 		err error
 	}{
-		{"v=spf1 +a:sr-%{sr}-sr", Pass, errMatchedA},
-		{"v=spf1 +a:sra-%{sr.}-sra", Pass, errMatchedA},
-		{"v=spf1 +a:o7-%{o7}-o7", Pass, errMatchedA},
-		{"v=spf1 +a:o1-%{o1}-o1", Pass, errMatchedA},
-		{"v=spf1 +a:o1r-%{o1r}-o1r", Pass, errMatchedA},
-		{"v=spf1 +a:vvv-%{v}-vvv", Pass, errMatchedA},
+		{"v=spf1 +a:sr-%{sr}-sr", Pass, ErrMatchedA},
+		{"v=spf1 +a:sra-%{sr.}-sra", Pass, ErrMatchedA},
+		{"v=spf1 +a:o7-%{o7}-o7", Pass, ErrMatchedA},
+		{"v=spf1 +a:o1-%{o1}-o1", Pass, ErrMatchedA},
+		{"v=spf1 +a:o1r-%{o1r}-o1r", Pass, ErrMatchedA},
+		{"v=spf1 +a:vvv-%{v}-vvv", Pass, ErrMatchedA},
 	}
 
 	dns.Ip["sr-com.user@domain-sr"] = []net.IP{ip1111}
@@ -466,9 +466,9 @@ func TestInvalidMacro(t *testing.T) {
 		}
 
 		out, err := r.expandMacros(macro, "sender.com")
-		if out != "" || err != errInvalidMacro {
+		if out != "" || err != ErrInvalidMacro {
 			t.Errorf(`[%s]:expected ""/%v, got %q/%v`,
-				macro, errInvalidMacro, out, err)
+				macro, ErrInvalidMacro, out, err)
 		}
 	}
 }
@@ -514,7 +514,7 @@ func TestOverrideLookupLimit(t *testing.T) {
 	// Set the limit to 3, which is not enough.
 	res, err = CheckHostWithSender(ip1111, "helo", "user@domain1",
 		OverrideLookupLimit(3))
-	if res != PermError || err != errLookupLimitReached {
+	if res != PermError || err != ErrLookupLimitReached {
 		t.Errorf("expected permerror/lookup limit reached, got %q / %q",
 			res, err)
 	}