Skip to content

Commit d77c263

Browse files
authored
BUGFIX: Fix SPF parsing of qualified mechanisms like +mx (#4062)
<!-- ## Before submiting a pull request Please make sure you've run the following commands from the root directory. bin/generate-all.sh (this runs commands like "go generate", fixes formatting, and so on) ## Release changelog section Help keep the release changelog clear by pre-naming the proper section in the GitHub pull request title. Some examples: * CICD: Add required GHA permissions for goreleaser * DOCS: Fixed providers with "contributor support" table * ROUTE53: Allow R53_ALIAS records to enable target health evaluation More examples/context can be found in the file .goreleaser.yml under the 'build' > 'changelog' key. !--> Fixes a regression where `SPF_BUILDER` with `overflow` rejected valid RFC 7208 qualified mechanisms like `+mx`, `~mx`, `?a`, etc. with the error `unsupported SPF part mx`. The root cause was that `lcpart` (the lowercase comparison variable) was created before stripping the qualifier prefix (`+`,`-`,`~`,`?`), so `HasPrefix` checks against `"mx"` or `"a"` failed when the string was still `"+mx"` or `"+a"`. Qualifiers on mechanisms are valid per [RFC 7208 Section 4.7](https://www.rfc-editor.org/rfc/rfc7208#section-4.7). A regression test covering all qualifier/mechanism combinations has been added. Fixes #4042
1 parent afd69e9 commit d77c263

File tree

2 files changed

+35
-1
lines changed

2 files changed

+35
-1
lines changed

pkg/spflib/parse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ func Parse(text string, dnsres Resolver) (*SPFRecord, error) {
5252
if part == "" {
5353
continue
5454
}
55-
lcpart := strings.ToLower(part) // We have seen "Ip4" instead of "ip4". Let's be gracious and allow it.
5655
p := &SPFPart{Text: part}
5756
if qualifiers[part[0]] {
5857
part = part[1:]
5958
}
59+
lcpart := strings.ToLower(part) // We have seen "Ip4" instead of "ip4". Let's be gracious and allow it.
6060
rec.Parts = append(rec.Parts, p)
6161
if part == "all" {
6262
// all. nothing else matters.

pkg/spflib/parse_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,40 @@ func TestParseRedirectOnly(t *testing.T) {
117117
t.Log(rec.Print())
118118
}
119119

120+
func TestParseQualifiedMechanisms(t *testing.T) {
121+
// Regression test: SPF mechanisms with qualifiers (+mx, -all, ~all, ?mx)
122+
// should be parsed correctly. See https://github.com/StackExchange/dnscontrol/issues/4042
123+
dnsres, err := NewCache("testdata-dns1.json")
124+
if err != nil {
125+
t.Fatal(err)
126+
}
127+
tests := []struct {
128+
input string
129+
wantErr bool
130+
lookups int
131+
}{
132+
{"v=spf1 +mx -all", false, 1},
133+
{"v=spf1 ~mx -all", false, 1},
134+
{"v=spf1 ?mx -all", false, 1},
135+
{"v=spf1 +a -all", false, 1},
136+
{"v=spf1 ~a -all", false, 1},
137+
{"v=spf1 +mx +a -all", false, 2},
138+
{"v=spf1 +ip4:192.168.0.0/24 -all", false, 0},
139+
{"v=spf1 +ip6:2001:db8::/32 -all", false, 0},
140+
}
141+
for _, tt := range tests {
142+
t.Run(tt.input, func(t *testing.T) {
143+
rec, err := Parse(tt.input, dnsres)
144+
if (err != nil) != tt.wantErr {
145+
t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
146+
}
147+
if err == nil && rec.Lookups() != tt.lookups {
148+
t.Errorf("Parse(%q) lookups = %d, want %d", tt.input, rec.Lookups(), tt.lookups)
149+
}
150+
})
151+
}
152+
}
153+
120154
func TestParseRedirectLast(t *testing.T) {
121155
dnsres, err := NewCache("testdata-dns1.json")
122156
if err != nil {

0 commit comments

Comments
 (0)