# This is the openspf.org test suite (version 2006.11) based on RFC 4408. # $Id: rfc4408-tests.yml,v 1.1.2.5 2007/01/14 04:54:42 customdesigned Exp $ # vim:sw=2 sts=2 # # Contributors: # Stuart D Gathman everything so far # Julian Mehnle proofread YAML syntax, spelling, formal schema # Informal contributors (suggestions but no code): # Craig Whitmore # Frank Ellermann # Wayne Schlitt # Scott Kitterman # Norman Maurer # Mark Shewmaker # --- description: Initial processing tests: toolonglabel: description: >- DNS labels limited to 63 chars. comment: >- For initial processing, a long label results in None, not TempError spec: 4.3/1 helo: mail.example.net host: 1.2.3.5 mailfrom: lyme.eater@A123456789012345678901234567890123456789012345678901234567890123.example.com result: none longlabel: description: >- DNS labels limited to 63 chars. spec: 4.3/1 helo: mail.example.net host: 1.2.3.5 mailfrom: lyme.eater@A12345678901234567890123456789012345678901234567890123456789012.example.com result: fail emptylabel: spec: 4.3/1 helo: mail.example.net host: 1.2.3.5 mailfrom: lyme.eater@A...example.com result: none nolocalpart: spec: 4.3/2 helo: mail.example.net host: 1.2.3.4 mailfrom: '@example.net' result: fail explanation: postmaster zonedata: example.com: - TIMEOUT example.net: - SPF: v=spf1 -all exp=exp.example.net a.example.net: - SPF: v=spf1 -all exp=exp.example.net exp.example.net: - TXT: '%{l}' a12345678901234567890123456789012345678901234567890123456789012.example.com: - SPF: v=spf1 -all --- description: Record lookup tests: both: spec: 4.4/1 helo: mail.example.net host: 1.2.3.4 mailfrom: foo@both.example.net result: fail txtonly: description: Result is none if checking SPF records only. spec: 4.4/1 helo: mail.example.net host: 1.2.3.4 mailfrom: foo@txtonly.example.net result: [fail, none] spfonly: description: Result is none if checking TXT records only. spec: 4.4/1 helo: mail.example.net host: 1.2.3.4 mailfrom: foo@spfonly.example.net result: [fail, none] spftimeout: description: >- TXT record present, but SPF lookup times out. Result is temperror if checking SPF records only. comment: >- This actually happens for a popular braindead DNS server. spec: 4.4/1 helo: mail.example.net host: 1.2.3.4 mailfrom: foo@spftimeout.example.net result: [fail, temperror] txttimeout: description: >- SPF record present, but TXT lookup times out. If only TXT records are checked, result is temperror. spec: 4.4/1 helo: mail.example.net host: 1.2.3.4 mailfrom: foo@txttimeout.example.net result: [fail, temperror] nospftxttimeout: description: >- No SPF record present, and TXT lookup times out. If only TXT records are checked, result is temperror. comment: >- Because TXT records is where v=spf1 records will likely be, returning temperror will try again later. A timeout due to a braindead server is unlikely in the case of TXT, as opposed to the newer SPF RR. spec: 4.4/1 helo: mail.example.net host: 1.2.3.4 mailfrom: foo@nospftxttimeout.example.net result: [temperror, none] alltimeout: description: Both TXT and SPF queries time out spec: 4.4/2 helo: mail.example.net host: 1.2.3.4 mailfrom: foo@alltimeout.example.net result: temperror zonedata: both.example.net: - TXT: v=spf1 -all - SPF: v=spf1 -all txtonly.example.net: - TXT: v=spf1 -all spfonly.example.net: - SPF: v=spf1 -all - TXT: NONE spftimeout.example.net: - TXT: v=spf1 -all - TIMEOUT txttimeout.example.net: - SPF: v=spf1 -all - TXT: NONE - TIMEOUT nospftxttimeout.example.net: - SPF: "v=spf3 !a:yahoo.com -all" - TXT: NONE - TIMEOUT alltimeout.example.net: - TIMEOUT --- description: Selecting records tests: nospace1: description: >- Version must be terminated by space or end of record. TXT pieces are joined without intervening spaces. spec: 4.5/4 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example2.com result: none empty: description: Empty SPF record. spec: 4.5/4 helo: mail1.example1.com host: 1.2.3.4 mailfrom: foo@example1.com result: neutral nospace2: spec: 4.5/4 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example3.com result: pass spfoverride: description: >- SPF records override TXT records. Older implementation may check TXT records only. spec: 4.5/5 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example4.com result: [pass, fail] multitxt1: description: >- Older implementations will give permerror/unknown because of the conflicting TXT records. However, RFC 4408 says the SPF records overrides them. spec: 4.5/5 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example5.com result: [pass, permerror] multitxt2: description: >- Multiple records is a permerror, v=spf1 is case insensitive spec: 4.5/6 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example6.com result: permerror multispf1: description: >- Multiple records is a permerror, even when they are identical. spec: 4.5/6 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example7.com result: permerror multispf2: description: >- Older implementations will give pass because there is a single TXT record. But RFC 4408 requires permerror because the SPF records override and there are more than one. spec: 4.5/6 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example8.com result: [permerror, pass] nospf: spec: 4.5/7 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@mail.example1.com result: none case-insensitive: description: >- v=spf1 is case insensitive spec: 4.5/6 helo: mail.example1.com host: 1.2.3.4 mailfrom: foo@example9.com result: softfail zonedata: example3.com: - SPF: v=spf10 - SPF: v=spf1 mx - MX: [0, mail.example1.com] example1.com: - SPF: v=spf1 example2.com: - SPF: [ 'v=spf1', 'mx' ] mail.example1.com: - A: 1.2.3.4 example4.com: - SPF: v=spf1 +all - TXT: v=spf1 -all example5.com: - SPF: v=spf1 +all - TXT: v=spf1 -all - TXT: v=spf1 +all example6.com: - TXT: v=spf1 -all - TXT: V=sPf1 +all example7.com: - SPF: v=spf1 -all - SPF: v=spf1 -all example8.com: - SPF: v=spf1 -all - SPF: v=spf1 -all - TXT: v=spf1 +all example9.com: - SPF: v=SpF1 ~all --- description: Record evaluation tests: detect-errors-anywhere: description: Any syntax errors anywhere in the record MUST be detected. spec: 4.6 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t1.example.com result: permerror modifier-charset-good: description: name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." ) spec: 4.6.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t2.example.com result: pass modifier-charset-bad1: description: >- '=' character immediately after the name and before any ":" or "/" spec: 4.6.1/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t3.example.com result: permerror modifier-charset-bad2: description: >- '=' character immediately after the name and before any ":" or "/" spec: 4.6.1/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t4.example.com result: permerror redirect-after-mechanisms1: description: >- The "redirect" modifier has an effect after all the mechanisms. comment: >- The redirect in this example would violate processing limits, except that it is never used because of the all mechanism. spec: 4.6.3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t5.example.com result: softfail redirect-after-mechanisms2: description: >- The "redirect" modifier has an effect after all the mechanisms. spec: 4.6.3 helo: mail.example.com host: 1.2.3.5 mailfrom: foo@t6.example.com result: fail default-result: description: Default result is neutral. spec: 4.7/1 helo: mail.example.com host: 1.2.3.5 mailfrom: foo@t7.example.com result: neutral redirect-is-modifier: description: |- Invalid mechanism. Redirect is a modifier. spec: 4.6.1/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t8.example.com result: permerror invalid-domain: description: >- Domain-spec must end in macro-expand or valid toplabel. spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t9.example.com result: permerror invalid-domain-empty-label: description: >- Domain-spec must end in macro-expand or valid toplabel. comment: >- But anything goes before the toplabel. Empty labels cannot be encoded for sending to a name server, so resolver must give error or empty result. Empty result is analogous to 4.3/1, and so is preferred. spec: [8.1/2, 5/10] helo: mail.example.com host: 1.2.3.4 mailfrom: foo@t10.example.com result: [ fail, temperror ] invalid-domain-long: description: >- Domain-spec must end in macro-expand or valid toplabel. comment: >- But anything goes before the toplabel. Upper case H macro url escapes the HELO string, the result is longer than 63 chars. Long labels cannot be coded in a DNS query packet, so resolver must give error or empty result. Empty result is analogous to 4.3/1, and so is preferred. spec: [8.1/2, 5/10] helo: "%%%%%%%%%%%%%%%%%%%%%%" host: 1.2.3.4 mailfrom: foo@t11.example.com result: [ fail, temperror ] zonedata: mail.example.com: - A: 1.2.3.4 t1.example.com: - SPF: v=spf1 ip4:1.2.3.4 -all moo t2.example.com: - SPF: v=spf1 moo.cow-far_out=man:dog/cat ip4:1.2.3.4 -all t3.example.com: - SPF: v=spf1 moo.cow/far_out=man:dog/cat ip4:1.2.3.4 -all t4.example.com: - SPF: v=spf1 moo.cow:far_out=man:dog/cat ip4:1.2.3.4 -all t5.example.com: - SPF: v=spf1 redirect=t5.example.com ~all t6.example.com: - SPF: v=spf1 ip4:1.2.3.4 redirect=t2.example.com t7.example.com: - SPF: v=spf1 ip4:1.2.3.4 t8.example.com: - SPF: v=spf1 ip4:1.2.3.4 redirect:t2.example.com t9.example.com: - SPF: v=spf1 a:foo-bar -all t10.example.com: - SPF: v=spf1 a:mail.example...com -all t11.example.com: - SPF: v=spf1 a:%{H}.bar -all --- description: ALL mechanism syntax tests: all-dot: description: | all = "all" comment: |- At least one implementation got this wrong spec: 5.1/1 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: permerror all-arg: description: | all = "all" comment: |- At least one implementation got this wrong spec: 5.1/1 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: permerror all-cidr: description: | all = "all" spec: 5.1/1 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e3.example.com result: permerror all-neutral: description: | all = "all" spec: 5.1/1 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e4.example.com result: neutral all-double: description: | all = "all" spec: 5.1/1 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e5.example.com result: pass zonedata: mail.example.com: - A: 1.2.3.4 e1.example.com: - SPF: v=spf1 -all. e2.example.com: - SPF: v=spf1 -all:foobar e3.example.com: - SPF: v=spf1 -all/8 e4.example.com: - SPF: v=spf1 ?all e5.example.com: - SPF: v=spf1 all -all --- description: PTR mechanism syntax tests: ptr-cidr: description: |- PTR = "ptr" [ ":" domain-spec ] spec: 5.5/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: permerror ptr-match-target: description: >- Check all validated domain names to see if they end in the <target-name> domain. spec: 5.5/5 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: pass ptr-match-implicit: description: >- Check all validated domain names to see if they end in the <target-name> domain. spec: 5.5/5 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e3.example.com result: pass ptr-nomatch-invalid: description: >- Check all validated domain names to see if they end in the <target-name> domain. comment: >- This PTR record does not validate spec: 5.5/5 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e4.example.com result: fail ptr-match-ip6: description: >- Check all validated domain names to see if they end in the <target-name> domain. spec: 5.5/5 helo: mail.example.com host: CAFE:BABE::1 mailfrom: foo@e3.example.com result: pass zonedata: mail.example.com: - A: 1.2.3.4 e1.example.com: - SPF: v=spf1 ptr/0 -all e2.example.com: - SPF: v=spf1 ptr:example.com -all 4.3.2.1.in-addr.arpa: - PTR: e3.example.com - PTR: e4.example.com - PTR: mail.example.com 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa: - PTR: e3.example.com e3.example.com: - SPF: v=spf1 ptr -all - A: 1.2.3.4 - AAAA: CAFE:BABE::1 e4.example.com: - SPF: v=spf1 ptr -all --- description: A mechanism syntax tests: a-cidr6: description: | A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] spec: 5.3/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6.example.com result: fail a-bad-cidr4: description: | A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] spec: 5.3/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6a.example.com result: permerror a-bad-cidr6: description: | A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] spec: 5.3/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e7.example.com result: permerror a-multi-ip1: description: >- A matches any returned IP. spec: 5.3/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e10.example.com result: pass a-multi-ip2: description: >- A matches any returned IP. spec: 5.3/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e10.example.com result: pass a-bad-domain: description: >- domain-spec must pass basic syntax checks, comment: >- A ':' may appear in domain-spec, but not in top-label. spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e9.example.com result: permerror a-nxdomain: description: >- If no ips are returned, A mechanism does not match, even with /0. spec: 5.3/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: fail a-cidr4-0: description: >- Matches if any A records are present in DNS. spec: 5.3/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: pass a-cidr4-0-ip6: description: >- Matches if any A records are present in DNS. spec: 5.3/3 helo: mail.example.com host: 1234::1 mailfrom: foo@e2.example.com result: fail a-cidr6-0-ip4: description: >- Would match if any AAAA records are present in DNS, but not for an IP4 connection. spec: 5.3/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2a.example.com result: fail a-cidr6-0-ip4mapped: description: >- Would match if any AAAA records are present in DNS, but not for an IP4 connection. spec: 5.3/3 helo: mail.example.com host: ::FFFF:1.2.3.4 mailfrom: foo@e2a.example.com result: fail a-cidr6-0-ip6: description: >- Matches if any AAAA records are present in DNS. spec: 5.3/3 helo: mail.example.com host: 1234::1 mailfrom: foo@e2a.example.com result: pass a-cidr6-0-nxdomain: description: >- No match if no AAAA records are present in DNS. spec: 5.3/3 helo: mail.example.com host: 1234::1 mailfrom: foo@e2b.example.com result: fail a-null: description: >- Null not allowed in top-label. spec: 8.1/2 helo: mail.example.com host: 1.2.3.5 mailfrom: foo@e3.example.com result: permerror a-numeric: description: >- Top-label may not be all numeric comment: >- A common publishing mistake is using ip4 addresses with A mechanism. This should receive special diagnostic attention in the permerror. spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e4.example.com result: permerror a-numeric-top-label: description: >- Top-label may not be all numeric spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e5.example.com result: permerror a-colon-domain: description: >- Domain-spec may contain any visible char except % spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e11.example.com result: pass a-colon-domain-ip4mapped: description: >- Domain-spec may contain any visible char except % spec: 8.1/2 helo: mail.example.com host: ::FFFF:1.2.3.4 mailfrom: foo@e11.example.com result: pass a-bad-toplab: description: >- Toplabel may not begin with - spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e12.example.com result: permerror zonedata: mail.example.com: - A: 1.2.3.4 e1.example.com: - SPF: v=spf1 a/0 -all e2.example.com: - A: 1.1.1.1 - AAAA: 1234::2 - SPF: v=spf1 a/0 -all e2a.example.com: - AAAA: 1234::1 - SPF: v=spf1 a//0 -all e2b.example.com: - A: 1.1.1.1 - SPF: v=spf1 a//0 -all e3.example.com: - SPF: "v=spf1 a:foo.example.com\0" e4.example.com: - SPF: v=spf1 a:111.222.33.44 e5.example.com: - SPF: v=spf1 a:abc.123 e6.example.com: - SPF: v=spf1 a//33 -all e6a.example.com: - SPF: v=spf1 a/33 -all e7.example.com: - SPF: v=spf1 a//129 -all e9.example.com: - SPF: v=spf1 a:example.com:8080 e10.example.com: - SPF: v=spf1 a:foo.example.com/24 foo.example.com: - A: 1.1.1.1 - A: 1.2.3.5 e11.example.com: - SPF: v=spf1 a:foo:bar/baz.example.com foo:bar/baz.example.com: - A: 1.2.3.4 e12.example.com: - SPF: v=spf1 a:example.-com --- description: Include mechanism semantics and syntax tests: include-fail: description: >- recursive check_host() result of fail causes include to not match. spec: 5.2/9 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: softfail include-softfail: description: >- recursive check_host() result of softfail causes include to not match. spec: 5.2/9 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: pass include-neutral: description: >- recursive check_host() result of neutral causes include to not match. spec: 5.2/9 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e3.example.com result: fail include-temperror: description: >- recursive check_host() result of temperror causes include to temperror spec: 5.2/9 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e4.example.com result: temperror include-permerror: description: >- recursive check_host() result of permerror causes include to permerror spec: 5.2/9 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e5.example.com result: permerror include-syntax-error: description: >- include = "include" ":" domain-spec spec: 5.2/1 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6.example.com result: permerror include-none: description: >- recursive check_host() result of none causes include to permerror spec: 5.2/9 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e7.example.com result: permerror zonedata: mail.example.com: - A: 1.2.3.4 ip5.example.com: - SPF: v=spf1 ip4:1.2.3.5 -all ip6.example.com: - SPF: v=spf1 ip4:1.2.3.6 ~all ip7.example.com: - SPF: v=spf1 ip4:1.2.3.7 ?all ip8.example.com: - TIMEOUT erehwon.example.com: - TXT: v=spfl am not an SPF record e1.example.com: - SPF: v=spf1 include:ip5.example.com ~all e2.example.com: - SPF: v=spf1 include:ip6.example.com all e3.example.com: - SPF: v=spf1 include:ip7.example.com -all e4.example.com: - SPF: v=spf1 include:ip8.example.com -all e5.example.com: - SPF: v=spf1 include:e6.example.com -all e6.example.com: - SPF: v=spf1 include +all e7.example.com: - SPF: v=spf1 include:erehwon.example.com -all --- description: MX mechanism syntax tests: mx-cidr6: description: | MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] spec: 5.4/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6.example.com result: fail mx-bad-cidr4: description: | MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] spec: 5.4/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6a.example.com result: permerror mx-bad-cidr6: description: | MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] spec: 5.4/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e7.example.com result: permerror mx-multi-ip1: description: >- MX matches any returned IP. spec: 5.4/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e10.example.com result: pass mx-multi-ip2: description: >- MX matches any returned IP. spec: 5.4/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e10.example.com result: pass mx-bad-domain: description: >- domain-spec must pass basic syntax checks, comment: >- A ':' may appear in domain-spec, but not in top-label. spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e9.example.com result: permerror mx-nxdomain: description: >- If no ips are returned, MX mechanism does not match, even with /0. spec: 5.4/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: fail mx-cidr4-0: description: >- Matches if any A records for any MX records are present in DNS. spec: 5.4/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: pass mx-cidr4-0-ip6: description: >- Matches if any A records for any MX records are present in DNS. spec: 5.4/3 helo: mail.example.com host: 1234::1 mailfrom: foo@e2.example.com result: fail mx-cidr6-0-ip4: description: >- Would match if any AAAA records for MX records are present in DNS, but not for an IP4 connection. spec: 5.4/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2a.example.com result: fail mx-cidr6-0-ip4mapped: description: >- Would match if any AAAA records for MX records are present in DNS, but not for an IP4 connection. spec: 5.4/3 helo: mail.example.com host: ::FFFF:1.2.3.4 mailfrom: foo@e2a.example.com result: fail mx-cidr6-0-ip6: description: >- Matches if any AAAA records for any MX records are present in DNS. spec: 5.3/3 helo: mail.example.com host: 1234::1 mailfrom: foo@e2a.example.com result: pass mx-cidr6-0-nxdomain: description: >- No match if no AAAA records for any MX records are present in DNS. spec: 5.4/3 helo: mail.example.com host: 1234::1 mailfrom: foo@e2b.example.com result: fail mx-null: description: >- Null not allowed in top-label. spec: 8.1/2 helo: mail.example.com host: 1.2.3.5 mailfrom: foo@e3.example.com result: permerror mx-numeric-top-label: description: >- Top-label may not be all numeric spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e5.example.com result: permerror mx-colon-domain: description: >- Domain-spec may contain any visible char except % spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e11.example.com result: pass mx-colon-domain-ip4mapped: description: >- Domain-spec may contain any visible char except % spec: 8.1/2 helo: mail.example.com host: ::FFFF:1.2.3.4 mailfrom: foo@e11.example.com result: pass mx-bad-toplab: description: >- Toplabel may not begin with - spec: 8.1/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e12.example.com result: permerror mx-empty: description: >- test null MX comment: >- Some implementations have had trouble with null MX spec: 5.4/3 helo: mail.example.com host: 1.2.3.4 mailfrom: "" result: neutral mx-implicit: description: >- If the target name has no MX records, check_host() MUST NOT pretend the target is its single MX, and MUST NOT default to an A lookup on the target-name directly. spec: 5.4/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e4.example.com result: neutral zonedata: mail.example.com: - A: 1.2.3.4 - MX: [0, ""] - SPF: v=spf1 mx e1.example.com: - SPF: v=spf1 mx/0 -all - MX: [0, e1.example.com] e2.example.com: - A: 1.1.1.1 - AAAA: 1234::2 - MX: [0, e2.example.com] - SPF: v=spf1 mx/0 -all e2a.example.com: - AAAA: 1234::1 - MX: [0, e2a.example.com] - SPF: v=spf1 mx//0 -all e2b.example.com: - A: 1.1.1.1 - MX: [0, e2b.example.com] - SPF: v=spf1 mx//0 -all e3.example.com: - SPF: "v=spf1 mx:foo.example.com\0" e4.example.com: - SPF: v=spf1 mx - A: 1.2.3.4 e5.example.com: - SPF: v=spf1 mx:abc.123 e6.example.com: - SPF: v=spf1 mx//33 -all e6a.example.com: - SPF: v=spf1 mx/33 -all e7.example.com: - SPF: v=spf1 mx//129 -all e9.example.com: - SPF: v=spf1 mx:example.com:8080 e10.example.com: - SPF: v=spf1 mx:foo.example.com/24 foo.example.com: - MX: [0, foo1.example.com] foo1.example.com: - A: 1.1.1.1 - A: 1.2.3.5 e11.example.com: - SPF: v=spf1 mx:foo:bar/baz.example.com foo:bar/baz.example.com: - MX: [ 0, "foo:bar/baz.example.com"] - A: 1.2.3.4 e12.example.com: - SPF: v=spf1 mx:example.-com --- description: IP4 mechanism syntax tests: cidr4-0: description: >- ip4-cidr-length = "/" 1*DIGIT spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: pass cidr4-32: description: >- ip4-cidr-length = "/" 1*DIGIT spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: pass cidr4-33: description: >- Invalid CIDR should get permerror. comment: >- The RFC is silent on ip4 CIDR > 32 or ip6 CIDR > 128. However, since there is no reasonable interpretation (except a noop), we have read between the lines to see a prohibition on invalid CIDR. spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e3.example.com result: permerror cidr4-032: description: >- Invalid CIDR should get permerror. comment: >- Leading zeros are not explicitly prohibited by the RFC. However, since the RFC explicity prohibits leading zeros in ip4-network, our interpretation is that CIDR should be also. spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e4.example.com result: permerror bare-ip4: description: >- IP4 = "ip4" ":" ip4-network [ ip4-cidr-length ] spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e5.example.com result: permerror bad-ip4-port: description: >- IP4 = "ip4" ":" ip4-network [ ip4-cidr-length ] comment: >- This has actually been published in SPF records. spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e8.example.com result: permerror bad-ip4-short: description: >- It is not permitted to omit parts of the IP address instead of using CIDR notations. spec: 5.6/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e9.example.com result: permerror ip4-dual-cidr: description: >- dual-cidr-length not permitted on ip4 spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6.example.com result: permerror ip4-mapped-ip6: description: >- IP4 mapped IP6 connections MUST be treated as IP4 spec: 5/9/2 helo: mail.example.com host: ::FFFF:1.2.3.4 mailfrom: foo@e7.example.com result: fail zonedata: mail.example.com: - A: 1.2.3.4 e1.example.com: - SPF: v=spf1 ip4:1.1.1.1/0 -all e2.example.com: - SPF: v=spf1 ip4:1.2.3.4/32 -all e3.example.com: - SPF: v=spf1 ip4:1.2.3.4/33 -all e4.example.com: - SPF: v=spf1 ip4:1.2.3.4/032 -all e5.example.com: - SPF: v=spf1 ip4 e6.example.com: - SPF: v=spf1 ip4:1.2.3.4//32 e7.example.com: - SPF: v=spf1 -ip4:1.2.3.4 ip6:::FFFF:1.2.3.4 e8.example.com: - SPF: v=spf1 ip4:1.2.3.4:8080 e9.example.com: - SPF: v=spf1 ip4:1.2.3 --- description: IP6 mechanism syntax comment: >- IP4 only implementations may skip tests where host is not IP4 tests: bare-ip6: description: >- IP6 = "ip6" ":" ip6-network [ ip6-cidr-length ] spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: permerror cidr6-0-ip4: description: >- IP4 connections do not match ip6. comment: >- There is controversy over ip4 mapped connections. RFC4408 clearly requires such connections to be considered as ip4. However, some interpret the RFC to mean that such connections should *also* match appropriate ip6 mechanisms (but not, inexplicably, A or MX mechanisms). Until there is consensus, both results are acceptable. spec: 5/9/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: [neutral, pass] cidr6-ip4: description: >- Even if the SMTP connection is via IPv6, an IPv4-mapped IPv6 IP address (see RFC 3513, Section 2.5.5) MUST still be considered an IPv4 address. comment: >- There is controversy over ip4 mapped connections. RFC4408 clearly requires such connections to be considered as ip4. However, some interpret the RFC to mean that such connections should *also* match appropriate ip6 mechanisms (but not, inexplicably, A or MX mechanisms). Until there is consensus, both results are acceptable. spec: 5/9/2 helo: mail.example.com host: ::FFFF:1.2.3.4 mailfrom: foo@e2.example.com result: [neutral, pass] cidr6-0: description: >- Match any IP6 spec: 5/8 helo: mail.example.com host: DEAF:BABE::CAB:FEE mailfrom: foo@e2.example.com result: pass cidr6-129: description: >- Invalid CIDR comment: >- IP4 only implementations MUST fully syntax check all mechanisms, even if they otherwise ignore them. spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e3.example.com result: permerror cidr6-bad: description: >- dual-cidr syntax not used for ip6 comment: >- IP4 only implementations MUST fully syntax check all mechanisms, even if they otherwise ignore them. spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e4.example.com result: permerror cidr6-33: description: >- make sure ip4 cidr restriction are not used for ip6 spec: 5.6/2 helo: mail.example.com host: "CAFE:BABE:8000::" mailfrom: foo@e5.example.com result: pass cidr6-33-ip4: description: >- make sure ip4 cidr restriction are not used for ip6 spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e5.example.com result: neutral ip6-bad1: description: >- spec: 5.6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6.example.com result: permerror zonedata: mail.example.com: - A: 1.2.3.4 e1.example.com: - SPF: v=spf1 -all ip6 e2.example.com: - SPF: v=spf1 ip6:::1.1.1.1/0 e3.example.com: - SPF: v=spf1 ip6:::1.1.1.1/129 e4.example.com: - SPF: v=spf1 ip6:::1.1.1.1//33 e5.example.com: - SPF: v=spf1 ip6:CAFE:BABE:8000::/33 e6.example.com: - SPF: v=spf1 ip6::CAFE::BABE --- description: Semantics of exp and other modifiers comment: >- Implementing exp= is optional. If not implemented, the test driver should not check the explanation field. tests: redirect-none: description: >- If no SPF record is found, or if the target-name is malformed, the result is a "PermError" rather than "None". spec: 6.1/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e10.example.com result: permerror redirect-cancels-exp: description: >- when executing "redirect", exp= from the original domain MUST NOT be used. spec: 6.2/13 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: fail explanation: DEFAULT include-ignores-exp: description: >- when executing "include", exp= from the target domain MUST NOT be used. spec: 6.2/13 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e7.example.com result: fail explanation: Correct! redirect-cancels-prior-exp: description: >- when executing "redirect", exp= from the original domain MUST NOT be used. spec: 6.2/13 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e3.example.com result: fail explanation: See me. invalid-modifier: description: | unknown-modifier = name "=" macro-string name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." ) comment: >- Unknown modifier name must begin with alpha. spec: A/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e5.example.com result: permerror empty-modifier-name: description: | name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." ) comment: >- Unknown modifier name must not be empty. spec: A/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6.example.com result: permerror dorky-sentinel: description: >- An implementation that uses a legal expansion as a sentinel. We cannot check them all, but we can check this one. comment: >- Spaces are allowed in local-part. spec: 8.1/6 helo: mail.example.com host: 1.2.3.4 mailfrom: "Macro Error@e8.example.com" result: fail explanation: Macro Error in implementation exp-multiple-txt: description: | Ignore exp if multiple TXT records. comment: >- If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given. spec: 6.2/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e11.example.com result: fail explanation: DEFAULT exp-empty-domain: description: | Ignore exp if empty domain-spec. comment: >- If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given. spec: 6.2/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e12.example.com result: fail explanation: DEFAULT exp-syntax-error: description: | Ignore exp if syntax error. comment: >- If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), or if no records are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no exp modifier was given. spec: 6.2/4 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e13.example.com result: fail explanation: DEFAULT exp-twice: description: | exp= appears twice. comment: >- These two modifiers (exp,redirect) MUST NOT appear in a record more than once each. If they do, then check_host() exits with a result of "PermError". spec: 6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e14.example.com result: permerror redirect-twice: description: | redirect= appears twice. comment: >- These two modifiers (exp,redirect) MUST NOT appear in a record more than once each. If they do, then check_host() exits with a result of "PermError". spec: 6/2 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e15.example.com result: permerror unknown-modifier-syntax: description: | unknown-modifier = name "=" macro-string comment: >- Unknown modifiers must have valid macro syntax. spec: A/3 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e9.example.com result: permerror zonedata: mail.example.com: - A: 1.2.3.4 e1.example.com: - SPF: v=spf1 exp=exp1.example.com redirect=e2.example.com e2.example.com: - SPF: v=spf1 -all e3.example.com: - SPF: v=spf1 exp=exp1.example.com redirect=e4.example.com e4.example.com: - SPF: v=spf1 -all exp=exp2.example.com exp1.example.com: - TXT: No-see-um exp2.example.com: - TXT: See me. exp3.example.com: - TXT: Correct! exp4.example.com: - TXT: "%{l} in implementation" e5.example.com: - SPF: v=spf1 1up=foo e6.example.com: - SPF: v=spf1 =all e7.example.com: - SPF: v=spf1 include:e3.example.com -all exp=exp3.example.com e8.example.com: - SPF: v=spf1 -all exp=exp4.example.com e9.example.com: - SPF: v=spf1 -all foo=%abc e10.example.com: - SPF: v=spf1 redirect=erehwon.example.com e11.example.com: - SPF: v=spf1 -all exp=e11msg.example.com e11msg.example.com: - TXT: Answer a fool according to his folly. - TXT: Do not answer a fool according to his folly. e12.example.com: - SPF: v=spf1 exp= -all e13.example.com: - SPF: v=spf1 exp=e13msg.example.com -all e13msg.example.com: - TXT: The %{x}-files. e14.example.com: - SPF: v=spf1 exp=e13msg.example.com -all exp=e11msg.example.com e15.example.com: - SPF: v=spf1 redirect=e12.example.com -all redirect=e12.example.com --- description: Macro expansion rules tests: trailing-dot-domain: spec: 8.1/16 description: >- trailing dot is ignored for domains helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@example.com result: pass trailing-dot-exp: spec: 8.1 description: >- trailing dot is not removed from explanation comment: >- A simple way for an implementation to ignore trailing dots on domains is to remove it when present. But be careful not to remove it for explanation text. helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@exp.example.com result: fail explanation: This is a test. exp-only-macro-char: spec: 8.1/8 description: >- The following macro letters are allowed only in "exp" text: c, r, t helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@e2.example.com result: permerror invalid-macro-char: spec: 8.1/9 description: >- A '%' character not followed by a '{', '%', '-', or '_' character is a syntax error. helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@e1.example.com result: permerror exp-txt-macro-char: spec: 8.1/20 description: >- For IPv4 addresses, both the "i" and "c" macros expand to the standard dotted-quad format. helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@e3.example.com result: fail explanation: Connections from 192.168.218.40 not authorized. domain-name-truncation: spec: 8.1/25 description: >- When the result of macro expansion is used in a domain name query, if the expanded domain name exceeds 253 characters, the left side is truncated to fit, by removing successive domain labels until the total length does not exceed 253 characters. helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@somewhat.long.exp.example.com result: fail explanation: Congratulations! That was tricky. v-macro-ip4: spec: 8.1/6 description: |- v = the string "in-addr" if <ip> is ipv4, or "ip6" if <ip> is ipv6 helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@e4.example.com result: fail explanation: 192.168.218.40 is queried as 40.218.168.192.in-addr.arpa v-macro-ip6: spec: 8.1/6 description: |- v = the string "in-addr" if <ip> is ipv4, or "ip6" if <ip> is ipv6 helo: msgbas2x.cos.example.com host: CAFE:BABE::1 mailfrom: test@e4.example.com result: fail explanation: cafe:babe::1 is queried as 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa undef-macro: spec: 8.1/6 description: >- Allowed macros chars are 'slodipvh' plus 'crt' in explanation. helo: msgbas2x.cos.example.com host: CAFE:BABE::192.168.218.40 mailfrom: test@e5.example.com result: permerror p-macro-ip4-novalid: spec: 8.1/22 description: |- p = the validated domain name of <ip> comment: >- The PTR in this example does not validate. helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@e6.example.com result: fail explanation: connect from unknown p-macro-ip4-valid: spec: 8.1/22 description: |- p = the validated domain name of <ip> comment: >- If a subdomain of the <domain> is present, it SHOULD be used. helo: msgbas2x.cos.example.com host: 192.168.218.41 mailfrom: test@e6.example.com result: fail explanation: connect from mx.example.com p-macro-ip6-novalid: spec: 8.1/22 description: |- p = the validated domain name of <ip> comment: >- The PTR in this example does not validate. helo: msgbas2x.cos.example.com host: CAFE:BABE::1 mailfrom: test@e6.example.com result: fail explanation: connect from unknown p-macro-ip6-valid: spec: 8.1/22 description: |- p = the validated domain name of <ip> comment: >- If a subdomain of the <domain> is present, it SHOULD be used. helo: msgbas2x.cos.example.com host: CAFE:BABE::3 mailfrom: test@e6.example.com result: fail explanation: connect from mx.example.com p-macro-multiple: spec: 8.1/22 description: |- p = the validated domain name of <ip> comment: >- If a subdomain of the <domain> is present, it SHOULD be used. helo: msgbas2x.cos.example.com host: 192.168.218.42 mailfrom: test@e7.example.com result: [ pass, softfail ] upper-macro: spec: 8.1/26 description: >- Uppercased macros expand exactly as their lowercased equivalents, and are then URL escaped. helo: msgbas2x.cos.example.com host: 192.168.218.42 mailfrom: jack&jill=up@e8.example.com result: fail explanation: http://example.com/why.html?l=jack%26jill%3Dup hello-macro: spec: 8.1/6 description: |- h = HELO/EHLO domain helo: msgbas2x.cos.example.com host: 192.168.218.40 mailfrom: test@e9.example.com result: pass invalid-hello-macro: spec: 8.1/2 description: |- h = HELO/EHLO domain, but HELO is invalid comment: >- Domain-spec must end in either a macro, or a valid toplabel. It is not correct to check syntax after macro expansion. helo: "JUMPIN' JUPITER" host: 192.168.218.40 mailfrom: test@e9.example.com result: fail require-valid-helo: spec: 8.1/6 description: >- Example of requiring valid helo in sender policy. helo: OEMCOMPUTER host: 1.2.3.4 mailfrom: test@e10.example.com result: fail zonedata: example.com.d.spf.example.com: - SPF: v=spf1 redirect=a.spf.example.com a.spf.example.com: - SPF: v=spf1 include:o.spf.example.com. ~all o.spf.example.com: - SPF: v=spf1 ip4:192.168.218.40 msgbas2x.cos.example.com: - A: 192.168.218.40 example.com: - A: 192.168.90.76 - SPF: v=spf1 redirect=%{d}.d.spf.example.com. exp.example.com: - SPF: v=spf1 exp=msg.example.com. -all msg.example.com: - TXT: This is a test. e1.example.com: - SPF: v=spf1 -exists:%(ir).sbl.example.com ?all e2.example.com: - SPF: v=spf1 -all exp=%{r}.example.com e3.example.com: - SPF: v=spf1 -all exp=%{ir}.example.com 40.218.168.192.example.com: - TXT: Connections from %{c} not authorized. somewhat.long.exp.example.com: - SPF: v=spf1 -all exp=foobar.%{o}.%{o}.%{o}.%{o}.%{o}.%{o}.%{o}.%{o}.example.com somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.example.com: - TXT: Congratulations! That was tricky. e4.example.com: - SPF: v=spf1 -all exp=e4msg.example.com e4msg.example.com: - TXT: "%{c} is queried as %{ir}.%{v}.arpa" e5.example.com: - SPF: v=spf1 a:%{a}.example.com -all e6.example.com: - SPF: v=spf1 -all exp=e6msg.example.com e6msg.example.com: - TXT: "connect from %{p}" mx.example.com: - A: 192.168.218.41 - A: 192.168.218.42 - AAAA: CAFE:BABE::2 - AAAA: CAFE:BABE::3 40.218.168.192.in-addr.arpa: - PTR: mx.example.com 41.218.168.192.in-addr.arpa: - PTR: mx.example.com 42.218.168.192.in-addr.arpa: - PTR: mx.example.com - PTR: mx.e7.example.com 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa: - PTR: mx.example.com 3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa: - PTR: mx.example.com mx.e7.example.com: - A: 192.168.218.42 mx.e7.example.com.should.example.com: - A: 127.0.0.2 mx.example.com.ok.example.com: - A: 127.0.0.2 e7.example.com: - SPF: v=spf1 exists:%{p}.should.example.com ~exists:%{p}.ok.example.com e8.example.com: - SPF: v=spf1 -all exp=msg8.%{D2} msg8.example.com: - TXT: "http://example.com/why.html?l=%{L}" e9.example.com: - SPF: v=spf1 a:%{H} -all e10.example.com: - SPF: v=spf1 -include:_spfh.%{d2} ip4:1.2.3.0/24 -all _spfh.example.com: - SPF: v=spf1 -a:%{h} +all --- description: Processing limits tests: redirect-loop: description: >- SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check. spec: 10.1/6 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e1.example.com result: permerror include-loop: description: >- SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check. spec: 10.1/6 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e2.example.com result: permerror mx-limit: description: >- there MUST be a limit of no more than 10 MX looked up and checked. comment: >- The required result for this test was the subject of much controversy. Many felt that the RFC *should* have specified permerror, but the consensus was that it failed to actually do so. The preferred result reflects evaluating the 10 allowed MX records in the order returned by the test data - or sorted via priority. If testing with live DNS, the MX order may be random, and a pass result would still be compliant. The SPF result is effectively random. spec: 10.1/7 helo: mail.example.com host: 1.2.3.5 mailfrom: foo@e4.example.com result: [neutral, pass] ptr-limit: description: >- there MUST be a limit of no more than 10 PTR looked up and checked. comment: >- The result of this test cannot be permerror not only because the RFC does not specify it, but because the sender has no control over the PTR records of spammers. The preferred result reflects evaluating the 10 allowed PTR records in the order returned by the test data. If testing with live DNS, the PTR order may be random, and a pass result would still be compliant. The SPF result is effectively randomized. spec: 10.1/7 helo: mail.example.com host: 1.2.3.5 mailfrom: foo@e5.example.com result: [neutral, pass] mech-at-limit: description: >- SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check. spec: 10.1/6 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e6.example.com result: pass mech-over-limit: description: >- SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check. comment: >- We do not check whether an implementation counts mechanisms before or after evaluation. The RFC is not clear on this. spec: 10.1/6 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e7.example.com result: permerror include-at-limit: description: >- SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check. comment: >- The part of the RFC that talks about MAY parse the entire record first (4.6) is specific to syntax errors. Processing limits is a different, non-syntax issue. Processing limits (10.1) specifically talks about limits during a check. spec: 10.1/6 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e8.example.com result: pass include-over-limit: description: >- SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check. spec: 10.1/6 helo: mail.example.com host: 1.2.3.4 mailfrom: foo@e9.example.com result: permerror zonedata: mail.example.com: - A: 1.2.3.4 e1.example.com: - SPF: v=spf1 ip4:1.1.1.1 redirect=e1.example.com e2.example.com: - SPF: v=spf1 include:e3.example.com e3.example.com: - SPF: v=spf1 include:e2.example.com e4.example.com: - SPF: v=spf1 mx - MX: [0, mail.example.com] - MX: [1, mail.example.com] - MX: [2, mail.example.com] - MX: [3, mail.example.com] - MX: [4, mail.example.com] - MX: [5, mail.example.com] - MX: [6, mail.example.com] - MX: [7, mail.example.com] - MX: [8, mail.example.com] - MX: [9, mail.example.com] - MX: [10, e4.example.com] - A: 1.2.3.5 e5.example.com: - SPF: v=spf1 ptr - A: 1.2.3.5 5.3.2.1.in-addr.arpa: - PTR: e1.example.com. - PTR: e2.example.com. - PTR: e3.example.com. - PTR: e4.example.com. - PTR: example.com. - PTR: e6.example.com. - PTR: e7.example.com. - PTR: e8.example.com. - PTR: e9.example.com. - PTR: e10.example.com. - PTR: e5.example.com. e6.example.com: - SPF: v=spf1 a mx a mx a mx a mx a ptr ip4:1.2.3.4 -all e7.example.com: - SPF: v=spf1 a mx a mx a mx a mx a ptr a ip4:1.2.3.4 -all e8.example.com: - SPF: v=spf1 a include:inc.example.com ip4:1.2.3.4 mx -all inc.example.com: - SPF: v=spf1 a a a a a a a a e9.example.com: - SPF: v=spf1 a include:inc.example.com a ip4:1.2.3.4 -all