12 "github.com/mjl-/mox/dns"
13 "github.com/mjl-/mox/smtp"
16func TestLookup(t *testing.T) {
17 resolver := dns.MockResolver{
18 TXT: map[string][]string{
19 "temperror.example.": {"irrelevant"},
20 "malformed.example.": {"v=spf1 !"},
21 "multiple.example.": {"v=spf1", "v=spf1"},
22 "nonspf.example.": {"something else"},
23 "ok.example.": {"v=spf1"},
26 "txt temperror.example.",
30 test := func(domain string, expStatus Status, expRecord *Record, expErr error) {
33 d := dns.Domain{ASCII: domain}
34 status, txt, record, _, err := Lookup(context.Background(), resolver, d)
35 if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) {
36 t.Fatalf("got err %v, expected err %v", err, expErr)
41 if status != expStatus || txt == "" || !reflect.DeepEqual(record, expRecord) {
42 t.Fatalf("got status %q, txt %q, record %#v, expected %q, ..., %#v", status, txt, record, expStatus, expRecord)
46 test("..", StatusNone, nil, ErrName)
47 test("absent.example", StatusNone, nil, ErrNoRecord)
48 test("temperror.example", StatusTemperror, nil, ErrDNS)
49 test("malformed.example", StatusPermerror, nil, ErrRecordSyntax)
50 test("multiple.example", StatusPermerror, nil, ErrMultipleRecords)
51 test("nonspf.example", StatusNone, nil, ErrNoRecord)
52 test("ok.example", StatusNone, &Record{Version: "spf1"}, nil)
55func TestExpand(t *testing.T) {
57 senderLocalpart: "strong-bad",
58 senderDomain: dns.Domain{ASCII: "email.example.com"},
59 domain: dns.Domain{ASCII: "email.example.com"},
61 MailFromLocalpart: "x",
62 MailFromDomain: dns.Domain{ASCII: "mox.example"},
63 HelloDomain: dns.IPDomain{Domain: dns.Domain{ASCII: "mx.mox.example"}},
64 LocalIP: net.ParseIP("10.10.10.10"),
65 LocalHostname: dns.Domain{ASCII: "self.example"},
68 resolver := dns.MockResolver{
69 PTR: map[string][]string{
70 "10.0.0.1": {"other.example.", "sub.mx.mox.example.", "mx.mox.example."},
71 "10.0.0.2": {"other.example.", "sub.mx.mox.example.", "mx.mox.example."},
72 "10.0.0.3": {"other.example.", "sub.mx.mox.example.", "mx.mox.example."},
74 A: map[string][]string{
75 "mx.mox.example.": {"10.0.0.1"},
76 "sub.mx.mox.example.": {"10.0.0.2"},
77 "other.example.": {"10.0.0.3"},
81 mustParseIP := func(s string) net.IP {
84 t.Fatalf("bad ip %q", s)
89 ctx := context.Background()
92 test := func(dns bool, macro, ip, exp string) {
96 args.dnsRequests = new(int)
97 args.voidLookups = new(int)
99 args.RemoteIP = mustParseIP(ip)
102 r, _, err := expandDomainSpec(ctx, resolver, macro, args, dns)
103 if (err == nil) != (exp != "") {
104 t.Fatalf("got err %v, expected expansion %q, for macro %q", err, exp, macro)
107 t.Fatalf("got expansion %q, expected %q, for macro %q", r, exp, macro)
111 testDNS := func(macro, ip, exp string) {
113 test(true, macro, ip, exp)
116 testExpl := func(macro, ip, exp string) {
118 test(false, macro, ip, exp)
121 testDNS("%{s}", "", "strong-bad@email.example.com")
122 testDNS("%{o}", "", "email.example.com")
123 testDNS("%{d}", "", "email.example.com")
124 testDNS("%{d4}", "", "email.example.com")
125 testDNS("%{d3}", "", "email.example.com")
126 testDNS("%{d2}", "", "example.com")
127 testDNS("%{d1}", "", "com")
128 testDNS("%{dr}", "", "com.example.email")
129 testDNS("%{d2r}", "", "example.email")
130 testDNS("%{l}", "", "strong-bad")
131 testDNS("%{l-}", "", "strong.bad")
132 testDNS("%{lr}", "", "strong-bad")
133 testDNS("%{lr-}", "", "bad.strong")
134 testDNS("%{l1r-}", "", "strong")
137 testDNS("%b", "", "")
138 testDNS("%{", "", "")
139 testDNS("%{s", "", "")
140 testDNS("%{s1", "", "")
141 testDNS("%{s0}", "", "")
142 testDNS("%{s1r", "", "")
143 testDNS("%{s99999999999999999999999999999999999999999999999999999999999999999999999}", "", "")
145 testDNS("%{ir}.%{v}._spf.%{d2}", "192.0.2.3", "3.2.0.192.in-addr._spf.example.com")
146 testDNS("%{lr-}.lp._spf.%{d2}", "192.0.2.3", "bad.strong.lp._spf.example.com")
147 testDNS("%{lr-}.lp.%{ir}.%{v}._spf.%{d2}", "192.0.2.3", "bad.strong.lp.3.2.0.192.in-addr._spf.example.com")
148 testDNS("%{ir}.%{v}.%{l1r-}.lp._spf.%{d2}", "192.0.2.3", "3.2.0.192.in-addr.strong.lp._spf.example.com")
149 testDNS("%{d2}.trusted-domains.example.net", "192.0.2.3", "example.com.trusted-domains.example.net")
151 testDNS("%{ir}.%{v}._spf.%{d2}", "2001:db8::cb01", "1.0.b.c.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.ip6._spf.example.com")
154 testDNS("%%%-%_", "10.0.0.1", "%%20 ")
155 testDNS("%{p}", "10.0.0.1", "mx.mox.example.")
156 testDNS("%{p}", "10.0.0.2", "sub.mx.mox.example.")
157 testDNS("%{p}", "10.0.0.3", "other.example.")
158 testDNS("%{p}", "10.0.0.4", "unknown")
159 testExpl("%{c}", "10.0.0.1", "10.10.10.10")
160 testExpl("%{r}", "10.0.0.1", "self.example")
166 timeNow = func() time.Time {
169 testExpl("%{t}", "10.0.0.1", fmt.Sprintf("%d", now.Unix()))
170 // DNS name can be 253 bytes long, each label can be 63 bytes.
171 xlabel := make([]byte, 62)
172 for i := range xlabel {
175 label := string(xlabel)
176 name := label + "." + label + "." + label + "." + label // 4*62+3 = 251
177 testDNS("x."+name, "10.0.0.1", "x."+name) // Still fits.
178 testDNS("xx."+name, "10.0.0.1", name) // Does not fit, "xx." is truncated to make it fit.
179 testDNS("%{p}..", "10.0.0.1", "")
180 testDNS("%{h}", "10.0.0.1", "mx.mox.example")
183func TestVerify(t *testing.T) {
184 xip := func(s string) net.IP {
187 t.Fatalf("bad ip: %q", s)
191 iplist := func(l ...string) []net.IP {
192 r := make([]net.IP, len(l))
193 for i, s := range l {
200 r := dns.MockResolver{
201 PTR: map[string][]string{
202 "192.0.2.10": {"example.com."},
203 "192.0.2.11": {"example.com."},
204 "192.0.2.65": {"amy.example.com."},
205 "192.0.2.66": {"bob.example.com."},
206 "192.0.2.129": {"mail-a.example.com."},
207 "192.0.2.130": {"mail-b.example.com."},
208 "192.0.2.140": {"mail-c.example.org."},
209 "10.0.0.4": {"bob.example.com."},
211 TXT: map[string][]string{
213 "mobile-users._spf.example.com.": {"v=spf1 exists:%{l1r+}.%{d}"},
214 "remote-users._spf.example.com.": {"v=spf1 exists:%{ir}.%{l1r+}.%{d}"},
217 "ip4._spf.example.com.": {"v=spf1 -ip4:192.0.2.0/24 +all"},
221 "_spf.example.com.": {"v=spf1 include:_netblock.example.com -all"},
222 "_netblock.example.com.": {"v=spf1 ip4:192.0.2.128/28 -all"},
224 A: map[string][]string{
225 "example.com.": {"192.0.2.10", "192.0.2.11"},
226 "amy.example.com.": {"192.0.2.65"},
227 "bob.example.com.": {"192.0.2.66"},
228 "mail-a.example.com.": {"192.0.2.129"},
229 "mail-b.example.com.": {"192.0.2.130"},
230 "mail-c.example.org.": {"192.0.2.140"},
233 "mary.mobile-users._spf.example.com.": {"127.0.0.2"},
234 "fred.mobile-users._spf.example.com.": {"127.0.0.2"},
235 "15.15.168.192.joel.remote-users._spf.example.com.": {"127.0.0.2"},
236 "16.15.168.192.joel.remote-users._spf.example.com.": {"127.0.0.2"},
238 AAAA: map[string][]string{},
239 MX: map[string][]*net.MX{
241 {Host: "mail-a.example.com.", Pref: 10},
242 {Host: "mail-b.example.com.", Pref: 20},
245 {Host: "mail-c.example.org.", Pref: 10},
250 ctx := context.Background()
252 verify := func(ip net.IP, localpart string, status Status) {
256 MailFromLocalpart: smtp.Localpart(localpart),
257 MailFromDomain: dns.Domain{ASCII: "example.com"},
259 LocalIP: xip("127.0.0.1"),
260 LocalHostname: dns.Domain{ASCII: "localhost"},
262 received, _, _, _, err := Verify(ctx, r, args)
263 if received.Result != status {
264 t.Fatalf("got status %q, expected %q, for ip %q (err %v)", received.Result, status, ip, err)
267 t.Fatalf("unexpected error: %s", err)
271 test := func(txt string, ips []net.IP, only bool) {
272 r.TXT["example.com."] = []string{txt}
273 seen := map[string]struct{}{}
274 for _, ip := range ips {
275 verify(ip, "", StatusPass)
276 seen[ip.String()] = struct{}{}
281 for ip := range r.PTR {
282 if _, ok := seen[ip]; ok {
285 verify(xip(ip), "", StatusFail)
290 test("v=spf1 +all", iplist("192.0.2.129", "1.2.3.4"), false)
291 test("v=spf1 a -all", iplist("192.0.2.10", "192.0.2.11"), true)
292 test("v=spf1 a:example.org -all", iplist(), true)
293 test("v=spf1 mx -all", iplist("192.0.2.129", "192.0.2.130"), true)
294 test("v=spf1 mx:example.org -all", iplist("192.0.2.140"), true)
295 test("v=spf1 mx mx:example.org -all", iplist("192.0.2.129", "192.0.2.130", "192.0.2.140"), true)
296 test("v=spf1 mx/30 mx:example.org/30 -all", iplist("192.0.2.129", "192.0.2.130", "192.0.2.140"), true)
297 test("v=spf1 ptr -all", iplist("192.0.2.10", "192.0.2.11", "192.0.2.65", "192.0.2.66", "192.0.2.129", "192.0.2.130"), true)
298 test("v=spf1 ip4:192.0.2.128/28 -all", iplist("192.0.2.129", "192.0.2.130", "192.0.2.140"), true)
301 test("v=spf1 redirect=_spf.example.com", iplist("192.0.2.129", "192.0.2.130", "192.0.2.140"), true)
304 r.TXT["example.com."] = []string{"v=spf1 mx include:mobile-users._spf.%{d} include:remote-users._spf.%{d} -all"}
305 verify(xip("1.2.3.4"), "mary", StatusPass)
306 verify(xip("1.2.3.4"), "fred", StatusPass)
307 verify(xip("1.2.3.4"), "fred+wildcard", StatusPass)
308 verify(xip("1.2.3.4"), "joel", StatusFail)
309 verify(xip("1.2.3.4"), "other", StatusFail)
310 verify(xip("192.168.15.15"), "joel", StatusPass)
311 verify(xip("192.168.15.16"), "joel", StatusPass)
312 verify(xip("192.168.15.17"), "joel", StatusFail)
313 verify(xip("192.168.15.17"), "other", StatusFail)
316 r.TXT["example.com."] = []string{"v=spf1 -include:ip4._spf.%{d} -include:ptr._spf.%{d} +all"}
317 r.PTR["192.0.2.1"] = []string{"a.example.com."}
318 r.PTR["192.0.0.1"] = []string{"b.example.com."}
319 r.A["a.example.com."] = []string{"192.0.2.1"}
320 r.A["b.example.com."] = []string{"192.0.0.1"}
322 verify(xip("192.0.2.1"), "", StatusPass) // IP in range and PTR matches.
323 verify(xip("192.0.2.2"), "", StatusFail) // IP in range but no PTR match.
324 verify(xip("192.0.0.1"), "", StatusFail) // PTR match but IP not in range.
325 verify(xip("192.0.0.2"), "", StatusFail) // No PTR match and IP not in range.
329func TestVerifyMultipleDomain(t *testing.T) {
330 resolver := dns.MockResolver{
331 TXT: map[string][]string{
332 "example.org.": {"v=spf1 include:example.com include:example.net -all"},
333 "la.example.org.": {"v=spf1 redirect=example.org"},
334 "example.com.": {"v=spf1 ip4:10.0.0.1 -all"},
335 "example.net.": {"v=spf1 ip4:10.0.0.2 -all"},
339 verify := func(domain, ip string, status Status) {
343 MailFromDomain: dns.Domain{ASCII: domain},
344 RemoteIP: net.ParseIP(ip),
345 LocalIP: net.ParseIP("127.0.0.1"),
346 LocalHostname: dns.Domain{ASCII: "localhost"},
348 received, _, _, _, err := Verify(context.Background(), resolver, args)
350 t.Fatalf("unexpected error: %s", err)
352 if received.Result != status {
353 t.Fatalf("got status %q, expected %q, for ip %q", received.Result, status, ip)
357 verify("example.com", "10.0.0.1", StatusPass)
358 verify("example.net", "10.0.0.2", StatusPass)
359 verify("example.com", "10.0.0.2", StatusFail)
360 verify("example.net", "10.0.0.1", StatusFail)
361 verify("example.org", "10.0.0.1", StatusPass)
362 verify("example.org", "10.0.0.2", StatusPass)
363 verify("example.org", "10.0.0.3", StatusFail)
364 verify("la.example.org", "10.0.0.1", StatusPass)
365 verify("la.example.org", "10.0.0.2", StatusPass)
366 verify("la.example.org", "10.0.0.3", StatusFail)
369func TestVerifyScenarios(t *testing.T) {
370 test := func(resolver dns.Resolver, args Args, expStatus Status, expDomain string, expExpl string, expErr error) {
373 recv, d, expl, _, err := Verify(context.Background(), resolver, args)
374 if (err == nil) != (expErr == nil) || err != nil && !errors.Is(err, expErr) {
375 t.Fatalf("got err %v, expected %v", err, expErr)
377 if expStatus != recv.Result || expDomain != "" && d.ASCII != expDomain || expExpl != "" && expl != expExpl {
378 t.Fatalf("got status %q, domain %q, expl %q, err %v", recv.Result, d, expl, err)
382 r := dns.MockResolver{
383 TXT: map[string][]string{
384 "mox.example.": {"v=spf1 ip6:2001:db8::0/64 -all"},
385 "void.example.": {"v=spf1 exists:absent1.example exists:absent2.example ip4:1.2.3.4 exists:absent3.example -all"},
386 "loop.example.": {"v=spf1 include:loop.example -all"},
387 "a-unknown.example.": {"v=spf1 a:absent.example"},
388 "include-bad-expand.example.": {"v=spf1 include:%{c}"}, // macro 'c' only valid while expanding for "exp".
389 "exists-bad-expand.example.": {"v=spf1 exists:%{c}"}, // macro 'c' only valid while expanding for "exp".
390 "redir-bad-expand.example.": {"v=spf1 redirect=%{c}"}, // macro 'c' only valid while expanding for "exp".
391 "a-bad-expand.example.": {"v=spf1 a:%{c}"}, // macro 'c' only valid while expanding for "exp".
392 "mx-bad-expand.example.": {"v=spf1 mx:%{c}"}, // macro 'c' only valid while expanding for "exp".
393 "ptr-bad-expand.example.": {"v=spf1 ptr:%{c}"}, // macro 'c' only valid while expanding for "exp".
394 "include-temperror.example.": {"v=spf1 include:temperror.example"},
395 "include-none.example.": {"v=spf1 include:absent.example"},
396 "include-permerror.example.": {"v=spf1 include:permerror.example"},
397 "permerror.example.": {"v=spf1 a:%%"},
398 "no-mx.example.": {"v=spf1 mx -all"},
399 "many-mx.example.": {"v=spf1 mx -all"},
400 "many-ptr.example.": {"v=spf1 ptr:many-mx.example ~all"},
401 "expl.example.": {"v=spf1 ip4:10.0.1.1 -ip4:10.0.1.2 ?all exp=details.expl.example"},
402 "details.expl.example.": {"your ip %{i} is not allowed"},
403 "expl-multi.example.": {"v=spf1 ip4:10.0.1.1 -ip4:10.0.1.2 ~all exp=details-multi.expl.example"},
404 "details-multi.expl.example.": {"your ip ", "%{i} is not allowed"},
406 A: map[string][]string{
407 "mail.mox.example.": {"10.0.0.1"},
408 "mx1.many-mx.example.": {"10.0.1.1"},
409 "mx2.many-mx.example.": {"10.0.1.2"},
410 "mx3.many-mx.example.": {"10.0.1.3"},
411 "mx4.many-mx.example.": {"10.0.1.4"},
412 "mx5.many-mx.example.": {"10.0.1.5"},
413 "mx6.many-mx.example.": {"10.0.1.6"},
414 "mx7.many-mx.example.": {"10.0.1.7"},
415 "mx8.many-mx.example.": {"10.0.1.8"},
416 "mx9.many-mx.example.": {"10.0.1.9"},
417 "mx10.many-mx.example.": {"10.0.1.10"},
418 "mx11.many-mx.example.": {"10.0.1.11"},
420 AAAA: map[string][]string{
421 "mail.mox.example.": {"2001:db8::1"},
423 MX: map[string][]*net.MX{
424 "no-mx.example.": {{Host: ".", Pref: 10}},
425 "many-mx.example.": {
426 {Host: "mx1.many-mx.example.", Pref: 1},
427 {Host: "mx2.many-mx.example.", Pref: 2},
428 {Host: "mx3.many-mx.example.", Pref: 3},
429 {Host: "mx4.many-mx.example.", Pref: 4},
430 {Host: "mx5.many-mx.example.", Pref: 5},
431 {Host: "mx6.many-mx.example.", Pref: 6},
432 {Host: "mx7.many-mx.example.", Pref: 7},
433 {Host: "mx8.many-mx.example.", Pref: 8},
434 {Host: "mx9.many-mx.example.", Pref: 9},
435 {Host: "mx10.many-mx.example.", Pref: 10},
436 {Host: "mx11.many-mx.example.", Pref: 11},
439 PTR: map[string][]string{
440 "2001:db8::1": {"mail.mox.example."},
441 "10.0.1.1": {"mx1.many-mx.example.", "mx2.many-mx.example.", "mx3.many-mx.example.", "mx4.many-mx.example.", "mx5.many-mx.example.", "mx6.many-mx.example.", "mx7.many-mx.example.", "mx8.many-mx.example.", "mx9.many-mx.example.", "mx10.many-mx.example.", "mx11.many-mx.example."},
444 "txt temperror.example.",
449 test(r, Args{RemoteIP: net.ParseIP("2001:db8::1"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "mox.example"}}, StatusPass, "", "", nil)
450 test(r, Args{RemoteIP: net.ParseIP("2001:fa11::1"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "mox.example"}}, StatusFail, "", "", nil)
452 // Use EHLO identity.
453 test(r, Args{RemoteIP: net.ParseIP("2001:db8::1"), HelloDomain: dns.IPDomain{Domain: dns.Domain{ASCII: "mox.example"}}}, StatusPass, "", "", nil)
454 test(r, Args{RemoteIP: net.ParseIP("2001:db8::1"), HelloDomain: dns.IPDomain{Domain: dns.Domain{ASCII: "mail.mox.example"}}}, StatusNone, "", "", ErrNoRecord)
456 // Too many void lookups.
457 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "void.example"}}, StatusPass, "", "", nil) // IP found after 2 void lookups, but before 3rd.
458 test(r, Args{RemoteIP: net.ParseIP("1.1.1.1"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "void.example"}}, StatusPermerror, "", "", ErrTooManyVoidLookups) // IP not found, not doing 3rd lookup.
460 // Too many DNS requests.
461 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "loop.example"}}, StatusPermerror, "", "", ErrTooManyDNSRequests) // Self-referencing record, will abort after 10 includes.
463 // a:other where other does not exist.
464 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "a-unknown.example"}}, StatusNeutral, "", "", nil)
466 // Expand with an invalid macro.
467 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "include-bad-expand.example"}}, StatusPermerror, "", "", ErrMacroSyntax)
468 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "exists-bad-expand.example"}}, StatusPermerror, "", "", ErrMacroSyntax)
469 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "redir-bad-expand.example"}}, StatusPermerror, "", "", ErrMacroSyntax)
471 // Expand with invalid character (because macros are not expanded).
472 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "a-bad-expand.example"}}, StatusPermerror, "", "", ErrName)
473 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "mx-bad-expand.example"}}, StatusPermerror, "", "", ErrName)
474 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "ptr-bad-expand.example"}}, StatusPermerror, "", "", ErrName)
476 // Include with varying results.
477 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "include-temperror.example"}}, StatusTemperror, "", "", ErrDNS)
478 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "include-none.example"}}, StatusPermerror, "", "", ErrNoRecord)
479 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "include-permerror.example"}}, StatusPermerror, "", "", ErrName)
481 // MX with explicit "." for "no mail".
482 test(r, Args{RemoteIP: net.ParseIP("1.2.3.4"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "no-mx.example"}}, StatusFail, "", "", nil)
484 // MX names beyond 10th entry result in Permerror.
485 test(r, Args{RemoteIP: net.ParseIP("10.0.1.1"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "many-mx.example"}}, StatusPass, "", "", nil)
486 test(r, Args{RemoteIP: net.ParseIP("10.0.1.10"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "many-mx.example"}}, StatusPass, "", "", nil)
487 test(r, Args{RemoteIP: net.ParseIP("10.0.1.11"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "many-mx.example"}}, StatusPermerror, "", "", ErrTooManyDNSRequests)
488 test(r, Args{RemoteIP: net.ParseIP("10.0.1.254"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "many-mx.example"}}, StatusPermerror, "", "", ErrTooManyDNSRequests)
490 // PTR names beyond 10th entry are ignored.
491 test(r, Args{RemoteIP: net.ParseIP("10.0.1.1"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "many-ptr.example"}}, StatusPass, "", "", nil)
492 test(r, Args{RemoteIP: net.ParseIP("10.0.1.2"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "many-ptr.example"}}, StatusSoftfail, "", "", nil)
494 // Explanation from txt records.
495 test(r, Args{RemoteIP: net.ParseIP("10.0.1.1"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "expl.example"}}, StatusPass, "", "", nil)
496 test(r, Args{RemoteIP: net.ParseIP("10.0.1.2"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "expl.example"}}, StatusFail, "", "your ip 10.0.1.2 is not allowed", nil)
497 test(r, Args{RemoteIP: net.ParseIP("10.0.1.3"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "expl.example"}}, StatusNeutral, "", "", nil)
498 test(r, Args{RemoteIP: net.ParseIP("10.0.1.2"), MailFromLocalpart: "x", MailFromDomain: dns.Domain{ASCII: "expl-multi.example"}}, StatusFail, "", "your ip 10.0.1.2 is not allowed", nil)
500 // Verify with IP EHLO.
501 test(r, Args{RemoteIP: net.ParseIP("2001:db8::1"), HelloDomain: dns.IPDomain{IP: net.ParseIP("::1")}}, StatusNone, "", "", nil)
504func TestEvaluate(t *testing.T) {
506 resolver := dns.MockResolver{}
508 status, _, _, _, _ := Evaluate(context.Background(), record, resolver, args)
509 if status != StatusNone {
510 t.Fatalf("got status %q, expected none", status)
514 HelloDomain: dns.IPDomain{Domain: dns.Domain{ASCII: "test.example"}},
516 status, mechanism, _, _, err := Evaluate(context.Background(), record, resolver, args)
517 if status != StatusNeutral || mechanism != "default" || err != nil {
518 t.Fatalf("got status %q, mechanism %q, err %v, expected neutral, default, no error", status, mechanism, err)