# Simple tests, used for debugging the testing infrastructure; and some
# additional tests for situations not covered by the other files.
---
description: Simple successes
tests:
test1:
description: Straightforward sucesss
helo: example.net
mailfrom: "foobar@example.net"
host: 1.2.3.4
result: pass
test2:
description: HELO is set, but expected to be ignored
helo: blargh
mailfrom: "foobar@example.net"
host: 1.2.3.4
result: pass
zonedata:
example.net:
- SPF: v=spf1 +all
---
description: Simple failures
tests:
test1:
description: Straightforward failure
helo: example.net
mailfrom: "foobar@example.net"
host: 1.2.3.4
result: fail
test2:
description: HELO is set, but expected to be ignored
helo: blargh
mailfrom: "foobar@example.net"
host: 1.2.3.4
result: fail
zonedata:
example.net:
- SPF: v=spf1 -all
---
description: Regexp edge cases for "a", "mx" and "ptr"
tests:
ipv6-with-a:
description: |
Send from an ip6 address that has "a:" inside. If we incorrectly parse
the "ip6" as "a", this results in a permerror since the host doesn't
match.
mailfrom: "foobar@a1.net"
host: a::a
result: pass
bad-a-mask:
description: |
If we incorrectly parse the "ip6" as "a", this results in a permerror
due to an invalid mask.
mailfrom: "foobar@a2.net"
host: 2001:db8:ff0:100::2
result: softfail
exp-contains-mx:
description: exp= contains mx:, which should be ignored.
mailfrom: "foobar@expmx.net"
host: 1.2.3.4
result: softfail
exp-contains-ptr:
description: |
exp= contains ptr:, which should be ignored.
Note this test case involves unusual/invalid domains.
mailfrom: "foobar@expptr.net"
host: 1.2.3.4
result: softfail
zonedata:
a1.net:
- SPF: v=spf1 ip6:a::a ~all
a2.net:
- SPF: v=spf1 ip6:1a0a:cccc::/29 ~all
expmx.net:
- SPF: v=spf1 exp=mx:mymx.com ~all
- MX: [10, mymx.com]
mymx.com:
- A: 1.2.3.4
expptr.net:
- SPF: v=spf1 exp=ptr:lalala.com ~all
4.3.2.1.in-addr.arpa:
- PTR: ptr:lalala.com.
ptr:lalala.com:
- A: 1.2.3.4
---
description: Error on PTR forward resolution
tests:
broken-ptr-forward:
description: |
Check that if during 'ptr' forward resolution we get an error, we skip
the domain (and consequently fail the check).
mailfrom: "foo@domain.net"
host: 1.2.3.4
result: softfail
zonedata:
domain.net:
- SPF: v=spf1 ptr:lalala.com ~all
4.3.2.1.in-addr.arpa:
- PTR: lalala.com
lalala.com:
- TIMEOUT: true
---
description: Permanent error on 'exists' resolution
tests:
exists-perm-error:
description: |
Check that if, during an 'exists' forward resolution we get an error, we
return temperror.
mailfrom: "foo@domain.net"
host: 1.2.3.4
result: temperror
zonedata:
domain.net:
- SPF: v=spf1 exists:lalala.com ~all
lalala.com:
- SERVFAIL: true
---
description: Resolve H macros correctly
tests:
resolve-h-macros:
description: |
Check that '%{h}' macros are correctly resolved to the HELO/EHLO and not
the sender domain.
mailfrom: "foo@domain.net"
helo: holahola
host: 1.2.3.4
result: pass
zonedata:
domain.net:
- SPF: v=spf1 exists:%{h}.com ~all
holahola.com:
- A: 127.0.0.2
---
description: Only include the first 10 PTR results
tests:
only-first-10-ptr:
description: |
Check that if during 'ptr' forward resolution we only consider the first
10 names, and ignore the rest.
mailfrom: "foo@domain.net"
host: 1.2.3.4
result: softfail
zonedata:
domain.net:
- A: 127.0.0.1
- SPF: v=spf1 ptr ~all
4.3.2.1.in-addr.arpa:
- PTR: dom01.com
- PTR: dom02.com
- PTR: dom03.com
- PTR: dom04.com
- PTR: dom05.com
- PTR: dom06.com
- PTR: dom07.com
- PTR: dom08.com
- PTR: dom09.com
- PTR: dom10.com
# Entries below here should get dropped. They would make it pass.
- PTR: domain.net
dom01.com:
- A: 127.0.0.1
dom02.com:
- A: 127.0.0.2
dom03.com:
- A: 127.0.0.3
dom04.com:
- A: 127.0.0.4
dom05.com:
- A: 127.0.0.5
dom06.com:
- A: 127.0.0.6
dom07.com:
- A: 127.0.0.7
dom08.com:
- A: 127.0.0.8
dom09.com:
- A: 127.0.0.9
dom10.com:
- A: 127.0.0.10
---
description: Resolution limits
tests:
resolution-with-10-lookups:
description: |
Check that a resolution with precisely 10 lookups (the default limit)
works fine.
mailfrom: "foo@okay.com"
host: 1.2.3.4
result: pass
resolution-with-11-lookups:
description: |
Check that a resolution with precisely 11 lookups (over default limit)
fails as expected.
mailfrom: "foo@bad.com"
host: 1.2.3.4
result: permerror
zonedata:
okay.com:
- SPF: v=spf1 include:d11 include:d12 include:d13
include:d14 include:d15 -all
bad.com:
- SPF: v=spf1 include:d00
include:d11 include:d12 include:d13
include:d14 include:d15 -all
d00:
- SPF: v=spf1 -all
d11:
- SPF: v=spf1 include:d21 ~all
d12:
- SPF: v=spf1 include:d22 ~all
d13:
- SPF: v=spf1 include:d23 ~all
d14:
- SPF: v=spf1 include:d24 ~all
d15:
- SPF: v=spf1 include:d25 ~all
d21:
- SPF: v=spf1 -all
d22:
- SPF: v=spf1 -all
d23:
- SPF: v=spf1 -all
d24:
- SPF: v=spf1 -all
d25:
- SPF: v=spf1 all
---
description: Lookup limit boundary
tests:
lookup-limit-at-10:
description: |
Check that a resolution with exactly 10 terms that cause DNS lookups is
allowed, and that the terms after them are still evaluated.
mailfrom: "foo@at10"
host: 1.2.3.4
result: pass
lookup-limit-at-10-matches:
description: |
Check that the 10th term that causes a DNS lookup is evaluated, and can
produce a match. It is the last term, so nothing after it can alter the
result.
mailfrom: "foo@at10match"
host: 1.2.3.4
result: pass
lookup-limit-over-a:
description: |
Check that the 11th "a" term is not evaluated, even though it would
match. The limit must be checked before doing the lookup: checking it
only when moving on to the next term allows 11 lookups instead of 10,
and lets the 11th one decide the result.
mailfrom: "foo@over-a"
host: 1.2.3.4
result: permerror
lookup-limit-over-mx:
description: |
Same as lookup-limit-over-a, for the "mx" mechanism.
mailfrom: "foo@over-mx"
host: 1.2.3.4
result: permerror
lookup-limit-over-exists:
description: |
Same as lookup-limit-over-a, for the "exists" mechanism.
mailfrom: "foo@over-exists"
host: 1.2.3.4
result: permerror
lookup-limit-over-ptr:
description: |
Same as lookup-limit-over-a, for the "ptr" mechanism.
mailfrom: "foo@over-ptr"
host: 1.2.3.4
result: permerror
lookup-limit-over-no-match:
description: |
Check that the 11th term causes a permerror even when it does not match
and there is no term after it. Otherwise the record is evaluated to the
end, and the excess lookup goes unnoticed.
mailfrom: "foo@over-nomatch"
host: 1.2.3.4
result: permerror
zonedata:
at10:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
ip4:1.2.3.4 -all
at10match:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch
a:amatch
over-a:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:amatch
over-mx:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
mx:mxmatch
over-exists:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
exists:amatch
over-ptr:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
ptr:ptrmatch.com
over-nomatch:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch
nomatch:
- A: 10.0.0.1
amatch:
- A: 1.2.3.4
mxmatch:
- MX: [10, amatch]
ptrmatch.com:
- A: 1.2.3.4
4.3.2.1.in-addr.arpa:
- PTR: ptrmatch.com
---
description: Void lookup limit
tests:
void-limit-at-2:
description: |
Check that 2 void lookups (the default limit) are allowed, and that the
terms after them are still evaluated.
mailfrom: "foo@atvoid2"
host: 1.2.3.4
result: pass
void-limit-over-a:
description: |
Check that exceeding the void lookup limit on an "a" term causes a
permerror, even when it is the last term. The limit must be checked
right after the lookup: checking it only when moving on to the next
term lets the excess go unnoticed if there is no next term.
mailfrom: "foo@overvoid-a"
host: 1.2.3.4
result: permerror
void-limit-over-mx:
description: |
Same as void-limit-over-a, for the "mx" mechanism. The domain exists but
has no MX records, which is a void lookup.
mailfrom: "foo@overvoid-mx"
host: 1.2.3.4
result: permerror
void-limit-over-exists:
description: |
Same as void-limit-over-a, for the "exists" mechanism.
mailfrom: "foo@overvoid-exists"
host: 1.2.3.4
result: permerror
void-limit-over-ptr:
description: |
Same as void-limit-over-a, for the "ptr" mechanism. The address has no
PTR records, which is a void lookup.
mailfrom: "foo@overvoid-ptr"
host: 1.2.3.4
result: permerror
void-limit-over-in-include:
description: |
Check that exceeding the void lookup limit inside an "include" makes the
whole evaluation return permerror, instead of just making the include
not match. Note the include is the last term, so nothing after it can
turn the excess into a permerror on its own.
mailfrom: "foo@overvoid-include"
host: 1.2.3.4
result: permerror
zonedata:
atvoid2:
- SPF: v=spf1 a:void1 a:void2 ip4:1.2.3.4 -all
overvoid-a:
- SPF: v=spf1 a:void1 a:void2 a:void3
overvoid-mx:
- SPF: v=spf1 a:void1 a:void2 mx:onlya
overvoid-exists:
- SPF: v=spf1 a:void1 a:void2 exists:void3
overvoid-ptr:
- SPF: v=spf1 a:void1 a:void2 ptr
overvoid-include:
- SPF: v=spf1 include:overvoid-a
onlya:
- A: 1.2.3.4
---
description: MX resolution limits
tests:
mx-resolution-10-terms:
description: |
Check that a resolution with 10 "mx" terms works, because it's within
the limit. Each term will resolve to multiple records, but those
shouldn't be individually counted (there's a limit of 10 MX records per
MX lookup, but that's tested separately).
mailfrom: "foo@mx10"
host: 1.2.3.4
result: pass
mx-resolution-11-terms:
description: |
Check that a resolution with 11 "mx" terms, causes a permerror due to
exceeding lookup limits.
mailfrom: "foo@mx11"
host: 1.2.3.4
result: permerror
zonedata:
mx10:
- SPF: v=spf1 mx:domain mx:domain mx:domain mx:domain mx:domain
mx:domain mx:domain mx:domain mx:domain mx:domain
all
mx11:
- SPF: v=spf1 mx:domain mx:domain mx:domain mx:domain mx:domain
mx:domain mx:domain mx:domain mx:domain mx:domain
mx:domain all
domain:
- MX: [1, blah1]
- MX: [2, blah2]
- MX: [3, blah3]
---
description: PTR resolution limits
tests:
ptr-resolution-10-terms:
description: |
Check that a resolution with 10 "ptr" terms works, because it's within
the limit, and that the terms after them are still evaluated. The
address is only resolved once and the names reused, but each term still
counts against the limit.
mailfrom: "foo@ptr10"
host: 1.2.3.4
result: pass
ptr-resolution-11-terms:
description: |
Check that a resolution with 11 "ptr" terms causes a permerror due to
exceeding lookup limits, even though only one DNS lookup is performed.
mailfrom: "foo@ptr11"
host: 1.2.3.4
result: permerror
ptr-resolution-mixed-terms:
description: |
Check that "ptr" terms are counted individually when mixed with other
terms that cause DNS lookups: here the second "ptr" is the 11th term,
and takes the resolution over the limit.
mailfrom: "foo@ptrmixed"
host: 1.2.3.4
result: permerror
zonedata:
ptr10:
- SPF: v=spf1 ptr:nomatch.com ptr:nomatch.com ptr:nomatch.com
ptr:nomatch.com ptr:nomatch.com ptr:nomatch.com
ptr:nomatch.com ptr:nomatch.com ptr:nomatch.com
ptr:nomatch.com ip4:1.2.3.4 -all
ptr11:
- SPF: v=spf1 ptr:nomatch.com ptr:nomatch.com ptr:nomatch.com
ptr:nomatch.com ptr:nomatch.com ptr:nomatch.com
ptr:nomatch.com ptr:nomatch.com ptr:nomatch.com
ptr:nomatch.com ptr:nomatch.com -all
ptrmixed:
- SPF: v=spf1 a:nomatch a:nomatch a:nomatch a:nomatch a:nomatch
a:nomatch a:nomatch a:nomatch a:nomatch
ptr:nomatch.com ptr:nomatch.com -all
nomatch:
- A: 10.0.0.1
ptrhost.com:
- A: 1.2.3.4
4.3.2.1.in-addr.arpa:
- PTR: ptrhost.com
---
description: NXDOMAIN tests
tests:
one-mx-not-found:
description: |
Check that if one of the MXs is not found, we continue evaluating the
rest.
mailfrom: "foo@d00"
host: 1.2.3.4
result: pass
all-mx-not-found:
description: |
Check that if none of the MXs is not found, we continue evaluating the
rest of the terms.
mailfrom: "foo@d01"
host: 1.2.3.4
result: pass
domain-not-found:
description: |
Check if the top-level domain is not found (which is different that it
existing but having no TXT record).
mailfrom: "foo@doesnotexist"
host: 1.2.3.4
result: none
include-not-found:
description: |
For include, if the recursive check returns None, it should make the
evaluation return PermError.
mailfrom: "foo@d02"
host: 1.2.3.4
result: permerror
redirect-not-found:
description: |
For redirect, if the recursive check returns None, it should make the
evaluation return PermError.
mailfrom: "foo@d03"
host: 1.2.3.4
result: permerror
zonedata:
d00:
- SPF: v=spf1 mx -all
- MX: [10, "doesnotexist"]
- MX: [20, "sender"]
d01:
- SPF: v=spf1 mx ip4:1.2.3.4 -all
- MX: [10, "doesnotexist"]
- MX: [20, "doesnotexist"]
d02:
- SPF: v=spf1 include:doesnotexist all
d03:
- SPF: v=spf1 redirect=doesnotexist
sender:
- A: 1.2.3.4
---
description: PTR forward confirmation
tests:
ptr-forward-confirmed:
description: |
A PTR name that resolves back to the IP being checked is validated, and
matches.
mailfrom: "foo@good.com"
host: 1.2.3.4
result: pass
ptr-resolves-elsewhere:
description: |
A PTR name that resolves, but to a different address, must NOT be
validated: RFC 7208 section 5.5 requires <ip> to be among the addresses
returned for the name, not just that the name resolves to something.
mailfrom: "foo@bad.com"
host: 1.2.3.4
result: fail
ptr-resolves-elsewhere-v6:
description: |
As above, but the name resolves to an address of a different family.
mailfrom: "foo@badv6.com"
host: 1.2.3.4
result: fail
ptr-one-of-many:
description: |
A name with several addresses is validated if any one of them is the IP
being checked.
mailfrom: "foo@many.com"
host: 1.2.3.4
result: pass
zonedata:
good.com:
- SPF: v=spf1 ptr:good.com -all
- A: 1.2.3.4
bad.com:
- SPF: v=spf1 ptr:bad.com -all
- A: 9.9.9.9
badv6.com:
- SPF: v=spf1 ptr:badv6.com -all
- AAAA: 2001:db8::1
many.com:
- SPF: v=spf1 ptr:many.com -all
- A: 10.0.0.1
- A: 1.2.3.4
- A: 10.0.0.2
4.3.2.1.in-addr.arpa:
- PTR: good.com
- PTR: bad.com
- PTR: badv6.com
- PTR: many.com
---
description: PTR domain matching
tests:
ptr-exact:
description: |
The validated name is the target domain itself, so it matches.
mailfrom: "foo@exact.com"
host: 1.2.3.4
result: pass
ptr-subdomain:
description: |
The validated name is a subdomain of the target domain, so it matches.
mailfrom: "foo@sub.com"
host: 1.2.3.5
result: pass
ptr-not-subdomain:
description: |
The validated name ends with the target domain as a string, but not at
a label boundary, so it must NOT match: RFC 7208 section 5.5 requires
the name to be the target domain, or a subdomain of it.
mailfrom: "foo@notsub.com"
host: 1.2.3.6
result: fail
ptr-target-trailing-dot:
description: |
A target domain written with a trailing dot matches the same names as
one written without it.
mailfrom: "foo@dot.com"
host: 1.2.3.7
result: pass
zonedata:
exact.com:
- SPF: v=spf1 ptr:exactname.com -all
exactname.com:
- A: 1.2.3.4
4.3.2.1.in-addr.arpa:
- PTR: exactname.com
sub.com:
- SPF: v=spf1 ptr:subname.com -all
deep.sub.subname.com:
- A: 1.2.3.5
5.3.2.1.in-addr.arpa:
- PTR: deep.sub.subname.com
notsub.com:
- SPF: v=spf1 ptr:example.com -all
notexample.com:
- A: 1.2.3.6
6.3.2.1.in-addr.arpa:
- PTR: notexample.com
dot.com:
- SPF: v=spf1 ptr:dotname.com. -all
dotname.com:
- A: 1.2.3.7
7.3.2.1.in-addr.arpa:
- PTR: dotname.com
---
description: Macro context in recursive evaluations
tests:
include-macro-d:
description: |
The %{d} macro inside an included record expands to the included
domain, not the original one: RFC 7208 section 5.2 says check_host()
is evaluated with the include's target as <domain>, and section 7.2
defines d = <domain>.
If %{d} was (incorrectly) expanded to the original domain, the exists
would not match, and the check would fail.
spec: 5.2/3, 7.2
mailfrom: "foo@senderd.net"
host: 1.2.3.4
result: pass
include-macro-sender:
description: |
The %{l} and %{o} macros inside an included record still refer to the
original <sender>: RFC 7208 section 5.2 says the <sender> argument
remains the same in the recursive evaluation, and section 7.3 notes
that %{s}, %{l} and %{o} remain the same during recursive and chained
evaluations.
If they were (incorrectly) taken from the included domain, the exists
would not match, and the check would fail.
spec: 5.2/3, 7.3/23
mailfrom: "foo@senders.net"
host: 1.2.3.4
result: pass
redirect-macro-d:
description: |
Like include-macro-d, but for "redirect": RFC 7208 section 6.1 says
check_host() is evaluated with the redirect target as <domain>.
spec: 6.1/3, 7.2
mailfrom: "foo@rsenderd.net"
host: 1.2.3.4
result: pass
redirect-macro-sender:
description: |
Like include-macro-sender, but for "redirect": RFC 7208 section 6.1
says the <sender> argument remains the same after a redirect.
spec: 6.1/3, 7.3/23
mailfrom: "foo@rsenders.net"
host: 1.2.3.4
result: pass
zonedata:
senderd.net:
- SPF: v=spf1 include:incd.net -all
incd.net:
- SPF: v=spf1 exists:d.%{d} -all
d.incd.net:
- A: 127.0.0.2
senders.net:
- SPF: v=spf1 include:incs.net -all
incs.net:
- SPF: v=spf1 exists:%{l}.%{o}.sendermacro.net -all
foo.senders.net.sendermacro.net:
- A: 127.0.0.2
rsenderd.net:
- SPF: v=spf1 redirect=rincd.net
rincd.net:
- SPF: v=spf1 exists:d.%{d} -all
d.rincd.net:
- A: 127.0.0.2
rsenders.net:
- SPF: v=spf1 redirect=rincs.net
rincs.net:
- SPF: v=spf1 exists:%{l}.%{o}.rsendermacro.net -all
foo.rsenders.net.rsendermacro.net:
- A: 127.0.0.2
---
description: Case-insensitivity of mechanism, modifier, and version names
tests:
upper-version:
description: |
The version section is case-insensitive: RFC 7208 section 12 says
literal text strings in the ABNF are case-insensitive, and the record
grammar defines version = "v=spf1".
spec: 4.5/3, 12/2
mailfrom: "foo@verup.net"
host: 1.2.3.4
result: fail
upper-ip4-all:
description: |
Mechanism names are case-insensitive: RFC 7208 section 12 says "mx"
matches "mx", "MX", "mX" and "Mx"; the same applies to every
mechanism name.
spec: 4.6.1/9, 12/2
mailfrom: "foo@ip4up.net"
host: 1.2.3.4
result: pass
upper-all:
description: |
"ALL" with a qualifier is the "all" mechanism.
spec: 4.6.1/9, 12/2
mailfrom: "foo@allup.net"
host: 1.2.3.4
result: softfail
upper-a:
description: |
A bare uppercase "A" is the "a" mechanism.
spec: 4.6.1/9, 12/2
mailfrom: "foo@aup.net"
host: 1.2.3.4
result: pass
upper-mx:
description: |
A bare uppercase "MX" is the "mx" mechanism.
spec: 4.6.1/9, 12/2
mailfrom: "foo@mxup.net"
host: 1.2.3.4
result: pass
upper-include-exists:
description: |
Uppercase "INCLUDE:" and "EXISTS:" (with their domain argument) work
like their lowercase versions.
spec: 4.6.1/9, 12/2
mailfrom: "foo@incup.net"
host: 1.2.3.4
result: pass
upper-ptr:
description: |
A bare uppercase "PTR" is the "ptr" mechanism.
spec: 4.6.1/9, 12/2
mailfrom: "foo@ptrup.net"
host: 1.2.3.4
result: pass
upper-redirect:
description: |
Modifier names are case-insensitive too: "REDIRECT=" is the
"redirect" modifier, not an unknown modifier to be ignored.
spec: 4.6.1/9, 12/2
mailfrom: "foo@redirup.net"
host: 1.2.3.4
result: pass
redirect-mixed-case-duplicate:
description: |
Duplicate modifier detection must be case-insensitive as well:
"redirect" appearing twice is a permerror (RFC 7208 section 6), even
if spelled with different case each time.
spec: 6/2, 12/2
mailfrom: "foo@redirdup.net"
host: 1.2.3.4
result: permerror
zonedata:
verup.net:
- SPF: V=SPF1 -ALL
ip4up.net:
- SPF: v=spf1 IP4:1.2.3.4 -ALL
allup.net:
- SPF: v=spf1 ~ALL
aup.net:
- SPF: v=spf1 A -all
- A: 1.2.3.4
mxup.net:
- SPF: v=spf1 MX -all
- MX: [10, mxhost.mxup.net]
mxhost.mxup.net:
- A: 1.2.3.4
incup.net:
- SPF: v=spf1 INCLUDE:incup2.net -all
incup2.net:
- SPF: v=spf1 EXISTS:existup.net -all
existup.net:
- A: 127.0.0.2
ptrup.net:
- SPF: v=spf1 PTR -all
- A: 1.2.3.4
4.3.2.1.in-addr.arpa:
- PTR: ptrup.net
redirup.net:
- SPF: v=spf1 REDIRECT=redirtarget.net
redirdup.net:
- SPF: v=spf1 redirect=redirtarget.net ReDirEct=redirtarget.net
redirtarget.net:
- SPF: v=spf1 ip4:1.2.3.4 -all
---
description: Macro expansion details
tests:
macro-digit-exceeds-parts:
description: |
RFC 7208 section 7.3: if the DIGIT transformer specifies more parts
than are available, all the available parts are used; and
implementations MUST support a DIGIT value of at least 127.
spec: 7.3/20
mailfrom: "foo@macrod.net"
host: 1.2.3.4
result: pass
macro-digit-zero:
description: |
RFC 7208 section 7.3: if a DIGIT is specified, the value MUST be
nonzero; a zero value is a syntax error.
spec: 7.3/20
mailfrom: "foo@zerod.net"
host: 1.2.3.4
result: permerror
macro-v-ip4:
description: |
The %{v} macro expands to "in-addr" for IPv4 connections.
The existing rfc7208-tests.yml tests for %{v} only observe it through
the explanation string, which our test runner ignores; this one is
observable through the result.
spec: 7.2/3
mailfrom: "foo@macrov.net"
host: 1.2.3.4
result: pass
macro-v-ip6:
description: |
The %{v} macro expands to "ip6" for IPv6 connections, and %{ir}
expands to the reversed dot-format (nibble) address. This is the
expansion example from RFC 7208 section 7.4.
spec: 7.2/3, 7.3/21, 7.4
mailfrom: "foo@macrov.net"
host: 2001:db8::cb01
result: pass
macro-upper-escape:
description: |
Uppercase macros expand as their lowercase equivalents and are then
URL escaped; characters not in the "unreserved" set MUST be escaped.
Here %{S} expands the sender and escapes the "@" as "%40".
The existing test for this (upper-macro) only observes it through the
explanation string, which our test runner ignores.
spec: 7.3/26
mailfrom: "bar@escape.net"
host: 1.2.3.4
result: pass
macro-p-unknown:
description: |
RFC 7208 section 7.3: if there are no validated domain names for the
%{p} macro (here, <ip> has no PTR records), the string "unknown" is
used.
comment: |
We always expand %{p} to "unknown" without doing the PTR lookup, which
happens to be the RFC-mandated result for this test. Note that the
RFC also says the lookup counts against the processing limits
(section 4.6.4), which we consequently don't do; that is not tested
here.
spec: 7.3/22
mailfrom: "foo@pmacro.net"
host: 1.2.3.4
result: pass
macro-postmaster-empty-local:
description: |
RFC 7208 section 4.3: if the <sender> has no local-part, the string
"postmaster" is substituted for the local-part; %{l} must then expand
to "postmaster".
The existing test for this (nolocalpart) only observes it through the
explanation string, which our test runner ignores.
spec: 4.3/2, 7.3/23
mailfrom: "@nolocal.net"
host: 1.2.3.4
result: pass
skip: We don't substitute "postmaster" for an empty local-part.
macro-postmaster-null-sender:
description: |
Like macro-postmaster-empty-local, but with a completely null sender:
per RFC 7208 section 2.4, the "MAIL FROM" identity is then taken to
be postmaster@<HELO identity>, so %{l} must expand to "postmaster".
spec: 2.4/2, 4.3/2
helo: hel.nullsender.net
mailfrom: ""
host: 1.2.3.4
result: pass
skip: We don't substitute "postmaster" for an empty local-part.
macro-empty-parts:
description: |
RFC 7208 section 7.3: no special treatment is given to consecutive
delimiters in macro values, so the list of parts might contain empty
strings, which are then rejoined with dots. A local-part of "a..b"
must therefore expand (via %{l}) to "a..b", not "a.b".
The expanded name has an empty label, which cannot be queried; per
section 4.8 handling of such domains is undefined (no-match and
permerror are both acceptable), but expanding to "a.b" and matching
is not.
spec: 7.3/17, 4.8/5
mailfrom: "a..b@empties.net"
host: 1.2.3.4
result: [fail, permerror]
skip: We drop empty parts when splitting macro values, giving pass here.
macro-truncation:
description: |
RFC 7208 section 7.3: when the result of macro expansion is used in a
domain name query and exceeds 253 characters, the left side is
truncated to fit, by removing successive domain labels (and their
following dots) until the total length does not exceed 253
characters.
Here %{h} is a 255-character name; after appending ".trunc.net" the
first label has to be removed, and the resulting 201-character name
does exist.
spec: 7.3/25
helo: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
mailfrom: "foo@trunc.net"
host: 1.2.3.4
result: pass
skip: We don't truncate expanded domains to 253 characters.
zonedata:
macrod.net:
- SPF: v=spf1 exists:x.%{d127} -all
x.macrod.net:
- A: 127.0.0.2
zerod.net:
- SPF: v=spf1 exists:%{d0}.zero.net -all
macrov.net:
- SPF: v=spf1 exists:%{ir}.%{v}.macrov.net -all
4.3.2.1.in-addr.macrov.net:
- A: 127.0.0.2
1.0.b.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.macrov.net:
- A: 127.0.0.2
escape.net:
- SPF: v=spf1 exists:%{S}.esc.escape.net -all
bar%40escape.net.esc.escape.net:
- A: 127.0.0.2
pmacro.net:
- SPF: v=spf1 exists:%{p}.pmacro.net -all
unknown.pmacro.net:
- A: 127.0.0.2
nolocal.net:
- SPF: v=spf1 exists:%{l}.l.nolocal.net -all
postmaster.l.nolocal.net:
- A: 127.0.0.2
hel.nullsender.net:
- SPF: v=spf1 exists:%{l}.l.nullsender.net -all
postmaster.l.nullsender.net:
- A: 127.0.0.2
empties.net:
- SPF: v=spf1 exists:%{l}.l.empties.net -all
a.b.l.empties.net:
- A: 127.0.0.2
trunc.net:
- SPF: v=spf1 exists:%{h}.trunc.net -all
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd.trunc.net:
- A: 127.0.0.2
---
description: MX address record limit boundary
tests:
mx-at-10-records:
description: |
RFC 7208 section 4.6.4: the evaluation of each "MX" record MUST NOT
result in querying more than 10 address records. Exactly 10 MX
records is within the limit and must be evaluated normally.
The over-limit case (11 records) is covered by rfc7208-tests.yml
(mx-limit); this is the boundary.
spec: 4.6.4/2
mailfrom: "foo@mx10.net"
host: 1.2.3.4
result: pass
zonedata:
mx10.net:
- SPF: v=spf1 mx -all
- MX: [1, mxno.mx10.net]
- MX: [2, mxno.mx10.net]
- MX: [3, mxno.mx10.net]
- MX: [4, mxno.mx10.net]
- MX: [5, mxno.mx10.net]
- MX: [6, mxno.mx10.net]
- MX: [7, mxno.mx10.net]
- MX: [8, mxno.mx10.net]
- MX: [9, mxno.mx10.net]
- MX: [10, mxyes.mx10.net]
mxno.mx10.net:
- A: 10.0.0.1
mxyes.mx10.net:
- A: 1.2.3.4
---
description: Temperror from mechanism DNS lookups
tests:
a-lookup-error:
description: |
RFC 7208 section 5: for the DNS queries done by mechanisms, if the
DNS server returns an error or the query times out, the mechanism
stops and the topmost check_host() returns temperror. Here, on the
address lookup of an "a" mechanism.
spec: 5/8
mailfrom: "foo@aerr.net"
host: 1.2.3.4
result: temperror
mx-lookup-error:
description: |
Like a-lookup-error, on the MX lookup of an "mx" mechanism.
spec: 5/8
mailfrom: "foo@mxerr.net"
host: 1.2.3.4
result: temperror
mx-target-lookup-error:
description: |
Like a-lookup-error, on the address lookup of one of the hosts
returned by the MX lookup of an "mx" mechanism.
spec: 5/8
mailfrom: "foo@mxerr2.net"
host: 1.2.3.4
result: temperror
zonedata:
aerr.net:
- SPF: v=spf1 a:err.aerr.net -all
err.aerr.net:
- TIMEOUT: true
mxerr.net:
- SPF: v=spf1 mx:err.mxerr.net -all
err.mxerr.net:
- TIMEOUT: true
mxerr2.net:
- SPF: v=spf1 mx -all
- MX: [10, err.mxerr2.net]
err.mxerr2.net:
- TIMEOUT: true
---
description: Redirect and the lookup limit
tests:
redirect-at-limit:
description: |
RFC 7208 section 4.6.4 includes the "redirect" modifier among the
terms that cause DNS queries and count against the limit of 10.
Here the redirect is the 10th such term, so it is still followed.
spec: 4.6.4/1
mailfrom: "foo@at10.redir.net"
host: 1.2.3.4
result: pass
redirect-over-limit:
description: |
Like redirect-at-limit, but the redirect is the 11th term that causes
DNS queries, which exceeds the limit and must permerror (rather than,
for example, being ignored).
spec: 4.6.4/1
mailfrom: "foo@over10.redir.net"
host: 1.2.3.4
result: permerror
zonedata:
at10.redir.net:
- SPF: v=spf1 a:nm.redir.net a:nm.redir.net a:nm.redir.net
a:nm.redir.net a:nm.redir.net a:nm.redir.net
a:nm.redir.net a:nm.redir.net a:nm.redir.net
redirect=target.redir.net
over10.redir.net:
- SPF: v=spf1 a:nm.redir.net a:nm.redir.net a:nm.redir.net
a:nm.redir.net a:nm.redir.net a:nm.redir.net
a:nm.redir.net a:nm.redir.net a:nm.redir.net
a:nm.redir.net redirect=target.redir.net
nm.redir.net:
- A: 10.0.0.1
target.redir.net:
- SPF: v=spf1 ip4:1.2.3.4 -all
---
description: PTR reverse lookup errors
tests:
ptr-reverse-lookup-error:
description: |
RFC 7208 section 5.5: "If a DNS error occurs while doing the PTR RR
lookup, then this mechanism fails to match." This is an explicit
exception to the general rule of section 5 that DNS errors cause
temperror, because the reverse zone is controlled by the owner of
the connecting IP, not by the publishing domain.
spec: 5.5/7
mailfrom: "foo@ptrerr.net"
host: 1.2.3.4
result: softfail
skip: We return temperror instead of treating it as a no-match.
zonedata:
ptrerr.net:
- SPF: v=spf1 ptr ~all
4.3.2.1.in-addr.arpa:
- TIMEOUT: true
---
description: IP4 network syntax
tests:
ip4-octet-over-255:
description: |
RFC 7208 section 5.6: the qnum ABNF limits each octet to 0-255, so
values above that are a syntax error.
spec: 5.6/2
mailfrom: "foo@byte.ipfour.net"
host: 1.2.3.4
result: permerror
ip4-leading-zero:
description: |
RFC 7208 section 5.6: the qnum ABNF does not allow leading zeros
("conventional dotted-quad notation"), so they are a syntax error.
spec: 5.6/2
mailfrom: "foo@zero.ipfour.net"
host: 1.2.3.4
result: permerror
ip4-empty-cidr:
description: |
RFC 7208 section 5.6: ip4-cidr-length requires at least one digit
after the "/", so a trailing slash is a syntax error.
spec: 5.6/2
mailfrom: "foo@cidr.ipfour.net"
host: 1.2.3.4
result: permerror
zonedata:
byte.ipfour.net:
- SPF: v=spf1 ip4:1.2.3.256 -all
zero.ipfour.net:
- SPF: v=spf1 ip4:01.2.3.4 -all
cidr.ipfour.net:
- SPF: v=spf1 ip4:1.2.3.4/ -all
---
description: Modifier syntax edge cases
tests:
qualifier-on-modifier:
description: |
Qualifiers apply to mechanisms only: directive = [qualifier]
mechanism. A qualifier on a modifier ("+redirect=...") makes the term
invalid (it is not an unknown-modifier either, since a modifier name
must start with ALPHA), so the record has a syntax error.
spec: 4.6.1/3, 12/2
mailfrom: "foo@qualmod.net"
host: 1.2.3.4
result: permerror
unknown-mechanism:
description: |
RFC 7208 section 4.6.1: terms that do not contain any of "=", ":" or
"/" are mechanisms; an unknown mechanism name is a syntax error, and
it must be caught even if a later term would match.
spec: 4.6.1/5, 4.6/1
mailfrom: "foo@unkmech.net"
host: 1.2.3.4
result: permerror
unknown-modifier-repeated:
description: |
RFC 7208 section 6: unrecognized modifiers MUST be ignored no matter
where, or *how often*, they appear in a record. Only "redirect" and
"exp" are limited to appearing once.
spec: 6/3
mailfrom: "foo@unkmod.net"
host: 1.2.3.4
result: pass
unknown-modifier-mechanism-name:
description: |
A term like "all=..." or "mx=..." is an unknown modifier, not a
mechanism: modifiers always contain "=" immediately after the name
(RFC 7208 section 4.6.1), and neither "all" nor "mx" take an "="
argument. They must be ignored, not treated as mechanisms or as
syntax errors.
spec: 4.6.1/4, 6/3
mailfrom: "foo@mechmod.net"
host: 1.2.3.4
result: pass
unknown-modifier-empty-value:
description: |
The value of an unknown modifier is a macro-string, which can be
empty: unknown-modifier = name "=" macro-string, with macro-string
allowing zero characters. "foo=" is thus valid and ignored.
spec: 12/2
mailfrom: "foo@emptymod.net"
host: 1.2.3.4
result: pass
zonedata:
qualmod.net:
- SPF: v=spf1 +redirect=target.qualmod.net ~all
target.qualmod.net:
- SPF: v=spf1 ip4:1.2.3.4 -all
unkmech.net:
- SPF: v=spf1 foobar ip4:1.2.3.4 -all
unkmod.net:
- SPF: v=spf1 moo=cow moo=bull ip4:1.2.3.4 -all
mechmod.net:
- SPF: v=spf1 all=deny mx=1.2.3.4 ip4:1.2.3.4 -all
emptymod.net:
- SPF: v=spf1 foo= ip4:1.2.3.4 -all