From 88a528586d9c9d6152b1436fae1a18bda8a0d596 Mon Sep 17 00:00:00 2001 From: Mikael Peigney Date: Sat, 27 Apr 2019 17:49:56 +0200 Subject: [PATCH] Fix OpenSPF tests, now that openspf.org is down --- tests/RFC4408Test.php | 3 +- tests/RFC7208Test.php | 3 +- tests/rfc4408-tests.LICENSE | 26 + tests/rfc4408-tests.yml | 2465 ++++++++++++++++++++++++++++++++ tests/rfc7208-tests.CHANGES | 125 ++ tests/rfc7208-tests.LICENSE | 27 + tests/rfc7208-tests.yml | 2624 +++++++++++++++++++++++++++++++++++ 7 files changed, 5271 insertions(+), 2 deletions(-) create mode 100644 tests/rfc4408-tests.LICENSE create mode 100644 tests/rfc4408-tests.yml create mode 100644 tests/rfc7208-tests.CHANGES create mode 100644 tests/rfc7208-tests.LICENSE create mode 100644 tests/rfc7208-tests.yml diff --git a/tests/RFC4408Test.php b/tests/RFC4408Test.php index 8a0653c..727c2de 100644 --- a/tests/RFC4408Test.php +++ b/tests/RFC4408Test.php @@ -3,6 +3,7 @@ * * @author Mikael Peigney */ + namespace Mika56\SPFCheck; class RFC4408Test extends OpenSPFTest @@ -26,7 +27,7 @@ public function testRFC4408($ipAddress, $domain, DNSRecordGetterInterface $dnsDa public function RFC4408DataProvider() { - $scenarios = file_get_contents('http://www.openspf.org/svn/project/test-suite/rfc4408-tests.yml'); + $scenarios = file_get_contents(__DIR__.DIRECTORY_SEPARATOR.'rfc4408-tests.yml'); return $this->loadTestCases($scenarios); } diff --git a/tests/RFC7208Test.php b/tests/RFC7208Test.php index b1d2a66..270cbcb 100644 --- a/tests/RFC7208Test.php +++ b/tests/RFC7208Test.php @@ -3,6 +3,7 @@ * * @author Mikael Peigney */ + namespace Mika56\SPFCheck; class RFC7208Test extends OpenSPFTest @@ -26,7 +27,7 @@ public function testRFC7208($ipAddress, $domain, DNSRecordGetterInterface $dnsDa public function RFC7208DataProvider() { - $scenarios = file_get_contents('http://www.openspf.org/svn/project/test-suite/rfc7208-tests.yml'); + $scenarios = file_get_contents(__DIR__.DIRECTORY_SEPARATOR.'rfc7208-tests.yml'); // Apparently there is a YML error in that file $scenarios = str_replace('Result is none if checking SPF records only', '>-'."\n".' Result is none if checking SPF records only', $scenarios); diff --git a/tests/rfc4408-tests.LICENSE b/tests/rfc4408-tests.LICENSE new file mode 100644 index 0000000..c6c810b --- /dev/null +++ b/tests/rfc4408-tests.LICENSE @@ -0,0 +1,26 @@ +The RFC 4408 test-suite (rfc4408-tests.yml) is +(C) 2006-2007 Stuart D Gathman + 2007 Julian Mehnle +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/rfc4408-tests.yml b/tests/rfc4408-tests.yml new file mode 100644 index 0000000..cdbebb4 --- /dev/null +++ b/tests/rfc4408-tests.yml @@ -0,0 +1,2465 @@ +# This is the openspf.org test suite (release 2009.10) based on RFC 4408. +# http://www.openspf.org/Test_Suite +# +# $Id$ +# vim:sw=2 sts=2 et +# +# See rfc4408-tests.CHANGES for a changelog. +# +# Contributors: +# Stuart D Gathman 90% of the tests +# Julian Mehnle proofread YAML syntax, spelling, formal schema +# Informal contributors (suggestions but no code): +# Craig Whitmore +# Frank Ellermann +# Scott Kitterman +# Wayne Schlitt +# Craig Whitmore +# Norman Maurer +# Mark Shewmaker +# Philip Gladstone +# +# While the test suite is designed for all types of implementations, it only +# needs to explicitly concern itself with SPF-only (type 99) and TXT-only +# implementations. This is because while an implementation may support both, +# it must use only one record type for a given query - see 4.5/5. If an +# implementation finds SPF (type 99) records and decides to use them, they +# override TXT, and it must ignore any TXT records. Note that an +# implementation may decide whether to use SPF records on a case by case basis. +# Maybe it looks TXT and SPF up in parallel and goes with the first result to +# come back. Or maybe one is cached already. Or maybe it chooses at random. +# Think of dual SPF/TXT implementations as a quantum superposition of SPF-only +# and TXT-only. It must collapse to one or the other on each observation to be +# compliant. +# +# The "Selecting records" test section is the only one concerned with weeding +# out (incorrect) mixed behaviour and checking for proper response to duplicate +# or conflicting records. Other sections rely on auto-magic duplication +# of SPF to TXT records (by test suite drivers) to test all implementation +# types with one specification. +# +--- +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 + helo-not-fqdn: + spec: 4.3/1 + helo: A2345678 + host: 1.2.3.5 + mailfrom: "" + result: none + helo-domain-literal: + spec: 4.3/1 + helo: "[1.2.3.5]" + host: 1.2.3.5 + mailfrom: "" + result: none + nolocalpart: + spec: 4.3/2 + helo: mail.example.net + host: 1.2.3.4 + mailfrom: '@example.net' + result: fail + explanation: postmaster + domain-literal: + spec: 4.3/1 + helo: OEMCOMPUTER + host: 1.2.3.5 + mailfrom: "foo@[1.2.3.5]" + result: none + non-ascii-policy: + description: >- + SPF policies are restricted to 7-bit ascii. + spec: 3.1.1/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@hosed.example.com" + result: permerror + non-ascii-mech: + description: >- + SPF policies are restricted to 7-bit ascii. + comment: >- + Checking a possibly different code path for non-ascii chars. + spec: 3.1.1/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@hosed2.example.com" + result: permerror + non-ascii-result: + description: >- + SPF policies are restricted to 7-bit ascii. + comment: >- + Checking yet another code path for non-ascii chars. + spec: 3.1.1/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@hosed3.example.com" + result: permerror + non-ascii-non-spf: + description: >- + Non-ascii content in non-SPF related records. + comment: >- + Non-SPF related TXT records are none of our business. (But what about SPF records?) + spec: 3.1.1/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@nothosed.example.com" + result: fail + explanation: DEFAULT + two-spaces: + description: >- + ABNF for term separation is one or more spaces, not just one. + spec: 4.6.1 + helo: hosed + host: 1.2.3.4 + mailfrom: "actually@fine.example.com" + result: fail +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 + hosed.example.com: + - SPF: "v=spf1 a:\xEF\xBB\xBFgarbage.example.net -all" + hosed2.example.com: + - SPF: "v=spf1 \x80a:example.net -all" + hosed3.example.com: + - SPF: "v=spf1 a:example.net \x96all" + nothosed.example.com: + - SPF: "v=spf1 a:example.net -all" + - SPF: "\x96" + fine.example.com: + - TXT: "v=spf1 a -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. + However, this situation cannot be reliably reproduced with live + DNS since cache and resolvers are allowed to combine identical + records. + spec: 4.5/6 + helo: mail.example1.com + host: 1.2.3.4 + mailfrom: foo@example7.com + result: [permerror, fail] + multispf2: + description: >- + Older implementations ignoring SPF-type records 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: + - SPF: v=spf1 -all + - SPF: 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: >- + target-name that is a valid domain-spec per RFC 4408 but an invalid + domain name per RFC 1035 (empty label) must be treated as non-existent. + comment: >- + An empty domain label, i.e. two successive dots, in a mechanism + target-name is valid domain-spec syntax, even though a DNS query cannot + be composed from it. The spec being unclear about it, this could either + be considered a syntax error, or, by analogy to 4.3/1 and 5/10/3, the + mechanism chould be treated as a no-match. + spec: [4.3/1, 5/10/3] + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@t10.example.com + result: [permerror, fail] + invalid-domain-long: + description: >- + target-name that is a valid domain-spec per RFC 4408 but an invalid + domain name per RFC 1035 (long label) must be treated as non-existent. + comment: >- + A domain label longer than 63 characters in a mechanism target-name is + valid domain-spec syntax, even though a DNS query cannot be composed + from it. The spec being unclear about it, this could either be + considered a syntax error, or, by analogy to 4.3/1 and 5/10/3, the + mechanism chould be treated as a no-match. + spec: [4.3/1, 5/10/3] + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@t11.example.com + result: [permerror,fail] + invalid-domain-long-via-macro: + description: >- + target-name that is a valid domain-spec per RFC 4408 but an invalid + domain name per RFC 1035 (long label) must be treated as non-existent. + comment: >- + A domain label longer than 63 characters that results from macro + expansion in a mechanism target-name is valid domain-spec syntax (and is + not even subject to syntax checking after macro expansion), even though + a DNS query cannot be composed from it. The spec being unclear about + it, this could either be considered a syntax error, or, by analogy to + 4.3/1 and 5/10/3, the mechanism chould be treated as a no-match. + spec: [4.3/1, 5/10/3] + helo: "%%%%%%%%%%%%%%%%%%%%%%" + host: 1.2.3.4 + mailfrom: foo@t12.example.com + result: [permerror,fail] +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:a123456789012345678901234567890123456789012345678901234567890123.example.com -all + t12.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 + 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 + 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 + 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 + domain. + spec: 5.5/5 + helo: mail.example.com + host: CAFE:BABE::1 + mailfrom: foo@e3.example.com + result: pass + ptr-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.5/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e5.example.com + result: permerror +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 + e5.example.com: + - SPF: "v=spf1 ptr:" +--- +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-dual-cidr-ip4-match: + 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@e8.example.com + result: pass + a-dual-cidr-ip4-err: + 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@e8e.example.com + result: permerror + a-dual-cidr-ip6-match: + 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: 2001:db8:1234::cafe:babe + mailfrom: foo@e8.example.com + result: pass + a-dual-cidr-ip4-default: + 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@e8b.example.com + result: fail + a-dual-cidr-ip6-default: + 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: 2001:db8:1234::cafe:babe + mailfrom: foo@e8a.example.com + result: fail + 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; + 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-ip6-dualstack: + description: >- + Simple IP6 Address match with dual stack. + spec: 5.3/3 + helo: mail.example.com + host: 1234::1 + mailfrom: foo@ipv6.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 octets not allowed in toplabel + spec: 8.1/2 + helo: mail.example.com + host: 1.2.3.5 + mailfrom: foo@e3.example.com + result: permerror + a-numeric: + description: >- + toplabel 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-toplabel: + description: >- + toplabel 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-dash-in-toplabel: + description: >- + toplabel may contain dashes + comment: >- + Going from the "toplabel" grammar definition, an implementation using + regular expressions in incrementally parsing SPF records might + erroneously try to match a TLD such as ".xn--zckzah" (cf. IDN TLDs!) to + '( *alphanum ALPHA *alphanum )' first before trying the alternative + '( 1*alphanum "-" *( alphanum / "-" ) alphanum )', essentially causing + a non-greedy, and thus, incomplete match. Make sure a greedy match is + performed! + spec: 8.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e14.example.com + result: pass + a-bad-toplabel: + description: >- + toplabel may not begin with a dash + spec: 8.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e12.example.com + result: permerror + a-only-toplabel: + description: >- + domain-spec may not consist of only a toplabel. + spec: 8.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e5a.example.com + result: permerror + a-only-toplabel-trailing-dot: + description: >- + domain-spec may not consist of only a toplabel. + comment: >- + "A trailing dot doesn't help." + spec: 8.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e5b.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-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.3/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e13.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 + ipv6.example.com: + - AAAA: 1234::1 + - A: 1.1.1.1 + - SPF: v=spf1 a -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 + e5a.example.com: + - SPF: v=spf1 a:museum + e5b.example.com: + - SPF: v=spf1 a:museum. + 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 + e8.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a/24//64 -all + e8e.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a/24/64 -all + e8a.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a/24 -all + e8b.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a//64 -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 + e13.example.com: + - SPF: "v=spf1 a:" + e14.example.com: + - SPF: "v=spf1 a:foo.example.xn--zckzah -all" + foo.example.xn--zckzah: + - A: 1.2.3.4 +--- +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-cidr: + description: >- + include = "include" ":" domain-spec + spec: 5.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e9.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 + include-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e8.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 + e8.example.com: + - SPF: "v=spf1 include: -all" + e9.example.com: + - SPF: "v=spf1 include:ip5.example.com/24 -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: >- + cidr4 doesn't apply to IP6 connections. + 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 + mx-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e13.example.com + result: permerror +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 + e13.example.com: + - SPF: "v=spf1 mx: -all" +--- +description: EXISTS mechanism syntax +tests: + exists-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.7/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e1.example.com + result: permerror + exists-implicit: + description: >- + exists = "exists" ":" domain-spec + spec: 5.7/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e2.example.com + result: permerror + exists-cidr: + description: >- + exists = "exists" ":" domain-spec + spec: 5.7/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e3.example.com + result: permerror + exists-ip4: + description: >- + mechanism matches if any DNS A RR exists + spec: 5.7/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e4.example.com + result: pass + exists-ip6: + description: >- + The lookup type is A even when the connection is ip6 + spec: 5.7/3 + helo: mail.example.com + host: CAFE:BABE::3 + mailfrom: foo@e4.example.com + result: pass + exists-ip6only: + description: >- + The lookup type is A even when the connection is ip6 + spec: 5.7/3 + helo: mail.example.com + host: CAFE:BABE::3 + mailfrom: foo@e5.example.com + result: fail + exists-dnserr: + description: >- + Result for DNS error is being clarified in spfbis + spec: 5.7/3 + helo: mail.example.com + host: CAFE:BABE::3 + mailfrom: foo@e6.example.com + result: [fail, temperror] +zonedata: + mail.example.com: + - A: 1.2.3.4 + mail6.example.com: + - AAAA: CAFE:BABE::4 + err.example.com: + - TIMEOUT + e1.example.com: + - SPF: "v=spf1 exists:" + e2.example.com: + - SPF: "v=spf1 exists" + e3.example.com: + - SPF: "v=spf1 exists:mail.example.com/24" + e4.example.com: + - SPF: "v=spf1 exists:mail.example.com" + e5.example.com: + - SPF: "v=spf1 exists:mail6.example.com -all" + e6.example.com: + - SPF: "v=spf1 exists:err.example.com -all" +--- +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 + redirect-syntax-error: + description: | + redirect = "redirect" "=" domain-spec + comment: >- + A literal application of the grammar causes modifier syntax + errors (except for macro syntax) to become unknown-modifier. + + modifier = explanation | redirect | unknown-modifier + + However, it is generally agreed, with precedent in other RFCs, + that unknown-modifier should not be "greedy", and should not + match known modifier names. There should have been explicit + prose to this effect, and some has been proposed as an erratum. + spec: 6.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e17.example.com + result: permerror + 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-no-txt: + description: | + Ignore exp if no 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@e22.example.com + result: fail + explanation: DEFAULT + exp-dns-error: + description: | + Ignore exp if DNS 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@e21.example.com + result: fail + explanation: DEFAULT + exp-empty-domain: + description: | + PermError if exp= domain-spec is empty. + comment: >- + Section 6.2/4 says, "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." However, "if domain-spec is empty" conflicts with the grammar + given for the exp modifier. This was reported as an erratum, and the + solution chosen was to report explicit "exp=" as PermError, but ignore + problems due to macro expansion, DNS, or invalid explanation string. + spec: 6.2/4 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e12.example.com + result: permerror + explanation-syntax-error: + description: | + Ignore exp if the explanation string has a 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-syntax-error: + description: | + explanation = "exp" "=" domain-spec + comment: >- + A literal application of the grammar causes modifier syntax + errors (except for macro syntax) to become unknown-modifier. + + modifier = explanation | redirect | unknown-modifier + + However, it is generally agreed, with precedent in other RFCs, + that unknown-modifier should not be "greedy", and should not + match known modifier names. There should have been explicit + prose to this effect, and some has been proposed as an erratum. + spec: 6.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e16.example.com + result: permerror + 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-empty-domain: + description: | + redirect = "redirect" "=" domain-spec + comment: >- + Unlike for exp, there is no instruction to override the permerror + for an empty domain-spec (which is invalid syntax). + spec: 6.2/4 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e18.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 + default-modifier-obsolete: + description: | + Unknown modifiers do not modify the RFC SPF result. + comment: >- + Some implementations may have a leftover default= modifier from + earlier drafts. + spec: 6/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e19.example.com + result: neutral + default-modifier-obsolete2: + description: | + Unknown modifiers do not modify the RFC SPF result. + comment: >- + Some implementations may have a leftover default= modifier from + earlier drafts. + spec: 6/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e20.example.com + result: neutral + non-ascii-exp: + description: >- + SPF explanation text is restricted to 7-bit ascii. + comment: >- + Checking a possibly different code path for non-ascii chars. + spec: 6.2/5 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@nonascii.example.com" + result: fail + explanation: DEFAULT + two-exp-records: + description: >- + Must ignore exp= if DNS returns more than one TXT record. + spec: 6.2/4 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@tworecs.example.com" + result: fail + explanation: DEFAULT +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 + e16.example.com: + - SPF: v=spf1 exp=-all + e17.example.com: + - SPF: v=spf1 redirect=-all ?all + e18.example.com: + - SPF: v=spf1 ?all redirect= + e19.example.com: + - SPF: v=spf1 default=pass + e20.example.com: + - SPF: "v=spf1 default=+" + e21.example.com: + - SPF: v=spf1 exp=e21msg.example.com -all + e21msg.example.com: + - TIMEOUT + e22.example.com: + - SPF: v=spf1 exp=mail.example.com -all + nonascii.example.com: + - SPF: v=spf1 exp=badexp.example.com -all + badexp.example.com: + - TXT: "\xEF\xBB\xBFExplanation" + tworecs.example.com: + - SPF: v=spf1 exp=twoexp.example.com -all + twoexp.example.com: + - TXT: "one" + - TXT: "two" +--- +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 + invalid-embedded-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@e1e.example.com + result: permerror + invalid-trailing-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@e1t.example.com + result: permerror + macro-mania-in-domain: + description: >- + macro-encoded percents (%%), spaces (%_), and URL-percent-encoded + spaces (%-) + spec: 8.1/3, 8.1/4 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: test@e1a.example.com + result: pass + 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 is ipv4, or "ip6" if 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 is ipv4, or "ip6" if 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 + 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 + comment: >- + If a subdomain of the 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 + 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 + comment: >- + If a subdomain of the 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 + comment: >- + If a subdomain of the 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 + hello-domain-literal: + spec: 8.1/2 + description: |- + h = HELO/EHLO domain, but HELO is a domain literal + comment: >- + Domain-spec must end in either a macro, or a valid toplabel. + It is not correct to check syntax after macro expansion. + helo: "[192.168.218.40]" + 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. This is a complex + policy testing several points at once. + helo: OEMCOMPUTER + host: 1.2.3.4 + mailfrom: test@e10.example.com + result: fail + macro-reverse-split-on-dash: + spec: [8.1/15, 8.1/16, 8.1/17, 8.1/18] + description: >- + Macro value transformation (splitting on arbitrary characters, reversal, + number of right-hand parts to use) + helo: mail.example.com + host: 1.2.3.4 + mailfrom: philip-gladstone-test@e11.example.com + result: pass + macro-multiple-delimiters: + spec: [8.1/15, 8.1/16] + description: |- + Multiple delimiters may be specified in a macro expression. + macro-expand = ( "%{" macro-letter transformers *delimiter "}" ) + / "%%" / "%_" / "%-" + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo-bar+zip+quux@e12.example.com + result: pass +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 + e1e.example.com: + - SPF: v=spf1 exists:foo%(ir).sbl.example.com ?all + e1t.example.com: + - SPF: v=spf1 exists:foo%.sbl.example.com ?all + e1a.example.com: + - SPF: "v=spf1 a:macro%%percent%_%_space%-url-space.example.com -all" + "macro%percent space%20url-space.example.com": + - A: 1.2.3.4 + 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 + e11.example.com: + - SPF: v=spf1 exists:%{i}.%{l2r-}.user.%{d2} + 1.2.3.4.gladstone.philip.user.example.com: + - A: 127.0.0.2 + e12.example.com: + - SPF: v=spf1 exists:%{l2r+-}.user.%{d2} + bar.foo.user.example.com: + - A: 127.0.0.2 +--- +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, permerror] + 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] + false-a-limit: + description: >- + unlike MX, PTR, there is no RR limit for A + comment: >- + There seems to be a tendency for developers to want to limit + A RRs in addition to MX and PTR. These are IPs, not usable for + 3rd party DoS attacks, and hence need no low limit. + spec: 10.1/7 + helo: mail.example.com + host: 1.2.3.12 + mailfrom: foo@e10.example.com + result: 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 + - A: 1.2.3.6 + e2.example.com: + - SPF: v=spf1 include:e3.example.com + - A: 1.2.3.7 + e3.example.com: + - SPF: v=spf1 include:e2.example.com + - A: 1.2.3.8 + 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 + - A: 1.2.3.8 + - MX: [10, e6.example.com] + e7.example.com: + - SPF: v=spf1 a mx a mx a mx a mx a ptr a ip4:1.2.3.4 -all + - A: 1.2.3.20 + e8.example.com: + - SPF: v=spf1 a include:inc.example.com ip4:1.2.3.4 mx -all + - A: 1.2.3.4 + inc.example.com: + - SPF: v=spf1 a a a a a a a a + - A: 1.2.3.10 + e9.example.com: + - SPF: v=spf1 a include:inc.example.com a ip4:1.2.3.4 -all + - A: 1.2.3.21 + e10.example.com: + - SPF: v=spf1 a -all + - A: 1.2.3.1 + - A: 1.2.3.2 + - A: 1.2.3.3 + - A: 1.2.3.4 + - A: 1.2.3.5 + - A: 1.2.3.6 + - A: 1.2.3.7 + - A: 1.2.3.8 + - A: 1.2.3.9 + - A: 1.2.3.10 + - A: 1.2.3.11 + - A: 1.2.3.12 diff --git a/tests/rfc7208-tests.CHANGES b/tests/rfc7208-tests.CHANGES new file mode 100644 index 0000000..1b86914 --- /dev/null +++ b/tests/rfc7208-tests.CHANGES @@ -0,0 +1,125 @@ +# Legend: +# --- = A new release +# ! = Added a test case or otherwise tightened a requirement, possibly +# causing implementations to become incompliant with the current +# test-suite release +# - = Removed a test case or otherwise relaxed a requirement +# * = Fixed a bug, or made a minor improvement + +--- 2014.04 (UNRELEASED) + ! Updates for RFC 7208 (4408bis) + ! Updated multiple tests not to consider type SPF records under mixed + conditions - Note: due to the way the test suite is structured, many + records are still labled SPF internally, but for test functions, it + doesn't matter externally. + - Removed "invalid-domain-empty-label", "invalid-domain-long", and + "invalid-domain-long-via-macro". Since RFC 7208 explicitly describes + the results for these conditions as undefined, there's no point in + testing for a particular result. + ! Modified multiple tests to remove ambiguous results for cases that were + ambiguous in RFC 4408, but have been clarified in RFC 7208. + ! Changed "mx-limit" test to produce permerror result per changes in RFC + 7208 + ! Added "invalid-trailing-macro-char" and "invalid-embedded-macro-char" + tests from Stuart on pyspf trunk + +--- 2009.10 (2009-10-31 20:00) + + ! Added test case: + ! "macro-multiple-delimiters": + Multiple delimiters in a macro expression must be supported. + * Fixed "multitxt2" test case failing with SPF-type-only implementations. + Tolerate a "None" result to accomodate those. + +--- 2008.08 (2008-08-17 16:00) + + ! "invalid-domain-empty-label", "invalid-domain-long", + "invalid-domain-long-via-macro" test cases: + A that is a valid domain-spec per RFC 4408 but an invalid + domain name per RFC 1035 (two successive dots or labels longer than 63 + characters) must be treated either as a "PermError" or as non-existent and + thus a no-match. (In particular, those cases can never cause a TempError + because the error is guaranteed to reoccur given the same input data. + This applies likewise to RFC-1035-invalid s that are the + result of macro expansion.) Refined descriptions and comments to that + end. + The no-match behavior can be inferred by analogy from 4.3/1 and 5/10/3. + The spec reference to 8.1/2 is bogus because the formal grammar does not + preclude such invalid domain names. + ! The "exp= without domain-spec" controversy has been resolved; it must be a + syntax error. Tightened "exp-empty-domain" test case accordingly. + ! Added test cases: + ! "a-dash-in-toplabel": + may contain dashes. Implementations matching + non-greedily may get that wrong. + ! "a-only-toplabel", "a-only-toplabel-trailing-dot": + Both "a:museum" and "a:museum." are invalid syntax. A bare top-label is + insufficient, with or without a trailing dot. + ! "exp-no-txt", "exp-dns-error": + Clearly, "exp=" referring to a non-existent TXT RR, or the look-up + resulting in a DNS error, must cause the "exp=" modifier to be ignored per + 6.2/4. + ! "macro-mania-in-domain": + Test macro-encoded percents (%%), spaces (%_), and URL-percent-encoded + spaces (%20) in . + ! "macro-reverse-split-on-dash": + Test transformation of macro expansion results: splitting on non-dot + separator characters, reversal, number of right-hand parts to use. + - Removed "a-valid-syntax-but-unqueryable" test case. It is redundant to + the "invalid-domain-empty-label" test case. + - Relaxed "multispf1" test case: + If performed via live DNS (yes, some people do that!), this test may be + ineffective as DNS resolvers may combine multiple identical RRs. Thus, + tolerate the test failing in this manner. + * Adjusted "multispf2" test case: + Avoid combination of multiple identical RRs by using different + capitalization in intentionally duplicate RRs. + * Renamed test cases: + a-numeric-top-label -> a-numeric-toplabel + a-bad-toplab -> a-bad-toplabel + +--- 2007.05 (2007-05-30 21:00) + + - "exp-empty-domain" test case is subject to controversy. "exp=" with an + empty domain-spec may be considered a syntax error or not, thus both "Fail" + and "PermError" results are acceptable for now. + * Renamed the old "exp-syntax-error" test case to "explanation-syntax-error" + to indicate that it refers to syntax errors in the explanation string, not + in the "exp=" modifier. + ! Added test cases: + ! "exp-syntax-error", "redirect-syntax-error": Syntax errors in "exp=" and + "redirect=" must be treated as such. + ! "a-empty-domain", "mx-empty-domain", "ptr-empty-domain", + "include-empty-domain", "redirect-empty-domain": "a:", "mx:", "ptr:", + "include:", and "redirect=" with an empty domain-spec are syntax errors. + ! "include-cidr": "include:/" is a syntax error. + ! "helo-not-fqdn", "helo-domain-literal", "domain-literal": A non-FQDN + HELO or MAIL FROM must result in a "None" result. + ! "hello-domain-literal": Macro expansion results must not be checked for + syntax errors, but must rather be treated as non-matches if nonsensical. + ! "false-a-limit": There is no limit for the number of A records resulting + from an "a:"-induced lookup, and no such limit must be imposed. + ! "default-modifier-obsolete(2)": The "default=" modifier used in very old + spec drafts must be ignored by RFC 4408 implementations. + +--- 2007.01 (2007-01-14 05:19) + + ! Added test cases: + ! "nospftxttimeout": If no SPF-type record is present and the TXT lookup + times out, the result must either be "None" (preferred) or "TempError". + ! "exp-multiple-txt", "exp-syntax-error": Multiple explanation string TXT + records and syntax errors in explanation strings must be ignored (i.e., + specifically "PermError" must NOT be returned). + ! "exp-empty-domain": "exp=" with an empty domain-spec is to be tolerated, + i.e., ignored, too. (This is under debate.) + ! "exp-twice", "redirect-twice": Added. Multiple "exp=" or "redirect=" + modifiers are prohibited. + * "Macro expansion rules" scenario: Fixed a bug that caused TXT-only + implementations to fail several tests incorrectly due to a real TXT record + blocking the automatic synthesis of TXT records from the corresponding + SPF-type records. + +--- 2006.11 (initial release) (2006-11-27 21:27) + +# $Id$ +# vim:tw=79 sts=2 sw=2 diff --git a/tests/rfc7208-tests.LICENSE b/tests/rfc7208-tests.LICENSE new file mode 100644 index 0000000..589f9ec --- /dev/null +++ b/tests/rfc7208-tests.LICENSE @@ -0,0 +1,27 @@ +The RFC 7208 test-suite (rfc7208-tests.yml) is +(C) 2006-2008 Stuart D Gathman + 2007-2008 Julian Mehnle + 2014 Scott Kitterman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/rfc7208-tests.yml b/tests/rfc7208-tests.yml new file mode 100644 index 0000000..c65a0f3 --- /dev/null +++ b/tests/rfc7208-tests.yml @@ -0,0 +1,2624 @@ +# This is the openspf.org test suite (release 2014.04) based on RFC 7208. +# http://www.openspf.org/Test_Suite +# +# $Id$ +# vim:sw=2 sts=2 et +# +# See rfc7208-tests.CHANGES for a changelog. +# +# Contributors: +# Stuart D Gathman 90% of the tests +# Julian Mehnle some tests, proofread YAML syntax, formal schema +# Frank Ellermann +# Scott Kitterman +# Wayne Schlitt +# Craig Whitmore +# Norman Maurer +# Mark Shewmaker +# Philip Gladstone +# +# For RFC 4408, the test suite was designed for use with SPF (type 99) and TXT +# implementations. In RFC 7208, use of type SPF has been removed. +# +# The "Selecting records" test section is the only one concerned with weeding +# out (incorrect) queries for type SPF of any kind or proper response to +# duplicate or conflicting records. Other sections rely on auto-magic +# duplication of SPF to TXT records (by test suite drivers) to test all +# implementation types with one specification. +# +# All new tests should use Documentation IPs for both IP4 and IP6. I was +# stupid to use 1.2.3.4 - that is a real global IP (although it doesn't ping). +# +--- +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 + helo-not-fqdn: + spec: 4.3/1 + helo: A2345678 + host: 1.2.3.5 + mailfrom: "" + result: none + helo-domain-literal: + spec: 4.3/1 + helo: "[1.2.3.5]" + host: 1.2.3.5 + mailfrom: "" + result: none + nolocalpart: + spec: 4.3/2 + helo: mail.example.net + host: 1.2.3.4 + mailfrom: '@example.net' + result: fail + explanation: postmaster + domain-literal: + spec: 4.3/1 + helo: OEMCOMPUTER + host: 1.2.3.5 + mailfrom: "foo@[1.2.3.5]" + result: none + non-ascii-policy: + description: >- + SPF policies are restricted to 7-bit ascii. + spec: 3.1/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@hosed.example.com" + result: permerror + non-ascii-mech: + description: >- + SPF policies are restricted to 7-bit ascii. + comment: >- + Checking a possibly different code path for non-ascii chars. + spec: 3.1/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@hosed2.example.com" + result: permerror + non-ascii-result: + description: >- + SPF policies are restricted to 7-bit ascii. + comment: >- + Checking yet another code path for non-ascii chars. + spec: 3.1/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@hosed3.example.com" + result: permerror + non-ascii-non-spf: + description: >- + Non-ascii content in non-SPF related records. + comment: >- + Non-SPF related TXT records are none of our business. + spec: 4.5/1 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@nothosed.example.com" + result: fail + explanation: DEFAULT + control-char-policy: + description: >- + Mechanisms are separated by spaces only, not any control char. + spec: 4.6.1/2 + helo: hosed + host: 192.0.2.3 + mailfrom: "foobar@ctrl.example.com" + result: permerror + two-spaces: + description: >- + ABNF for term separation is one or more spaces, not just one. + spec: 4.6.1 + helo: hosed + host: 1.2.3.4 + mailfrom: "actually@fine.example.com" + result: fail + trailing-space: + description: >- + ABNF for record does allow trailing spaces. + comment: >- + record = version terms *SP + spec: 4.5/2 + helo: hosed + host: 192.0.2.5 + mailfrom: "silly@trail.example.com" + result: fail + null-text: + description: >- + Multiple strings are glued together with no separator. + comment: >- + Note that null text (no strings) is illegal, but SPF should not crash. + spec: 3.3 + helo: hosed + host: 192.0.2.5 + mailfrom: "silly@null.example.com" + result: pass +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 + hosed.example.com: + - SPF: "v=spf1 a:\xEF\xBB\xBFgarbage.example.net -all" + hosed2.example.com: + - SPF: "v=spf1 \x80a:example.net -all" + hosed3.example.com: + - SPF: "v=spf1 a:example.net \x96all" + nothosed.example.com: + - SPF: "v=spf1 a:example.net -all" + - SPF: "\x96" + ctrl.example.com: + - SPF: "v=spf1 a:ctrl.example.com\x0dptr -all" + - A: 192.0.2.3 + fine.example.com: + - SPF: "v=spf1 a -all" + trail.example.com: + - SPF: "v=spf1 a -all " + null.example.com: + - SPF: [ "v=spf1 ip4:", "192.0.2.5 -all" ] + - SPF: [ ] +--- +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 + (which you should not be doing). + spec: 4.4/1 + helo: mail.example.net + host: 1.2.3.4 + mailfrom: foo@txtonly.example.net + result: fail + 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: none + spftimeout: + description: >- + TXT record present, but SPF lookup times out. + Result is temperror if checking SPF records only. Fortunately, + we don't do type SPF anymore. + 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 + 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: 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 + 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 no longer used. + spec: 4.5/5 + helo: mail.example1.com + host: 1.2.3.4 + mailfrom: foo@example4.com + result: fail + multitxt1: + description: >- + Implementations should give permerror/unknown because of + the conflicting TXT records. + spec: 4.5/5 + helo: mail.example1.com + host: 1.2.3.4 + mailfrom: foo@example5.com + result: 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. + However, this situation cannot be reliably reproduced with live + DNS since cache and resolvers are allowed to combine identical + records. + spec: 4.5/6 + helo: mail.example1.com + host: 1.2.3.4 + mailfrom: foo@example7.com + result: [permerror, fail] + multispf2: + description: >- + Ignoring SPF-type records will give pass because there is a (single) + TXT record. + spec: 4.5/6 + helo: mail.example1.com + host: 1.2.3.4 + mailfrom: foo@example8.com + result: 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: + - SPF: v=spf1 -all + - SPF: 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: 7.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@t9.example.com + result: permerror + invalid-domain-empty-label: + description: >- + target-name that is a valid domain-spec per RFC 4408 and RFC 7208 but an + invalid domain name per RFC 1035 (empty label) should be treated as + non-existent. + comment: >- + An empty domain label, i.e. two successive dots, in a mechanism + target-name is valid domain-spec syntax (perhaps formed from a macro + expansion), even though a DNS query cannot be composed from it. The spec + being unclear about it, this could either be considered a syntax error, + or, by analogy to 4.3/1 and 5/10/3, the mechanism could be treated as a + no-match. RFC 7208 failed to agree on which result to use, and declares + the situation undefined. The preferred test result is therefore a matter + of opinion. + spec: 4.3/1, 4.8/5, 5/10/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@t10.example.com + result: [fail, permerror] + invalid-domain-long: + description: >- + target-name that is a valid domain-spec per RFC 4408 and RFC 7208 but an + invalid domain name per RFC 1035 (long label) must be treated as + non-existent. + comment: >- + A domain label longer than 63 characters in a mechanism target-name is + valid domain-spec syntax (perhaps formed from a macro expansion), even + though a DNS query cannot be composed from it. The spec being unclear + about it, this could either be considered a syntax error, or, by analogy + to 4.3/1 and 5/10/3, the mechanism could be treated as a no-match. RFC + 7208 failed to agree on which result to use, and declares the situation + undefined. The preferred test result is therefore a matter of opinion. + spec: 4.3/1, 4.8/5, 5/10/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@t11.example.com + result: [fail,permerror] + invalid-domain-long-via-macro: + description: >- + target-name that is a valid domain-spec per RFC 4408 and RFC 7208 but an + invalid domain name per RFC 1035 (long label) must be treated as + non-existent. + comment: >- + A domain label longer than 63 characters that results from macro + expansion in a mechanism target-name is valid domain-spec syntax (and is + not even subject to syntax checking after macro expansion), even though + a DNS query cannot be composed from it. The spec being unclear about + it, this could either be considered a syntax error, or, by analogy to + 4.3/1 and 5/10/3, the mechanism could be treated as a no-match. RFC 7208 + failed to agree on which result to use, and declares the situation + undefined. The preferred test result is therefore a matter of opinion. + spec: 4.3/1, 4.8/5, 5/10/3 + helo: "%%%%%%%%%%%%%%%%%%%%%%" + host: 1.2.3.4 + mailfrom: foo@t12.example.com + result: [fail,permerror] +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:a123456789012345678901234567890123456789012345678901234567890123.example.com -all + t12.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 + 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 + 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 + 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 + domain. + spec: 5.5/5 + helo: mail.example.com + host: CAFE:BABE::1 + mailfrom: foo@e3.example.com + result: pass + ptr-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.5/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e5.example.com + result: permerror + ptr-case-change: + description: >- + arpa domain is case insensitive. + comment: >- + Some DNS servers have random case in the domain part of returned + answers, especially for PTR records. For example, a query for + 1.2.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.F.0.0.4.F.1.1.1.0.1.0.A.2.ip6.arpa + may return + 1.2.6.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.F.0.0.4.F.1.1.1.0.1.0.a.2.ip6.arpa + spec: 5.5/2 + helo: mail.example.com + host: 2001:db8::1 + mailfrom: bar@e6.example.com + result: pass + ptr-cname-loop: + description: >- + a PTR with CNAME loop and inconsistent case in domain. + comment: >- + RFC 1034 3.6.2/11 says, CNAME chains should be followed and CNAME loops + signalled as an error. RFC 7208 5.5/7 says, If a DNS error occurs while + doing an A RR lookup, then that domain name is skipped and the search + continues. + spec: 5.5/7 + helo: loop.example.com + host: 192.0.2.4 + mailfrom: postmaster@loop.example.com + result: neutral +zonedata: + mail.example.com: + - A: 1.2.3.4 + - AAAA: 2001:db8::1 + 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 + 1.0.0.0.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.arpa: + - PTR: mail.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 + e5.example.com: + - SPF: "v=spf1 ptr:" + e6.example.com: + - SPF: "v=spf1 ptr:example.Com -all" + loop.example.com: + - SPF: "v=spf1 ptr" + 4.2.0.192.in-addr.arpa: + - PTR: "loop4.example.com." + loop4.example.com: + - CNAME: "CNAME.example.com." + cname.example.com: + - CNAME: "CNAME.example.com." + +--- +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-dual-cidr-ip4-match: + 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@e8.example.com + result: pass + a-dual-cidr-ip4-err: + 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@e8e.example.com + result: permerror + a-dual-cidr-ip6-match: + 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: 2001:db8:1234::cafe:babe + mailfrom: foo@e8.example.com + result: pass + a-dual-cidr-ip4-default: + 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@e8b.example.com + result: fail + a-dual-cidr-ip6-default: + 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: 2001:db8:1234::cafe:babe + mailfrom: foo@e8a.example.com + result: fail + 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; + a ':' may appear in domain-spec, but not in top-label + spec: 7.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-ip6-dualstack: + description: >- + Simple IP6 Address match with dual stack. + spec: 5.3/3 + helo: mail.example.com + host: 1234::1 + mailfrom: foo@ipv6.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 octets not allowed in toplabel + spec: 7.1/2 + helo: mail.example.com + host: 1.2.3.5 + mailfrom: foo@e3.example.com + result: permerror + a-numeric: + description: >- + toplabel 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: 7.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e4.example.com + result: permerror + a-numeric-toplabel: + description: >- + toplabel may not be all numeric + spec: 7.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e5.example.com + result: permerror + a-dash-in-toplabel: + description: >- + toplabel may contain dashes + comment: >- + Going from the "toplabel" grammar definition, an implementation using + regular expressions in incrementally parsing SPF records might + erroneously try to match a TLD such as ".xn--zckzah" (cf. IDN TLDs!) to + '( *alphanum ALPHA *alphanum )' first before trying the alternative + '( 1*alphanum "-" *( alphanum / "-" ) alphanum )', essentially causing + a non-greedy, and thus, incomplete match. Make sure a greedy match is + performed! + spec: 7.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e14.example.com + result: pass + a-bad-toplabel: + description: >- + toplabel may not begin with a dash + spec: 7.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e12.example.com + result: permerror + a-only-toplabel: + description: >- + domain-spec may not consist of only a toplabel. + spec: 7.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e5a.example.com + result: permerror + a-only-toplabel-trailing-dot: + description: >- + domain-spec may not consist of only a toplabel. + comment: >- + "A trailing dot doesn't help." + spec: 7.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e5b.example.com + result: permerror + a-colon-domain: + description: >- + domain-spec may contain any visible char except % + spec: 7.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: 7.1/2 + helo: mail.example.com + host: ::FFFF:1.2.3.4 + mailfrom: foo@e11.example.com + result: pass + a-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.3/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e13.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 + ipv6.example.com: + - AAAA: 1234::1 + - A: 1.1.1.1 + - SPF: v=spf1 a -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 + e5a.example.com: + - SPF: v=spf1 a:museum + e5b.example.com: + - SPF: v=spf1 a:museum. + 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 + e8.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a/24//64 -all + e8e.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a/24/64 -all + e8a.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a/24 -all + e8b.example.com: + - A: 1.2.3.5 + - AAAA: 2001:db8:1234::dead:beef + - SPF: v=spf1 a//64 -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 + e13.example.com: + - SPF: "v=spf1 a:" + e14.example.com: + - SPF: "v=spf1 a:foo.example.xn--zckzah -all" + foo.example.xn--zckzah: + - A: 1.2.3.4 +--- +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-cidr: + description: >- + include = "include" ":" domain-spec + spec: 5.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e9.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 + include-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e8.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 + e8.example.com: + - SPF: "v=spf1 include: -all" + e9.example.com: + - SPF: "v=spf1 include:ip5.example.com/24 -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: 7.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: >- + cidr4 doesn't apply to IP6 connections. + comment: >- + The IP6 CIDR starts with a double slash. + 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: 7.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: 7.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: 7.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: 7.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: 7.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 + mx-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e13.example.com + result: permerror +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 + e13.example.com: + - SPF: "v=spf1 mx: -all" +--- +description: EXISTS mechanism syntax +tests: + exists-empty-domain: + description: >- + domain-spec cannot be empty. + spec: 5.7/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e1.example.com + result: permerror + exists-implicit: + description: >- + exists = "exists" ":" domain-spec + spec: 5.7/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e2.example.com + result: permerror + exists-cidr: + description: >- + exists = "exists" ":" domain-spec + spec: 5.7/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e3.example.com + result: permerror + exists-ip4: + description: >- + mechanism matches if any DNS A RR exists + spec: 5.7/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e4.example.com + result: pass + exists-ip6: + description: >- + The lookup type is A even when the connection is ip6 + spec: 5.7/3 + helo: mail.example.com + host: CAFE:BABE::3 + mailfrom: foo@e4.example.com + result: pass + exists-ip6only: + description: >- + The lookup type is A even when the connection is ip6 + spec: 5.7/3 + helo: mail.example.com + host: CAFE:BABE::3 + mailfrom: foo@e5.example.com + result: fail + exists-dnserr: + description: >- + Result for DNS error clarified in RFC7208: MTAs or other processors + SHOULD impose a limit on the maximum amount of elapsed time to evaluate + check_host(). Such a limit SHOULD allow at least 20 seconds. If such + a limit is exceeded, the result of authorization SHOULD be "temperror". + spec: 5/8 + helo: mail.example.com + host: CAFE:BABE::3 + mailfrom: foo@e6.example.com + result: temperror +zonedata: + mail.example.com: + - A: 1.2.3.4 + mail6.example.com: + - AAAA: CAFE:BABE::4 + err.example.com: + - TIMEOUT + e1.example.com: + - SPF: "v=spf1 exists:" + e2.example.com: + - SPF: "v=spf1 exists" + e3.example.com: + - SPF: "v=spf1 exists:mail.example.com/24" + e4.example.com: + - SPF: "v=spf1 exists:mail.example.com" + e5.example.com: + - SPF: "v=spf1 exists:mail6.example.com -all" + e6.example.com: + - SPF: "v=spf1 exists:err.example.com -all" +--- +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 RFC4408 was silent on ip4 CIDR > 32 or ip6 CIDR > 128, but RFC7208 + is explicit. Invalid CIDR is prohibited. + 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 was controversy over IPv4 mapped connections. RFC7208 clearly + states IPv4 mapped addresses only match ip4: mechanisms. + spec: 5/9/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e2.example.com + result: neutral + 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 was controversy over ip4 mapped connections. RFC7208 clearly + requires such connections to be considered as ip4 only. + spec: 5/9/2 + helo: mail.example.com + host: ::FFFF:1.2.3.4 + mailfrom: foo@e2.example.com + result: neutral + 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 + redirect-syntax-error: + description: | + redirect = "redirect" "=" domain-spec + comment: >- + A literal application of the grammar causes modifier syntax + errors (except for macro syntax) to become unknown-modifier. + + modifier = explanation | redirect | unknown-modifier + + However, it is generally agreed, with precedent in other RFCs, + that unknown-modifier should not be "greedy", and should not + match known modifier names. There should have been explicit + prose to this effect, and some has been proposed as an erratum. + spec: 6.1/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e17.example.com + result: permerror + 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: 7.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-no-txt: + description: | + Ignore exp if no 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@e22.example.com + result: fail + explanation: DEFAULT + exp-dns-error: + description: | + Ignore exp if DNS 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@e21.example.com + result: fail + explanation: DEFAULT + exp-empty-domain: + description: | + PermError if exp= domain-spec is empty. + comment: >- + Section 6.2/4 says, "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." However, "if domain-spec is empty" conflicts with the grammar + given for the exp modifier. This was reported as an erratum, and the + solution chosen was to report explicit "exp=" as PermError, but ignore + problems due to macro expansion, DNS, or invalid explanation string. + spec: 6.2/4 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e12.example.com + result: permerror + explanation-syntax-error: + description: | + Ignore exp if the explanation string has a 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-syntax-error: + description: | + explanation = "exp" "=" domain-spec + comment: >- + A literal application of the grammar causes modifier syntax + errors (except for macro syntax) to become unknown-modifier. + + modifier = explanation | redirect | unknown-modifier + + However, it is generally agreed, with precedent in other RFCs, + that unknown-modifier should not be "greedy", and should not + match known modifier names. There should have been explicit + prose to this effect, and some has been proposed as an erratum. + spec: 6.2/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e16.example.com + result: permerror + 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-empty-domain: + description: | + redirect = "redirect" "=" domain-spec + comment: >- + Unlike for exp, there is no instruction to override the permerror + for an empty domain-spec (which is invalid syntax). + spec: 6.2/4 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e18.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 + default-modifier-obsolete: + description: | + Unknown modifiers do not modify the RFC SPF result. + comment: >- + Some implementations may have a leftover default= modifier from + earlier drafts. + spec: 6/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e19.example.com + result: neutral + default-modifier-obsolete2: + description: | + Unknown modifiers do not modify the RFC SPF result. + comment: >- + Some implementations may have a leftover default= modifier from + earlier drafts. + spec: 6/3 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e20.example.com + result: neutral + non-ascii-exp: + description: >- + SPF explanation text is restricted to 7-bit ascii. + comment: >- + Checking a possibly different code path for non-ascii chars. + spec: 6.2/5 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@nonascii.example.com" + result: fail + explanation: DEFAULT + two-exp-records: + description: >- + Must ignore exp= if DNS returns more than one TXT record. + spec: 6.2/4 + helo: hosed + host: 1.2.3.4 + mailfrom: "foobar@tworecs.example.com" + result: fail + explanation: DEFAULT + exp-void: + description: | + exp=nxdomain.tld + comment: >- + Non-existent exp= domains MUST NOT count against the void lookup limit. + Implementations should lookup any exp record at most once after + computing the result. + spec: 4.6.4/1, 6/2 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e23.example.com + result: fail + redirect-implicit: + description: | + redirect changes implicit domain + spec: 6.1/4 + helo: e24.example.com + host: 192.0.2.2 + mailfrom: bar@e24.example.com + result: pass +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 + e16.example.com: + - SPF: v=spf1 exp=-all + e17.example.com: + - SPF: v=spf1 redirect=-all ?all + e18.example.com: + - SPF: v=spf1 ?all redirect= + e19.example.com: + - SPF: v=spf1 default=pass + e20.example.com: + - SPF: "v=spf1 default=+" + e21.example.com: + - SPF: v=spf1 exp=e21msg.example.com -all + e21msg.example.com: + - TIMEOUT + e22.example.com: + - SPF: v=spf1 exp=mail.example.com -all + nonascii.example.com: + - SPF: v=spf1 exp=badexp.example.com -all + badexp.example.com: + - TXT: "\xEF\xBB\xBFExplanation" + tworecs.example.com: + - SPF: v=spf1 exp=twoexp.example.com -all + twoexp.example.com: + - TXT: "one" + - TXT: "two" + e23.example.com: + - SPF: v=spf1 a:erehwon.example.com a:foobar.com exp=nxdomain.com -all + e24.example.com: + - SPF: v=spf1 redirect=testimplicit.example.com + - A: 192.0.2.1 + testimplicit.example.com: + - SPF: v=spf1 a -all + - A: 192.0.2.2 +--- +description: Macro expansion rules +tests: + trailing-dot-domain: + spec: 7.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: 7.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: 7.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: 7.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 + invalid-embedded-macro-char: + spec: 7.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@e1e.example.com + result: permerror + invalid-trailing-macro-char: + spec: 7.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@e1t.example.com + result: permerror + macro-mania-in-domain: + description: >- + macro-encoded percents (%%), spaces (%_), and URL-percent-encoded + spaces (%-) + spec: 7.1/3, 7.1/4 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: test@e1a.example.com + result: pass + exp-txt-macro-char: + spec: 7.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: 7.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: 7.1/6 + description: |- + v = the string "in-addr" if is ipv4, or "ip6" if 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: 7.1/6 + description: |- + v = the string "in-addr" if is ipv4, or "ip6" if 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: 7.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: 7.1/22 + description: |- + p = the validated domain name of + 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: 7.1/22 + description: |- + p = the validated domain name of + comment: >- + If a subdomain of the 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: 7.1/22 + description: |- + p = the validated domain name of + 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: 7.1/22 + description: |- + p = the validated domain name of + comment: >- + If a subdomain of the 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: 7.1/22 + description: |- + p = the validated domain name of + comment: >- + If a subdomain of the 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: 7.1/26 + description: >- + Uppercased macros expand exactly as their lowercased equivalents, + and are then URL escaped. All chars not in the unreserved set + MUST be escaped. + comment: | + unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + helo: msgbas2x.cos.example.com + host: 192.168.218.42 + mailfrom: ~jack&jill=up-a_b3.c@e8.example.com + result: fail + explanation: http://example.com/why.html?l=~jack%26jill%3Dup-a_b3.c + hello-macro: + spec: 7.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: 7.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 + hello-domain-literal: + spec: 7.1/2 + description: |- + h = HELO/EHLO domain, but HELO is a domain literal + comment: >- + Domain-spec must end in either a macro, or a valid toplabel. + It is not correct to check syntax after macro expansion. + helo: "[192.168.218.40]" + host: 192.168.218.40 + mailfrom: test@e9.example.com + result: fail + require-valid-helo: + spec: 7.1/6 + description: >- + Example of requiring valid helo in sender policy. This is a complex + policy testing several points at once. + helo: OEMCOMPUTER + host: 1.2.3.4 + mailfrom: test@e10.example.com + result: fail + macro-reverse-split-on-dash: + spec: 7.1/15, 7.1/16, 7.1/17, 7.1/18 + description: >- + Macro value transformation (splitting on arbitrary characters, reversal, + number of right-hand parts to use) + helo: mail.example.com + host: 1.2.3.4 + mailfrom: philip-gladstone-test@e11.example.com + result: pass + macro-multiple-delimiters: + spec: 7.1/15, 7.1/16 + description: |- + Multiple delimiters may be specified in a macro expression. + macro-expand = ( "%{" macro-letter transformers *delimiter "}" ) + / "%%" / "%_" / "%-" + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo-bar+zip+quux@e12.example.com + result: pass +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 + e1e.example.com: + - SPF: v=spf1 exists:foo%(ir).sbl.example.com ?all + e1t.example.com: + - SPF: v=spf1 exists:foo%.sbl.example.com ?all + e1a.example.com: + - SPF: "v=spf1 a:macro%%percent%_%_space%-url-space.example.com -all" + "macro%percent space%20url-space.example.com": + - A: 1.2.3.4 + 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 + e11.example.com: + - SPF: v=spf1 exists:%{i}.%{l2r-}.user.%{d2} + 1.2.3.4.gladstone.philip.user.example.com: + - A: 127.0.0.2 + e12.example.com: + - SPF: v=spf1 exists:%{l2r+-}.user.%{d2} + bar.foo.user.example.com: + - A: 127.0.0.2 +--- +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: 4.6.4/1 + 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: 4.6.4/1 + 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 + with RFC4408. For RFC7208 the ambiguity was resolved in favor of + producing a permerror result. + spec: 4.6.4/2 + helo: mail.example.com + host: 1.2.3.5 + mailfrom: foo@e4.example.com + result: permerror + 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: 4.6.4/3 + helo: mail.example.com + host: 1.2.3.5 + mailfrom: foo@e5.example.com + result: [neutral, pass] + false-a-limit: + description: >- + unlike MX, PTR, there is no RR limit for A + comment: >- + There seems to be a tendency for developers to want to limit + A RRs in addition to MX and PTR. These are IPs, not usable for + 3rd party DoS attacks, and hence need no low limit. + spec: 4.6.4 + helo: mail.example.com + host: 1.2.3.12 + mailfrom: foo@e10.example.com + result: 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: 4.6.4/1 + 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: 4.6.4/1 + 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. In RFC7208, processing limits are + part of syntax checking (4.6). + spec: 4.6.4/1 + 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: 4.6.4/1 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e9.example.com + result: permerror + void-at-limit: + description: >- + SPF implementations SHOULD limit "void lookups" to two. An + implementation MAY choose to make such a limit configurable. + In this case, a default of two is RECOMMENDED. + comment: >- + This is a new check in RFC7208, but it's been implemented in Mail::SPF + for years with no issues. + spec: 4.6.4/7 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e12.example.com + result: neutral + void-over-limit: + description: >- + SPF implementations SHOULD limit "void lookups" to two. An + implementation MAY choose to make such a limit configurable. + In this case, a default of two is RECOMMENDED. + spec: 4.6.4/7 + helo: mail.example.com + host: 1.2.3.4 + mailfrom: foo@e11.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 + - A: 1.2.3.6 + e2.example.com: + - SPF: v=spf1 include:e3.example.com + - A: 1.2.3.7 + e3.example.com: + - SPF: v=spf1 include:e2.example.com + - A: 1.2.3.8 + 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 + - A: 1.2.3.8 + - MX: [10, e6.example.com] + e7.example.com: + - SPF: v=spf1 a mx a mx a mx a mx a ptr a ip4:1.2.3.4 -all + - A: 1.2.3.20 + e8.example.com: + - SPF: v=spf1 a include:inc.example.com ip4:1.2.3.4 mx -all + - A: 1.2.3.4 + inc.example.com: + - SPF: v=spf1 a a a a a a a a + - A: 1.2.3.10 + e9.example.com: + - SPF: v=spf1 a include:inc.example.com a ip4:1.2.3.4 -all + - A: 1.2.3.21 + e10.example.com: + - SPF: v=spf1 a -all + - A: 1.2.3.1 + - A: 1.2.3.2 + - A: 1.2.3.3 + - A: 1.2.3.4 + - A: 1.2.3.5 + - A: 1.2.3.6 + - A: 1.2.3.7 + - A: 1.2.3.8 + - A: 1.2.3.9 + - A: 1.2.3.10 + - A: 1.2.3.11 + - A: 1.2.3.12 + e11.example.com: + - TXT: v=spf1 a:err.example.com a:err1.example.com a:err2.example.com ?all + e12.example.com: + - TXT: v=spf1 a:err.example.com a:err1.example.com ?all +--- +description: Test cases from implementation bugs +tests: + bytes-bug: + description: >- + Bytes vs str bug from pyspf. + comment: >- + Pyspf failed with strict=2 only. Other implementations may ignore + the strict parameter. + spec: 5.4/4 + helo: example.org + host: 2001:db8:ff0:100::2 + mailfrom: test@example.org + result: pass + strict: 2 +zonedata: + example.org: + - SPF: "v=spf1 mx redirect=_spf.example.com" + - MX: [10,smtp.example.org] + - MX: [10,smtp1.example.com] + smtp.example.org: + - A: 198.51.100.2 + - AAAA: 2001:db8:ff0:100::3 + smtp1.example.com: + - A: 192.0.2.26 + - AAAA: 2001:db8:ff0:200::2 + 2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.F.F.0.8.B.D.0.1.0.0.2.ip6.arpa: + - PTR: smtp6-v.fe.example.org + smtp6-v.fe.example.org: + - AAAA: 2001:db8:ff0:100::2 + _spf.example.com: + - SPF: "v=spf1 ptr:fe.example.org ptr:sgp.example.com exp=_expspf.example.org -all" + _expspf.example.org: + - TXT: "Sender domain not allowed from this host. Please see http://www.openspf.org/Why?s=mfrom&id=%{S}&ip=%{C}&r=%{R}"