RFC 7208
Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1
RFC 7208: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1 ๐
- Series: Internet Engineering Task Force (IETF), Request for Comments 7208 ๐
- Author: S. Kitterman, Kitterman Technical Services ๐งโ๐ป
- Date: April 2014 ๐
- Category: Standards Track โจ
- ISSN: 2070-1721
- Obsoletes: RFC 4408 ๐
Abstract ๐ฆ
okay so email on the internet can be forged in a bunch of ways ๐ญ Like, the existing protocols don't stop a sending host from putting literally whatever it wants as the MAIL FROM of a message, or whatever domain it gives on the SMTP HELO/EHLO commands. This doc is version 1 of the Sender Policy Framework (SPF) ๐
It lets ADministrative Management Domains (ADMDs) explicitly say which hosts are allowed to use their domain names, and it lets a receiving host check that permission. This document obsoletes RFC 4408. ๐ซถ
Status of This Memo ๐ธ
This is an Internet Standards Track document. โจ
This document is a product of the Internet Engineering Task Force (IETF). It represents the consensus of the IETF community, it got public review, and the Internet Engineering Steering Group (IESG) approved it for publication ๐ If you want more on Internet Standards, check out Section 2 of RFC 5741.
For the current status, any errata, and how to send feedback, go to http://www.rfc-editor.org/info/rfc7208 ๐ง
Copyright Notice ๐
Copyright (c) 2014 IETF Trust and the persons identified as the document authors. All rights reserved. ๐
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info), as they stand on the publication date. Please read these carefully, because they spell out your rights and restrictions for this document ๐ Any code components pulled out of this document must include Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions. They also come without warranty, as the Simplified BSD License describes.
This document may include material from IETF Documents or IETF Contributions that were published or made publicly available before November 10, 2008. The people who control the copyright in some of that older material may not have given the IETF Trust permission to allow modifications outside the IETF Standards Process. So unless you get a proper license from those copyright holders, you can't modify this document outside the IETF Standards Process, and you can't make derivative works of it outside that process either. The only exceptions are formatting it for publication as an RFC or translating it into languages other than English ๐
Table of Contents ๐
1. Introduction ....................................................5
1.1. Terminology ................................................5
1.1.1. Key Words ...........................................5
1.1.2. Imported Definitions ................................5
1.1.3. MAIL FROM Definition ................................6
1.1.4. HELO Definition .....................................6
1.2. check_host() ...............................................6
2. Operational Overview ............................................6
2.1. Publishing Authorization ...................................6
2.2. Checking Authorization .....................................7
2.3. The "HELO" Identity ........................................8
2.4. The "MAIL FROM" Identity ...................................9
2.5. Location of Checks .........................................9
2.6. Results of Evaluation ......................................9
2.6.1. None ...............................................10
2.6.2. Neutral ............................................10
2.6.3. Pass ...............................................10
2.6.4. Fail ...............................................10
2.6.5. Softfail ...........................................10
2.6.6. Temperror ..........................................10
2.6.7. Permerror ..........................................10
3. SPF Records ....................................................11
3.1. DNS Resource Records ......................................11
3.2. Multiple DNS Records ......................................12
3.3. Multiple Strings in a Single DNS Record ...................12
3.4. Record Size ...............................................13
3.5. Wildcard Records ..........................................13
4. The check_host() Function ......................................14
4.1. Arguments .................................................14
4.2. Results ...................................................15
4.3. Initial Processing ........................................15
4.4. Record Lookup .............................................15
4.5. Selecting Records .........................................15
4.6. Record Evaluation .........................................16
4.6.1. Term Evaluation ....................................16
4.6.2. Mechanisms .........................................16
4.6.3. Modifiers ..........................................17
4.6.4. DNS Lookup Limits ..................................17
4.7. Default Result ............................................18
4.8. Domain Specification ......................................19
5. Mechanism Definitions ..........................................20
5.1. "all" .....................................................21
5.2. "include" .................................................21
5.3. "a" .......................................................23
5.4. "mx" ......................................................23
5.5. "ptr" (do not use) ........................................23
5.6. "ip4" and "ip6" ...........................................25
5.7. "exists" ..................................................25
6. Modifier Definitions ...........................................26
6.1. redirect: Redirected Query ................................26
6.2. exp: Explanation ..........................................27
7. Macros .........................................................28
7.1. Formal Specification ......................................29
7.2. Macro Definitions .........................................29
7.3. Macro Processing Details ..................................30
7.4. Expansion Examples ........................................32
8. Result Handling ................................................33
8.1. None ......................................................34
8.2. Neutral ...................................................34
8.3. Pass ......................................................34
8.4. Fail ......................................................35
8.5. Softfail ..................................................35
8.6. Temperror .................................................36
8.7. Permerror .................................................36
9. Recording the Result ...........................................36
9.1. The Received-SPF Header Field .............................37
9.2. SPF Results in the Authentication-Results Header Field ....39
10. Effects on Infrastructure .....................................39
10.1. Sending Domains ..........................................40
10.1.1. DNS Resource Considerations .......................40
10.1.2. Administrator's Considerations ....................41
10.1.3. Bounces ...........................................41
10.2. Receivers ................................................42
10.3. Mediators ................................................42
11. Security Considerations .......................................43
11.1. Processing Limits ........................................43
11.2. SPF-Authorized Email May Contain Other False Identities ..44
11.3. Spoofed DNS and IP Data ..................................44
11.4. Cross-User Forgery .......................................44
11.5. Untrusted Information Sources ............................45
11.5.1. Recorded Results ..................................45
11.5.2. External Explanations .............................45
11.5.3. Macro Expansion ...................................46
11.6. Privacy Exposure .........................................46
11.7. Delivering Mail Producing a "Fail" Result ................46
12. Collected ABNF ................................................46
13. Contributors and Acknowledgements .............................48
14. IANA Considerations ...........................................49
14.1. The SPF DNS Record Type ..................................49
14.2. The Received-SPF Mail Header Field .......................50
14.3. SPF Modifier Registry ....................................50
15. References ....................................................50
15.1. Normative References .....................................50
15.2. Informative References ...................................51
Appendix A. Extended Examples .....................................54
A.1. Simple Examples ............................................55
A.2. Multiple Domain Example ....................................56
A.3. DNS Blacklist (DNSBL) Style Example ........................56
A.4. Multiple Requirements Example ..............................57
Appendix B. Changes in Implementation Requirements from RFC 4408 ..57
Appendix C. Further Testing Advice ................................58
Appendix D. SPF/Mediator Interactions .............................59
D.1. Originating ADMDs ..........................................59
D.2. Mediators ..................................................60
D.3. Receiving ADMDs ............................................60
Appendix E. Mail Services .........................................61
Appendix F. MTA Relays ............................................61
Appendix G. Local Policy Considerations ...........................62
G.1. Policy for SPF Pass ........................................62
G.2. Policy for SPF Fail ........................................62
G.3. Policy for SPF Permerror ...................................63
G.4. Policy for SPF Temperror ...................................63
1. Introduction โจ
okay so bestie, the current email system has this big problem: any host that shoves mail into it can use basically ANY DNS domain name it wants in each of the identifiers defined by [RFC5321] and [RFC5322] ๐ญ. Sometimes that flexibility is actually useful, but it's a huge obstacle to cutting down on Unsolicited Bulk Email (UBE, aka spam) ๐ โโ๏ธ. And ADMDs (as described in [RFC5598]) are totally right to be stressed about how easily other entities can borrow their domain names, usually with shady intentions ๐.
This document defines a protocol that lets ADMDs authorize specific hosts to use their domain names in the "MAIL FROM" or "HELO" identities ๐. Compliant ADMDs publish Sender Policy Framework (SPF) records in the DNS that say which hosts are allowed to use their names ๐ฆ. Then compliant mail receivers check those published SPF records to test whether a sending Mail Transfer Agent (MTA) is authorized to use a given "HELO" or "MAIL FROM" identity during a mail transaction ๐.
An additional benefit for mail receivers is that once an identity is verified, local policy decisions about the mail can be made based on the sender's domain instead of the host's IP address ๐ธ. This is such a glow-up, because domain reputation is likely to be more accurate than IP reputation. Domains tend to stick around for longer, so they're more stable ๐ . Plus, if a claimed identity fails verification, local policy can take stronger action against that email, like rejecting it ๐ซ.
1.1. Terminology
1.1.1. Key Words
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119] ๐.
1.1.2. Imported Definitions
So ABNF (Augmented Backus-Naur Form) is defined in [RFC5234], and so are the tokens "ALPHA", "DIGIT", and "SP" (space) ๐งฉ.
The tokens "Local-part", "Domain", and "Mailbox" are defined in [RFC5321] ๐ฌ.
"dot-atom", "quoted-string", "comment", "CFWS" (comment folded white space), "FWS" (folded white space), and "CRLF" (carriage-return/ line-feed) are defined in [RFC5322] โจ.
1.1.3. MAIL FROM Definition
This document cares about the identity of the sender of a mail message, as referred to in [RFC5321]:
The transaction starts with a MAIL command that gives the sender
identification.
Since there are many other names for this identity, it is important to choose a name that is:
- commonly used
- well defined
So throughout this document, we're going with the term "MAIL FROM" ๐ซถ. It's defined as the RFC5321.MailFrom (reverse-path) identity described in [RFC5598] ๐.
1.1.4. HELO Definition
This document also makes use of the HELO/EHLO identity ๐. The "HELO" identity comes from either the SMTP HELO or EHLO command (see [RFC5321]). HELO and EHLO can often be used interchangeably, so this document just calls them "HELO" for short ๐. This means RFC5321.HELO/.EHLO as defined in [RFC5598]. These commands supply the identity of the SMTP client (sending host) for the SMTP session ๐ง.
1.2. check_host()
Section 4 introduces an algorithm that evaluates an SPF policy against an arriving email transaction ๐. In an early implementation, this algorithm was encoded in a function called check_host(). That name is used in this document as a symbol for the SPF evaluation algorithm, but implementers are not required to use this name, of course ๐.
2. Operational Overview โจ
2.1. Publishing Authorization
An SPF-compliant domain publishes valid SPF records as described in Section 3 ๐. These records authorize the use of the relevant domain names in the "HELO" and "MAIL FROM" identities by the MTAs specified in them ๐.
SPF results can be used to make both positive (source is authorized) and negative (source is not authorized) determinations ๐
. If ADMDs choose to publish SPF records and want receivers to make negative authorization determinations, they need to publish records that end in -all, or redirect to other records that do. Otherwise, no definitive determination of authorization can be made ๐. Potential issues and mitigations associated with negative determinations are discussed in Section 10 โ ๏ธ.
ADMDs that want to declare that no hosts are authorized to use their DNS domain names in the HELO or MAIL FROM commands during SMTP sessions can publish SPF records that say so. This works for domain names that are neither used in the domain part of email addresses nor expected to originate mail ๐.
When changing SPF records, you have to be careful and make sure there's a transition period. The old policy needs to stay valid until all legitimate email can reasonably be expected to have been checked โณ. [RFC5321], Section 4.5.4.1 discusses how long a message might be in transit ๐. Offline checks are possible, but the closer to the original transmission time the checks are performed, the more likely they are to get an SPF result that matches the sending ADMD's intent at the time the message was sent ๐ฏ.
2.2. Checking Authorization
A mail receiver can perform a set of SPF checks for each mail message it receives ๐ฅ. An SPF check tests the authorization of a client host to emit mail with a given identity ๐ต๏ธโโ๏ธ. Usually a receiving MTA does these checks, but they can happen elsewhere in the mail processing chain as long as the required information is available and reliable ๐. The "MAIL FROM" and "HELO" identities are checked as described in Sections 2.4 and 2.3, respectively ๐.
Without explicit approval from the publishing ADMD, checking other identities against SPF version 1 records is NOT RECOMMENDED, because there are known cases that give incorrect results ๐ฌ. For example, almost all mailing lists rewrite the "MAIL FROM" identity (see Section 10.3), but some don't change any other identities in the message ๐ฃ. Documents that define other identities will have to define the method for explicit approval ๐.
Mail receivers might use the SPF check as part of a bigger set of tests on incoming mail ๐ง . The results of other tests might influence whether or not a particular SPF check gets performed. For example, if the sending host's IP address is on a local whitelist, that might cause all other tests to be skipped and all mail from that host to be accepted ๐.
When a mail receiver decides to perform an SPF check, it has to use a correctly implemented check_host() function (Section 4) evaluated with the correct parameters ๐ ๏ธ. The test as a whole is optional, but once someone has decided to perform it, it has to be performed as specified. That's how the correct semantics get preserved between publisher and receiver ๐ค.
To make the test, the mail receiver MUST evaluate the check_host() function with the arguments described in Section 4.1 ๐ซก.
Here's a plot twist: invalid, malformed, or non-existent domains make SPF checks return "none", because no SPF record can be found ๐ป. Even so, many MTAs have long had a policy of rejecting email from such domains, especially when the "MAIL FROM" is invalid ๐ซ. Rejecting that email prevents one method of getting around SPF records ๐ช.
Implementations have to be careful to correctly extract the <domain> from the data given with the SMTP MAIL FROM command ๐. That's because many MTAs will still accept things like source routes (see Appendix C of [RFC5321]), the %-hack (see [RFC1123]), and bang paths (see [RFC1983]) ๐ง. These archaic features have been maliciously used to bypass security systems ๐.
2.3. The "HELO" Identity
It is RECOMMENDED that SPF verifiers not only check the "MAIL FROM" identity, but also separately check the "HELO" identity ๐. To do that, they apply the check_host() function (Section 4) to the "HELO" identity as the <sender>. Checking "HELO" promotes consistency of results and can reduce DNS resource usage ๐. If a check of "HELO" makes a conclusive determination about the message, then there's no need to spend DNS resources on the typically more complex "MAIL FROM" ๐
โโ๏ธ. Also, since SPF records published for "HELO" identities refer to a single host, when they're available they're a very reliable source of host authorization status ๐ฏ. Checking "HELO" before "MAIL FROM" is the RECOMMENDED sequence if both are checked ๐ช.
Note that the sending party isn't always clear on what the domain in the EHLO or HELO command should be ๐คทโโ๏ธ. So SPF verifiers have to be prepared for the identity to be an IP address literal (see [RFC5321], Section 4.1.3), or for it to simply be malformed ๐ซ . This SPF check can only be performed when the "HELO" string is a valid, multi-label domain name ๐ค.
2.4. The "MAIL FROM" Identity
SPF verifiers MUST check the "MAIL FROM" identity if a "HELO" check either has not been performed or has not reached a definitive policy result. To do this, they apply the check_host() function to the "MAIL FROM" identity as the <sender> ๐.
[RFC5321] allows the reverse-path to be null (see Section 4.5.5 in [RFC5321]). In that case there's no explicit sender mailbox, and such a message can be assumed to be a notification from the mail system itself ๐ค. When the reverse-path is null, this document defines the "MAIL FROM" identity as the mailbox made up of the local-part "postmaster" and the "HELO" identity. (That "HELO" identity might or might not have been checked separately before.) ๐ญ
2.5. Location of Checks
The authorization check SHOULD be performed while the SMTP transaction that receives the mail is being processed ๐ก. This keeps things simpler when figuring out the correct IP address to use as an input to check_host(), and it lets errors get sent straight back to the sending MTA through SMTP replies ๐ฌ. Appendix D of [RFC7001] has a more thorough discussion of this topic ๐.
The authorization check happens during the SMTP transaction at the time of the MAIL command. It uses the MAIL FROM value and the client IP address ๐งพ. Doing the check at later times, or with other input, can cause problems like these:
- It might be difficult to accurately extract the required information from potentially deceptive headers ๐ญ.
- Legitimate email might fail the authorization check because the sender's policy has since changed ๐.
Generating non-delivery notifications to forged identities that have failed the authorization check often counts as backscatter. That's nuisance rejection notices that nobody can act on ๐จ. Operators are strongly advised to avoid such practices ๐จ. Section 2 of [RFC3834] describes backscatter and the problems it causes ๐ง.
2.6. Results of Evaluation
Section 4 defines check_host(), a model function definition. It uses the inputs defined above and the sender's policy published in the DNS to reach a conclusion about client authorization ๐งฎ. An SPF verifier implements something semantically equivalent to the function defined there ๐ช.
This section lists and briefly defines the possible outputs of that function ๐. Note, however, that the protocol doesn't set any normative requirements for handling any particular result ๐ซถ. Discussion of handling options for each result can be found in Section 8 ๐ก.
2.6.1. None
A result of none means either (a) no syntactically valid DNS domain name could be pulled out of the SMTP session to be the one authorized, or (b) no SPF records were retrieved from the DNS ๐ซฅ.
2.6.2. Neutral ๐คทโโ๏ธ
okay so a "neutral" result means the ADMD (the domain's admin crew) has explicitly said "we're not gonna say whether this IP is authorized." no yes, no no, just a shrug ๐
2.6.3. Pass โ
"pass" is the explicit "yes bestie, this client is totally allowed to send mail with that identity." main character validation ๐
2.6.4. Fail โ
"fail" is the explicit "nope, this client is not authorized to use that domain in that identity." not the vibe ๐
2.6.5. Softfail ๐
"softfail" is a lowkey "probably not" from the publishing ADMD. they haven't gone for the stronger, more definitive policy that would have given a "fail" ๐ซฃ
2.6.6. Temperror โณ
"temperror" means the SPF verifier ran into a transient error (usually DNS) while doing the check. a later retry might work with no DNS operator action needed, so it's giving "try again in a bit" ๐ฆ
2.6.7. Permerror ๐จ
"permerror" means the domain's published records couldn't be interpreted correctly. this one is a real error condition, and it definitely needs a DNS operator to fix it. retrying won't magically slay it ๐ญ
3. SPF Records ๐
An SPF record is a DNS record that declares which hosts are, and are not, authorized to use a domain name for the "HELO" and "MAIL FROM" identities. loosely, the record sorts hosts into permitted and not-permitted sets (though some hosts might land in neither category) ๐ง
The SPF record is written as a single string of text in the RDATA of a single DNS TXT resource record. multiple SPF records are not permitted for the same owner name, so only one per name, bestie ๐ซถ The record format and the process for picking records are explained below in Section 4. here's an example record:
v=spf1 +mx a:colo.example.com/28 -all
this record has a version of "spf1" and three directives: "+mx", "a:colo.example.com/28" (the "+" is implied), and "-all" โจ
Each SPF record lives in the DNS tree at the owner name it's about, not in a subdomain under that owner name. this is kind of like how SRV records [RFC2782] work ๐
The example in this section might be published with these lines in a domain zone file:
example.com. TXT "v=spf1 +mx a:colo.example.com/28 -all"
TXT records have lots of uses, so beware of other TXT records published there for other purposes ๐ They might cause problems with size limits (see Section 3.4), and you have to be careful that only SPF records get used for SPF processing.
ADMDs publishing SPF records ought to keep the amount of DNS information needed to evaluate a record as small as possible ๐ธ Sections 4.6.4 and 10.1.1 have some suggestions about "include" mechanisms and chained "redirect" modifiers.
3.1. DNS Resource Records ๐
SPF records MUST be published as a DNS TXT (type 16) Resource Record (RR) [RFC1035] only. the character content of the record is encoded as [US-ASCII]. using other DNS RR types was supported during SPF's experimental phase, but that's been discontinued ๐ โโ๏ธ
back in 2003, when SPF was first being built, the rules for getting a new DNS RR type were way stricter than they are now. plus, DNS servers and provisioning systems didn't widely support easy deployment of new RR types. so the SPF devs found it easier and more practical to use the TXT RR type for SPF records ๐งธ
in its review of [RFC4408], the SPFbis working group concluded that its dual RR type transition model was fundamentally flawed. it had no common RR type that implementers were required to serve and required to check. a bunch of alternatives got considered, but the group decided that a big migration to the SPF RR type was very unlikely in the foreseeable future. so the best way to fix this interoperability problem was to drop support for the SPF RR type from SPF version 1 ๐ see Appendix A of [RFC6686] for more info.
SPF's initial deployment, a decade ago, is kind of a unique situation ๐ฐ๏ธ if a future update to SPF were developed that didn't reuse existing SPF records, it could use the SPF RR type. but using TXT for structured data should in no way be taken as precedent for future protocol designers. more on design considerations when using new DNS RR types can be found in [RFC5507] ๐ฅ
3.2. Multiple DNS Records ๐ซ
A domain name MUST NOT have multiple records that would cause an authorization check to select more than one record. see Section 4.5 for the selection rules ๐
3.3. Multiple Strings in a Single DNS Record ๐งต
As defined in [RFC1035], Sections 3.3 and 3.3.14, a single text DNS record can be made of more than one string. if a published record has multiple character-strings, then the record MUST be treated as if those strings are concatenated together with no spaces added. for example:
IN TXT "v=spf1 .... first" "second string..."
is equivalent to:
IN TXT "v=spf1 .... firstsecond string..."
TXT records with multiple strings are super useful for building records that would go over the 255-octet max length of a single character-string in one TXT record ๐
3.4. Record Size ๐
The published SPF record for a given domain name SHOULD stay small enough that the results of a query for it fit within 512 octets. otherwise, there's a chance of hitting a DNS protocol limit. this UDP limit is defined in [RFC1035], Section 2.3.4, although [RFC2671] raised it. staying below 512 octets ought to stop older DNS implementations from failing over to TCP, and it'll work with UDP when EDNS0 [RFC6891] support isn't there.
the answer size depends on lots of stuff outside this document's scope, so the only guideline we can give is this: if the size of the DNS message, meaning the combined length of the DNS name and the text of all the records of a given type, is under 450 octets, then DNS answers ought to fit in UDP packets ๐ฆ records that are too long to fit in one UDP packet could be silently ignored by SPF verifiers because of firewalls and other issues that mess with DNS over TCP or EDNS0.
note that when you compute sizes for replies to TXT-format queries, you have to count any other TXT records published at the domain name too ๐ Similarly, the sizes for replies to all SPF-related queries have to be checked so they fit in a single 512-octet UDP packet (i.e., DNS message size limited to 450 octets) ๐ซถ
3.5. Wildcard Records ๐
using wildcard records for publishing is discouraged, and you have to be careful if you use them ๐ if a zone includes wildcard MX records, it might want to publish wildcard declarations too, with the same requirements and problems. in particular, the declaration MUST be repeated for any host that has any RR records at all, and for subdomains of that host. check out the example in [RFC1034], Section 4.3.3. based on that, we can do the following:
EXAMPLE.COM. MX 10 A.EXAMPLE.COM
EXAMPLE.COM. TXT "v=spf1 a:A.EXAMPLE.COM -all"
*.EXAMPLE.COM. MX 10 A.EXAMPLE.COM
*.EXAMPLE.COM. TXT "v=spf1 a:A.EXAMPLE.COM -all"
A.EXAMPLE.COM. A 203.0.113.1
A.EXAMPLE.COM. MX 10 A.EXAMPLE.COM
A.EXAMPLE.COM. TXT "v=spf1 a:A.EXAMPLE.COM -all"
*.A.EXAMPLE.COM. MX 10 A.EXAMPLE.COM
*.A.EXAMPLE.COM. TXT "v=spf1 a:A.EXAMPLE.COM -all"
SPF records have to be listed twice for every name within the zone: once for the name itself, and once with a wildcard to cover the tree under that name. that way, all domains in use in outgoing mail are covered ๐
4. The check_host() Function ๐
this description isn't an application programming interface definition. it's a function description used to illustrate the algorithm. a compliant SPF implementation MUST produce results semantically equivalent to this description ๐
the check_host() function fetches SPF records, parses them, and evaluates them to figure out whether a particular host is or isn't permitted to send mail with a given identity. receiving ADMDs that do this check MUST correctly evaluate the check_host() function as described here ๐
Implementations MAY use a different algorithm than the canonical algorithm defined here, as long as the results are the same in all cases โจ
4.1. Arguments ๐งพ
the check_host() function takes these arguments:
<ip> - the IP address of the SMTP client that is emitting
the mail, either IPv4 or IPv6.
<domain> - the domain that provides the sought-after authorization
information; initially, the domain portion of the
"MAIL FROM" or "HELO" identity.
<sender> - the "MAIL FROM" or "HELO" identity.
for recursive evaluations, the domain portion of <sender> might not match the <domain> argument from when check_host() first runs. in most other cases they'll be the same (see Section 5.2 below). the overall DNS lookup limit for SPF terms, described below in Section 4.6.4, must be tracked as a single global limit across all evaluations, not just for one instance of a recursive evaluation ๐ง
heads up: the <domain> argument might not be a well-formed domain name ๐ for example, if the reverse-path was null, then the EHLO/HELO domain gets used, with its associated problems (see Section 2.3). in these cases, check_host() is defined in Section 4.3 to return a "none" result.
4.2. Results ๐ฏ
the check_host() function can return one of several results described in Section 2.6. based on the result, the action to take is decided by the receiver's local policies. this is discussed in Section 8 โจ
4.3. Initial Processing ๐ฑ
if the <domain> is malformed (e.g., label longer than 63 characters, zero-length label not at the end, etc.), or isn't a multi-label domain name, or if the DNS lookup returns "Name Error" (RCODE 3, also known as "NXDOMAIN" [RFC2308]), check_host() immediately returns the result "none". DNS RCODEs are defined in [RFC1035]. properly formed domains are fully qualified domains as defined in [RFC1983]. that means in the DNS they're implicitly qualified relative to the root (see Section 3.1 of [RFC1034]). internationalized domain names MUST be encoded as A-labels, as described in Section 2.3 of [RFC5890] ๐
if the <sender> has no local-part, substitute the string "postmaster" for the local-part ๐ฎ
4.4. Record Lookup ๐
following how records are published (see Section 3 above), a DNS query needs to be made for the <domain> name, asking for type TXT only ๐ง
if the DNS lookup returns a server failure (RCODE 2) or some other error (RCODE other than 0 or 3), or if the lookup times out, then check_host() terminates immediately with the result "temperror" โณ
4.5. Selecting Records ๐๏ธ
records begin with a version section:
record = version terms *SP
version = "v=spf1"
starting with the set of records returned by the lookup, discard records that don't begin with a version section of exactly "v=spf1". note that the version section ends with either an SP character or the end of the record. as an example, a record with a version section of "v=spf10" doesn't match and gets discarded ๐ โโ๏ธ
if the resulting record set has no records, check_host() produces the "none" result. if the resulting record set has more than one record, check_host() produces the "permerror" result ๐จ
4.6. Record Evaluation
okay so check_host() is the one that actually reads your SPF record and works out the answer ๐โจ it checks the syntax FIRST, and if there's a syntax error ANYWHERE in the record, it bails right away with "permerror" ๐ no interpreting, no evaluating, just a hard stop. iconic for keeping things clean ๐
4.6.1. Term Evaluation
so there are two kinds of terms ๐: mechanisms (we'll get into those in Section 5) and modifiers (Section 6). a record is just an ordered list of them, and the grammar looks like this:
terms = *( 1*SP ( directive / modifier ) )
directive = [ qualifier ] mechanism
qualifier = "+" / "-" / "?" / "~"
mechanism = ( all / include
/ a / mx / ptr / ip4 / ip6 / exists )
modifier = redirect / explanation / unknown-modifier
unknown-modifier = name "=" macro-string
; where name is not any known modifier
name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." )
most mechanisms let you put a ":" or "/" right after the name ๐ฆ
modifiers always have an equals ('=') sign right after the name, and it comes before any ":" or "/" that might be hiding inside the macro-string ๐ธ
anything with none of "=", ":", or "/" in it is a mechanism (Section 5 has the full tea) ๐ง
and per the ABNF rules in [RFC5234], mechanism and modifier names are case-insensitive, so "INCLUDE" and "include" are the same bestie ๐
4.6.2. Mechanisms
each mechanism gets checked one at a time, left to right ๐ if you run out of mechanisms, the result is the default one from Section 4.7.
when a mechanism gets evaluated, one of three things can happen โจ: it matches, it doesn't match, or it returns an exception.
if it matches, processing ends and the qualifier's value becomes the record's result ๐. if it doesn't match, we move on to the next mechanism. if it returns an exception, mechanism processing ends and the exception value is what you get ๐ญ
the possible qualifiers and the results they make check_host() return:
"+" pass
"-" fail
"~" softfail
"?" neutral
the qualifier is optional and it defaults to "+" ๐
when a mechanism matches and the qualifier is "-", you get a "fail" result, and the explanation string gets built the way Section 6.2 describes ๐
the specific mechanisms are all in Section 5 ๐
4.6.3. Modifiers
modifiers are NOT mechanisms, okay? ๐ โโ๏ธ they don't return match or not-match. they just add extra info ๐. they don't directly change how the record gets evaluated, BUT the "redirect" modifier does do something after all the mechanisms have been checked ๐
4.6.4. DNS Lookup Limits
some terms (mechanisms and modifiers, collectively "terms") make DNS queries while they're being evaluated, and some don't ๐ง. the ones that DO make queries are: the "include", "a", "mx", "ptr", and "exists" mechanisms, plus the "redirect" modifier.
SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, so the DNS doesn't get absolutely wrecked by unreasonable load ๐. if that limit gets exceeded, the implementation MUST return "permerror" ๐ซ.
the other terms (the "all", "ip4", and "ip6" mechanisms, and the "exp" modifier) do NOT cause DNS queries while SPF is being evaluated (the "exp" modifier only does a lookup at a later time ๐ฐ๏ธ), and they don't count toward this limit at all โจ.
now the "mx" mechanism has its own little rules ๐ฌ. the number of "MX" records queried counts toward the overall limit of 10 mechanisms/modifiers that cause DNS lookups. ON TOP OF THAT, evaluating each "MX" record MUST NOT result in querying more than 10 address records (either "A" or "AAAA" resource records). if that second limit gets exceeded, the "mx" mechanism MUST produce a "permerror" result ๐ โโ๏ธ.
the "ptr" mechanism and the %{p} macro are similar ๐ฆ. the number of "PTR" records queried counts toward that same overall limit of 10. ALSO, evaluating each "PTR" record MUST NOT result in querying more than 10 address records (either "A" or "AAAA" resource records). if that gets exceeded, every record other than the first 10 MUST be ignored ๐๏ธ.
why the different treatment? the set and contents of MX records are controlled by the publishing ADMD, but the set and contents of PTR records are controlled by whoever owns the IP address that's actually making the connection ๐ต๏ธโโ๏ธ. different owners, different rules, makes total sense ๐
all of these limits are per mechanism or macro in the record, and they're on top of the lookup limits from above ๐ธ.
MTAs or other processors SHOULD impose a limit on how much elapsed time check_host() gets to take ๐ฐ๏ธ. that limit SHOULD allow at least 20 seconds. if the limit gets exceeded, the result of authorization SHOULD be "temperror" ๐ต.
as Section 11.1 explains at the end, it can be useful to cap how many "terms" get DNS answers that are either a positive answer (RCODE 0) with an answer count of 0, or a "Name Error" (RCODE 3) answer. these are sometimes called "void lookups" ๐ซฅ. SPF implementations SHOULD limit "void lookups" to two. an implementation MAY choose to make that limit configurable, and in that case, a default of two is RECOMMENDED ๐. exceeding the limit produces a "permerror" result ๐จ.
4.7. Default Result
if none of the mechanisms match AND there's no "redirect" modifier, check_host() returns "neutral", just as if "?all" were the last directive ๐ค๏ธ. but if there IS a "redirect" modifier, check_host() goes on as defined in Section 6.1 ๐ฆ.
honestly it's better to use either a "redirect" modifier or an "all" mechanism to explicitly end processing ๐ . there's an implicit "?all" at the end of every record that isn't explicitly terminated, but it helps with debugging when you write it out yourself ๐โจ.
for example:
v=spf1 +mx -all
or
v=spf1 +mx redirect=_spf.example.com
4.8. Domain Specification
a bunch of these mechanisms and modifiers have a <domain-spec> part ๐ง. the <domain-spec> string gets macro expansion (see Section 7) ๐. the result is the usual presentation form of a fully qualified DNS name: labels separated by periods. this domain is called the <target-name> for the rest of the document ๐ฏ.
Note: the result of macro expansion doesn't get any further escaping ๐. so this facility can't produce every character that's legal in a DNS label (like control characters, for example). BUT it's still powerful enough to express legal host names and common utility labels (like "_spf") that show up in DNS ๐.
for a few mechanisms, the <domain-spec> is optional ๐ซถ. if you leave it out, the <domain> from the check_host() arguments (see Section 4.1) gets used as the <target-name>. "domain" and <domain-spec> are syntactically identical after macro expansion, but "domain" is an input value for check_host(), while <domain-spec> is computed BY check_host() ๐ง .
the result of calling check_host() with a syntactically invalid domain is undefined ๐คทโโ๏ธ.
Note: this document and its predecessors don't make any provisions for how to handle a syntactically invalid <domain-spec> (which could come out of macro expansion), per [RFC1035] ๐งฉ. examples are names with empty labels, like "foo..example.com", and labels longer than 63 characters ๐ฌ. some implementations treat these errors as not-match and ignore those names, while others return a "permerror" exception ๐.
5. Mechanism Definitions โจ
okay this section is where the actual mechanisms live ๐ก. there are two types: basic language framework mechanisms and designated sender mechanisms ๐ซถ.
basic mechanisms build the language framework ๐งฑ. they don't specify any particular type of authorization scheme. the basic mechanisms are:
all
include
designated sender mechanisms are used to identify a set of <ip> addresses that are permitted (or not permitted) to use the <domain> for sending mail ๐ฌ. they are:
a
mx
ptr (do not use)
ip4
ip6
exists
the following conventions apply to every mechanism that compares <ip> with an IP address at any point ๐:
if no CIDR prefix length is given in the directive, then <ip> and the IP address get compared for equality. (CIDR here is Classless Inter-Domain Routing, described in [RFC4632] ๐.)
if a CIDR prefix length IS specified, then only that number of high-order bits of <ip> and the IP address get compared for equality ๐.
when any mechanism fetches host addresses to compare with <ip>: if <ip> is an IPv4 address, "A" records get fetched. if <ip> is an IPv6 address, "AAAA" records get fetched ๐ฆ. SPF implementations on IPv6 servers need to handle both "AAAA" and "A" records, for clients on IPv4-mapped IPv6 addresses [RFC4291] โจ. IPv4 <ip> addresses only get listed in an SPF record using the "ip4" mechanism ๐.
several mechanisms rely on info fetched from DNS ๐ง. for these DNS queries, unless noted otherwise, if the DNS server returns an error (RCODE other than 0 or 3) or the query times out, the mechanism stops and the topmost check_host() returns "temperror" ๐ฑ. if the server returns "Name Error" (RCODE 3), then evaluation of the mechanism carries on as if the server had returned no error (RCODE 0) and zero answer records ๐ซฅ.
5.1. "all"
the "all" grammar is just:
all = "all"
the "all" mechanism is a test that ALWAYS matches ๐ฏ. it's used as the rightmost mechanism in a record to give you an explicit default ๐.
for example:
v=spf1 a mx -all
mechanisms after "all" will never be tested ๐. mechanisms listed after "all" MUST be ignored ๐ซ. and any "redirect" modifier (Section 6.1) MUST be ignored when there's an "all" mechanism in the record, no matter where the terms are ordered ๐ญ๐ซถ.
5.2. "include" ๐
include = "include" ":" domain-spec
okay so the include mechanism triggers a recursive evaluation of check_host() ๐โจ
- the
<domain-spec>gets expanded as per Section 7 ๐ธ check_host()is evaluated with the resulting string as the<domain>. the<ip>and<sender>arguments stay the same as in the current evaluation ofcheck_host()๐- the recursive evaluation hands back match, not-match, or an error ๐ฏ
- if it returns match, the appropriate result for the
includemechanism is used. soincludeor+includegives a "pass" result, and-includegives "fail" ๐ฆ - if it returns not-match or an error, the parent
check_host()picks up where it left off, following the table below, with the previous value of<domain>restored ๐ซถ
ngl the name "include" was a little unfortunate in hindsight ๐ญ only the evaluated result of the referenced SPF record gets used. the mechanisms in that record aren't literally copied into the first one. so a -all in the referenced record doesn't end the overall processing, and it doesn't automatically mean an overall "fail". (honestly "if-match" or "on-match" would've been way more iconic ๐)
the include mechanism lets one domain designate multiple administratively independent domains ๐ for example, a vanity domain "example.net" might send mail using the servers of the administratively independent domains example.com and example.org.
example.net could say:
IN TXT "v=spf1 include:example.com include:example.org -all"
this basically tells check_host() to check the records of example.com and example.org for a "pass" result ๐ the result only becomes "fail" if the host isn't permitted for either of those domains. main character energy for sure ๐
whether this mechanism matches, doesn't match, or returns an exception depends on the result of the recursive evaluation of check_host():
+---------------------------------+---------------------------------+
| A recursive check_host() result | Causes the "include" mechanism |
| of: | to: |
+---------------------------------+---------------------------------+
| pass | match |
| | |
| fail | not match |
| | |
| softfail | not match |
| | |
| neutral | not match |
| | |
| temperror | return temperror |
| | |
| permerror | return permerror |
| | |
| none | return permerror |
+---------------------------------+---------------------------------+
the include mechanism is meant for crossing administrative boundaries ๐ง if you're staying inside one administrative authority, include usually isn't the best pick. say example.com and example.org are run by the same entity, and the permitted set of hosts for both is "mx:example.com". example.org could say "include:example.com", but it'd be way cleaner to say "redirect=example.com" or even "mx:example.com" ๐ง
with include, an administratively external set of hosts can be authorized, but the sender policy is still decided by the original domain's SPF record (as determined by the "all" mechanism in that record) ๐ซ the redirect modifier is more suitable for bundling both authorizations and policy into a shared set inside an ADMD. redirect is much more like a common code element shared among records in a single ADMD ๐ and it's possible to control both authorized hosts and policy for an arbitrary number of domains from one single record. galaxy brain stuff โจ
5.3. "a" ๐ท
this mechanism matches if <ip> is one of the <target-name>'s IP addresses. for clarity, that means the "a" mechanism also matches AAAA records ๐ซถ
a = "a" [ ":" domain-spec ] [ dual-cidr-length ]
an address lookup is done on the <target-name>, using the type of lookup (A or AAAA) that fits the connection type (IPv4 or IPv6) ๐ the <ip> gets compared to the returned address(es). if any address matches, the mechanism matches ๐
5.4. "mx" ๐ฌ
this mechanism matches if <ip> is one of the MX hosts for a domain name ๐
mx = "mx" [ ":" domain-spec ] [ dual-cidr-length ]
check_host() first does an MX lookup on the <target-name>. then it does an address lookup on each MX name that comes back ๐ the <ip> gets compared to each returned IP address. to stop denial-of-service (DoS) attacks ๐, the processing limits defined in Section 4.6.4 MUST be followed. if the MX lookup limit is exceeded, "permerror" is returned and the evaluation is terminated. if any address matches, the mechanism matches ๐ฅ
Note regarding implicit MXes: if the<target-name>has no MX record,check_host()MUST NOT apply the implicit MX rules of [RFC5321] by querying for an A or AAAA record for the same name. no sneaky fallbacks, bestie ๐ โโ๏ธ
5.5. "ptr" (do not use) ๐
this mechanism checks whether the DNS reverse-mapping for <ip> exists and correctly points to a domain name within a particular domain ๐ but honestly? this one is a big no-no for publishing. it SHOULD NOT be published. see the note at the end of this section for more info ๐
ptr = "ptr" [ ":" domain-spec ]
the <ip>'s name gets looked up using this procedure ๐ต๏ธโโ๏ธ
- perform a DNS reverse-mapping for
<ip>: look up the corresponding PTR record inin-addr.arpa.if the address is an IPv4 address, and inip6.arpa.if it's an IPv6 address - for each record returned, validate the domain name by looking up its IP addresses. to prevent DoS attacks, the PTR processing limits defined in Section 4.6.4 MUST be applied. if they're exceeded, processing is terminated and the mechanism does not match
- if
<ip>is among the returned IP addresses, then that domain name is validated ๐
check all validated domain names to see if they either match the <target-name> domain or are a subdomain of the <target-name> domain. if any do, this mechanism matches. if no validated domain name can be found, or if none of them match or are a subdomain of the <target-name>, this mechanism fails to match. if a DNS error happens while doing the PTR RR lookup, this mechanism fails to match. if a DNS error happens while doing an A RR lookup, that domain name is skipped and the search keeps going ๐
this mechanism matches if
- the
<target-name>is a subdomain of a validated domain name, or - the
<target-name>and a validated domain name are the same
for example, "mail.example.com" is within the domain "example.com", but "mail.bad-example.com" is not ๐ง
Note: this mechanism is slow ๐, it's not as reliable as other mechanisms when DNS errors show up, and it puts a big burden on the .arpa name servers ๐ฉ. if you do use it, proper PTR records have to be in place for the domain's hosts, and the "ptr" mechanism SHOULD be one of the last mechanisms checked. after many years of SPF deployment experience, it's been concluded that it's unnecessary, and more reliable alternatives should be used instead ๐ฅฒ. it's still in use as part of the SPF protocol though, so compliant check_host() implementations MUST support it ๐ซก
5.6. "ip4" and "ip6" ๐
these mechanisms test whether <ip> is contained within a given IP network ๐บ๏ธ
ip4 = "ip4" ":" ip4-network [ ip4-cidr-length ]
ip6 = "ip6" ":" ip6-network [ ip6-cidr-length ]
ip4-cidr-length = "/" ("0" / %x31-39 0*1DIGIT) ; value range 0-32
ip6-cidr-length = "/" ("0" / %x31-39 0*2DIGIT) ; value range 0-128
dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ]
ip4-network = qnum "." qnum "." qnum "." qnum
qnum = DIGIT ; 0-9
/ %x31-39 DIGIT ; 10-99
/ "1" 2DIGIT ; 100-199
/ "2" %x30-34 DIGIT ; 200-249
/ "25" %x30-35 ; 250-255
; as per conventional dotted-quad notation, e.g., 192.0.2.0
ip6-network = <as per Section 2.2 of [RFC4291]>
; e.g., 2001:db8::cd30
the <ip> gets compared to the given network. if the CIDR prefix length high-order bits match, the mechanism matches ๐ฏ
if ip4-cidr-length is omitted, it's taken to be "/32". if ip6-cidr-length is omitted, it's taken to be "/128". it is not permitted to omit parts of the IP address instead of using CIDR notations. so use 192.0.2.0/24 instead of 192.0.2 ๐
โโ๏ธ๐
5.7. "exists" ๐งฉ
this mechanism is used to construct an arbitrary domain name that gets used for a DNS A record query ๐ช it allows for complicated schemes involving arbitrary parts of the mail envelope to decide what's permitted. bestie, the possibilities ๐ญ
exists = "exists" ":" domain-spec
the <domain-spec> is expanded as per Section 7. the resulting domain name is used for a DNS A RR lookup (even when the connection type is IPv6). if any A record is returned, this mechanism matches ๐ธ
domains can use this mechanism to specify arbitrarily complex queries ๐คฏ for example, suppose example.com publishes the record:
v=spf1 exists:%{ir}.%{l1r+-}._spf.%{d} -all
the <target-name> might expand to "1.2.0.192.someuser._spf.example.com". this makes fine-grained decisions possible at the level of the user and the client IP address ๐ซถโจ
6. Modifier Definitions ๐
modifiers are name/value pairs that provide additional information ๐ they always have an "=" separating the name and the value.
the modifiers defined in this document ("redirect" and "exp") SHOULD appear at the end of the record, after all mechanisms, though syntactically they can show up anywhere in the record ๐ the order of these two modifiers doesn't matter. these two modifiers MUST NOT appear in a record more than once each. if they do, then check_host() exits with a result of "permerror" ๐ซ
unrecognized modifiers MUST be ignored no matter where they show up, or how often ๐ this lets implementations conforming to this document gracefully handle records with modifiers that are defined in other specifications. so no drama, just vibes ๐ง๐
6.1. redirect: Redirected Query โจ
okay so the redirect modifier is basically the consolidation queen ๐. It's meant for putting both authorizations and policy into one shared set that everyone in a single ADMD can use. One record can control authorized hosts and policy for an arbitrary number of domains, which is so efficient it's giving main character energy ๐
redirect = "redirect" "=" domain-spec
If all mechanisms fail to match and a redirect modifier is present, here's what happens ๐ฆ The <domain-spec> part of the redirect gets expanded using the macro rules in Section 7. Then check_host() runs again with that resulting string as the <domain>. The <ip> and <sender> arguments stay exactly the same as in the current check_host() evaluation.
The result of this new check_host() evaluation becomes the result of the current one. The only twist: if no SPF record is found, or if the <target-name> is malformed, the result is a "permerror" instead of "none". ๐จ
Also, the newly queried domain can have its own redirect processing, so it can chain ๐
This facility is for organizations that want to apply the same record to multiple domains. For example:
la.example.com. TXT "v=spf1 redirect=_spf.example.com"
ny.example.com. TXT "v=spf1 redirect=_spf.example.com"
sf.example.com. TXT "v=spf1 redirect=_spf.example.com"
_spf.example.com. TXT "v=spf1 mx:example.com -all"
So mail from any of those three domains is described by the same record, which is a big administrative win ๐
โ ๏ธ Bestie, read this part: in general, domain "A" can't reliably use a redirect to another domain "B" that's not under the same administrative control. The <sender> stays the same, so there's no guarantee that domain "B"'s record will work for mailboxes in domain "A". This is especially true if domain "B" uses mechanisms involving local-parts. An "include" directive is usually the better move ๐ซถ
For clarity, any "redirect" modifier SHOULD appear as the very last term in a record. And any "redirect" modifier MUST be ignored if there is an "all" mechanism anywhere in the record. ๐
6.2. exp: Explanation ๐ฌ
explanation = "exp" "=" domain-spec
If check_host() results in a "fail" because of a mechanism match (like "-all"), and the "exp" modifier is present, then the explanation string gets computed as described below. If there's no "exp" modifier, then either a default explanation string or an empty explanation string MUST be returned to the calling application. ๐
The <domain-spec> gets macro expanded (see Section 7) and becomes the <target-name>. Then the DNS TXT RRset for the <target-name> is fetched.
If there are any DNS processing errors (any RCODE other than 0), or if no records come back, or if more than one record comes back, or if there are syntax errors in the explanation string, then it's as if no "exp" modifier was given. Just move on ๐ธ
The fetched TXT record's strings get concatenated with no spaces, then treated as an explain-string and macro-expanded. That final result is the explanation string. Implementations MAY limit the length of the resulting explanation string to make room for other protocol constraints and/or reasonable processing limits. Since the explanation string is meant for an SMTP response, and Section 2.4 of [RFC5321] says responses are in [US-ASCII], the explanation string MUST be limited to [US-ASCII]. ๐ฅ
Software that evaluates check_host() can use this string to pass along a short message or URL from the publishing domain. Software SHOULD make it crystal clear that the explanation string comes from a third party ๐ซข For example, it can prepend the macro string "%{o} explains: " to the explanation, like in the example in Section 8.4.
Say example.com has this record:
v=spf1 mx -all exp=explain._spf.%{d}
Here are some possible explanation TXT records at explain._spf.example.com:
"Mail from example.com should only be sent by its own servers."
-- a simple, constant message
"%{i} is not one of %{d}'s designated mail servers."
-- a message with a little more information, including the
IP address that failed the check
"See http://%{d}/why.html?s=%{S}&i=%{I}"
-- a complicated example that constructs a URL with the
arguments to check_host() so that a web page can be
generated with detailed, custom instructions
Note: during recursion into an "include" mechanism, an "exp" modifier from the <target-name> MUST NOT be used. But when executing a "redirect" modifier, an "exp" modifier from the original domain MUST NOT be used. ๐ง The reason: "include" is meant to cross administrative boundaries, so the explanation should be the one from the receiving ADMD. "redirect" is a tool for consolidating policy records within one ADMD, so the redirected explanation is the one that should win โจ
7. Macros ๐
When evaluating an SPF policy record, certain character sequences are meant to get replaced by parameters of the message or of the connection. These character sequences are called "macros" ๐ฆ
7.1. Formal Specification ๐
The ABNF description for a macro looks like this:
domain-spec = macro-string domain-end
domain-end = ( "." toplabel [ "." ] ) / macro-expand
toplabel = ( *alphanum ALPHA *alphanum ) /
( 1*alphanum "-" *( alphanum / "-" ) alphanum )
alphanum = ALPHA / DIGIT
explain-string = *( macro-string / SP )
macro-string = *( macro-expand / macro-literal )
macro-expand = ( "%{" macro-letter transformers *delimiter "}" )
/ "%%" / "%_" / "%-"
macro-literal = %x21-24 / %x26-7E
; visible characters except "%"
macro-letter = "s" / "l" / "o" / "d" / "i" / "p" / "h" /
"c" / "r" / "t" / "v"
transformers = *DIGIT [ "r" ]
delimiter = "." / "-" / "+" / "," / "/" / "_" / "="
The "toplabel" construction follows the letter-digit-hyphen (LDH) rule, plus extra top-level domain (TLD) restrictions. Check out Section 2 of [RFC3696] for background ๐
Some special cases to remember:
- A literal "%" is expressed by "%%".
- "%_" expands to a single " " space.
- "%-" expands to a URL-encoded space, viz., "%20".
7.2. Macro Definitions ๐
The following macro letters get expanded in term arguments:
s = <sender>
l = local-part of <sender>
o = domain of <sender>
d = <domain>
i = <ip>
p = the validated domain name of <ip> (do not use)
v = the string "in-addr" if <ip> is ipv4, or "ip6" if <ip> is ipv6
h = HELO/EHLO domain
<domain>, <sender>, and <ip> are defined in Section 4.1 ๐ซถ
The following macro letters are allowed only in "exp" text:
c = SMTP client IP (easily readable format)
r = domain name of host performing the check
t = current timestamp
7.3. Macro Processing Details โจ
okay bestie, this part is where macros get real ๐
. First up, a % that isn't followed by {, %, -, or _ is a syntax error, so this one is a no-go ๐
โโ๏ธ:
-exists:%(ir).sbl.example.org
That's incorrect and will cause check_host() to yield a "permerror" ๐. The legal version is:
-exists:%{ir}.sbl.example.org
Optional transformers are the following:
*DIGIT = zero or more digits
'r' = reverse value, splitting on dots by default
So if transformers or delimiters are provided, the replacement value for a macro letter gets split into parts separated by one or more of the specified delimiter characters ๐ช. After any reversal and/or removal of left-hand parts, the parts get glued back together with "." instead of the original splitting characters ๐งฉ.
By default, strings are split on "." (dots) ๐ฆ. Heads up: there's no special treatment for leading, trailing, or consecutive delimiters in input strings, so the list of parts might contain empty strings ๐ซ . Some older implementations of SPF prohibit trailing dots in domain names, so trailing dots SHOULD NOT be published ๐, although they MUST be accepted by implementations conforming to this document ๐ซถ. Macros can also specify delimiter characters that get used instead of "." โจ.
The "r" transformer is the reversal operation ๐. If the client IP address were 192.0.2.1, then %{i} would expand to "192.0.2.1" and %{ir} would expand to "1.2.0.192" ๐ญ.
The DIGIT transformer tells you how many right-hand parts to use, after optional reversal ๐. If a DIGIT is specified, the value MUST be nonzero ๐ซ0๏ธโฃ. If no DIGITs are specified, or if the value asks for more parts than are available, all the available parts get used ๐. So if the DIGIT was 5 but only 3 parts were available, the macro interpreter would pretend the DIGIT was 3 ๐. Implementations MUST support at least a value of 127, because that's the maximum number of labels in a domain name (less the zero-length label at the end) ๐.
The "s" macro expands to the <sender> argument, which is an email address with a local-part, an "@" character, and a domain ๐. The "l" macro expands to just the local-part, and the "o" macro expands to just the domain part ๐ง. These values stay the same during recursive and chained evaluations because of "include" and/or "redirect" ๐. Also, if the original <sender> had no local-part, the local-part was set to "postmaster" in initial processing (see Section 4.3) ๐ง.
For IPv4 addresses, both the "i" and "c" macros expand to the standard dotted-quad format ๐ธ.
For IPv6 addresses, the "i" macro expands to a dot-format address, and it's intended for use in %{ir} ๐ช. The "c" macro can expand to any of the hexadecimal colon-format addresses specified in Section 2.2 of [RFC4291] ๐. It's meant for humans to read ๐.
The "p" macro expands to the validated domain name of <ip> ๐. The procedure for finding that validated domain name is defined in Section 5.5 ๐ . If the <domain> is present in the list of validated domains, it SHOULD be used ๐ซถ. Otherwise, if a subdomain of the <domain> is present, it SHOULD be used ๐ท. Otherwise, any name from the list can be used ๐. If there are no validated domain names, or if a DNS error occurs, the string "unknown" is used ๐คทโโ๏ธ.
This macro SHOULD NOT be published. Seriously, don't do it (see Section 5.5 for the discussion) ๐จ.
The "h" macro expands to the parameter that was provided to the SMTP server via the HELO or EHLO SMTP verb ๐ฌ. For sessions where that verb was provided more than once, the most recent instance is the one used โฑ๏ธ.
The "r" macro expands to the name of the receiving MTA ๐ฌ. This SHOULD be a fully qualified domain name ๐. But if one doesn't exist (like when the checking is done by a Mail User Agent (MUA)), or if policy restrictions say otherwise, the word "unknown" SHOULD be substituted ๐ซ . The domain name can be different from the name found in the MX record that the client MTA used to locate the receiving MTA ๐งญ.
The "t" macro expands to the decimal representation of the approximate number of seconds since the Epoch (Midnight, January 1, 1970, UTC) at the time of the evaluation โฐ. That's the same value that the Portable Operating System Interface (POSIX) time() function returns in most standards-compliant libraries ๐ง .
When the result of macro expansion gets used in a domain name query, and the expanded domain name exceeds 253 characters (the maximum length of a domain name in this format), the left side is truncated to fit โ๏ธ. You keep removing successive domain labels (and their following dots) until the total length does not exceed 253 characters ๐งน.
Uppercase macros expand exactly like their lowercase equivalents, and then they're URL escaped ๐ . URL escaping MUST be performed for characters that are not in the "unreserved" set, which is defined in [RFC3986] ๐.
Care has to be taken by the sending ADMD so that macro expansion for legitimate email doesn't exceed the 63-character limit on DNS labels ๐ง. The local-part of email addresses, in particular, can have more than 63 characters between dots ๐.
To minimize DNS lookup resource requirements, it's better if sending ADMDs avoid using the "s", "l", "o", or "h" macros in conjunction with any mechanism directive ๐ง. These macros are powerful and allow per-user records to be published, but they severely limit the ability of implementations to cache results of check_host(), and they reduce the effectiveness of DNS caches ๐ฅฒ.
If no directive processed during the evaluation of check_host() contains an "s", "l", "o", or "h" macro, then the results of the evaluation can be cached on the basis of <domain> and <ip> alone, for as long as the DNS record involved with the shortest Time to Live (TTL) has not expired โญ.
7.4. Expansion Examples
okay now the fun part, the examples ๐! The <sender> is strong-bad@email.example.com. The IPv4 SMTP client IP is 192.0.2.3. The IPv6 SMTP client IP is 2001:db8::cb01. The PTR domain name of the client IP is mx.example.org ๐ท.
macro expansion
------- ----------------------------
%{s} strong-bad@email.example.com
%{o} email.example.com
%{d} email.example.com
%{d4} email.example.com
%{d3} email.example.com
%{d2} example.com
%{d1} com
%{dr} com.example.email
%{d2r} example.email
%{l} strong-bad
%{l-} strong.bad
%{lr} strong-bad
%{lr-} bad.strong
%{l1r-} strong
macro-string expansion
--------------------------------------------------------------------
%{ir}.%{v}._spf.%{d2} 3.2.0.192.in-addr._spf.example.com
%{lr-}.lp._spf.%{d2} bad.strong.lp._spf.example.com
%{lr-}.lp.%{ir}.%{v}._spf.%{d2}
bad.strong.lp.3.2.0.192.in-addr._spf.example.com
%{ir}.%{v}.%{l1r-}.lp._spf.%{d2}
3.2.0.192.in-addr.strong.lp._spf.example.com
%{d2}.trusted-domains.example.net
example.com.trusted-domains.example.net
IPv6:
%{ir}.%{v}._spf.%{d2} 1.0.b.c.0.0.0.0.
0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6._spf.example.com
8. Result Handling
This section gives guidance for SPF verifier operators on the various possible outputs of check_host() on a message ๐จ. Definitions of SPF results are in Section 2.6, and this section goes deeper on each one so you can build local policy for message handling ๐ง .
Every operating environment is different ๐. Some receivers should follow SPF strictly, and definitive treatment of messages that are explicitly unauthorized ("fail" and sometimes "softfail") is the norm there ๐. Others are more worried about "false negative" cases ๐ฌ. That worry is usually handled by just recording the result in the header and letting the message pass on for additional processing ๐. And some receivers use SPF as one of several inputs to the message-handling decision ๐งฉ. So there's no comprehensive normative requirement for message handling in response to any particular result ๐ซถ. This section is here to paint a complete picture of the likely cause of each result and, where available, the experience gained during experimental deployment ๐ญ.
There are essentially two classes of handling choices:
- Handling within the SMTP session that attempted to deliver the message, such as by returning a permanent SMTP error (rejection) or temporary SMTP error ("try again later") ๐ช;
- Permitting the message to pass (a successful SMTP reply code) and adding an additional header field that indicates the result returned by
check_host()and other salient details. This is discussed in more detail in Section 9 ๐ฌ.
8.1. None
With a "none" result, the SPF verifier has no information at all about the authorization, or lack thereof, of the client to use the checked identity or identities ๐คทโโ๏ธ. The check_host() function completed without errors but wasn't able to reach any conclusion ๐ซ .
8.2. Neutral
A "neutral" result means that although a policy for the identity was discovered, there's no definite assertion (positive or negative) about the client ๐.
A "neutral" result MUST be treated exactly like the "none" result ๐ โโ๏ธ. The distinction only exists for informational purposes ๐ . Treating "neutral" more harshly than "none" would discourage ADMDs from testing the use of SPF records (see Section 10.1) ๐ฅบ.
8.3. Pass
A "pass" result means the client is authorized to inject mail with the given identity ๐. In the sense of reputation, the domain can now be considered responsible for sending the message ๐. Further policy checks can now proceed with confidence in the legitimate use of the identity ๐ซถ. This is further discussed in Appendix G.1 โจ.
8.4. Fail
A "fail" result is an explicit statement that the client is not authorized to use the domain in the given identity ๐ซ. Disposition of SPF fail messages is a matter of local policy ๐งโโ๏ธ. See Appendix G.2 for considerations on developing local policy ๐.
If the checking software chooses to reject the mail during the SMTP transaction, then it SHOULD use an SMTP reply code of 550 (see [RFC5321]) and, if supported, the 5.7.1 enhanced status code (see [RFC3463], Section 3.8), in addition to an appropriate reply text ๐. The check_host() function will return either a default explanation string or one from the domain that published the SPF records (see Section 6.2) ๐ฃ. If the information doesn't originate with the checking software, it's good to make it clear that the text is provided by the sender's domain ๐ซถ. For example:
550 5.7.1 SPF MAIL FROM check failed:
550 5.7.1 The domain example.com explains:
550 5.7.1 Please see http://www.example.com/mailpolicy.html
If the checking software chooses not to reject the mail during the SMTP transaction, then it SHOULD add a Received-SPF or Authentication-Results header field (see Section 9) to communicate this result to downstream message processors ๐จ. While this is true for all SPF results, it's of particular importance for "fail" results, since the message is explicitly not authorized by the ADMD ๐จ๐.
8.5. Softfail ๐ธ
okay so a "softfail" result is basically somewhere between "fail" and "neutral"/"none" ๐ฅบ the ADMD thinks the host isn't authorized, but they're not ready to make a strong policy call. it's giving "maybe, not sure bestie" energy ๐ซถ so receiving software SHOULD NOT reject the message based solely on this result ๐ โโ๏ธ, but it MAY subject the message to closer scrutiny than normal ๐
the ADMD wants to discourage this host without being loud about it, so they want limited feedback when a "softfail" happens โจ like, the recipient's MUA could highlight the "softfail" status ๐ or the receiving MTA could send the sender a greylisting [RFC6647] message the first time, with a little note ๐ and then accept the message on a later attempt based on receiver policy. very lowkey, very iconic ๐
8.6. Temperror ๐ง๏ธ
a "temperror" means the SPF verifier ran into a transient (generally DNS) error while doing the check ๐ญ it's not the sender's fault necessarily, it's just a glitch. checking software can choose to accept or temporarily reject the message ๐ฆ and if it does reject during the SMTP transaction, the software SHOULD use an SMTP reply code of 451 and, if supported, the 4.4.3 enhanced status code (see Section 3.5 of [RFC3463]) ๐ง these errors can be caused by problems in either the sender's or receiver's DNS software, so both sides gotta check their stuff ๐ and see Appendix G.4 for considerations on developing local policy ๐
8.7. Permerror ๐
okay this one is the "nope, that's a real problem" result ๐ซ a "permerror" means the domain's published records could not be correctly interpreted. this definitely requires DNS operator intervention to be resolved, so the operator has to go fix it ๐ ๏ธ if the message is rejected during the SMTP transaction for this reason, the software SHOULD use an SMTP reply code of 550 and, if supported, the 5.5.2 enhanced status code (see [RFC3463], Section 3.6) ๐
be aware that if the ADMD uses macros (Section 7), it is possible that this result is due to the checked identities having an unexpected format ๐ and it's also possible that certain SPF verifiers generate this result because the input arguments had an unexpected format, so see Section 4.8 ๐ and for local policy stuff, check Appendix G.3 ๐
9. Recording the Result ๐
so downstream agents, like MUAs, need info to figure out how safe the message content looks โจ which is why it is RECOMMENDED that SMTP receivers record the result of SPF processing in the message header ๐ซถ for SPF verifier operators who want to record results in the header for internal filters or MUAs, there are two methods: Section 9.1 defines the Received-SPF field, which is the results field originally defined for SPF use ๐ and Section 9.2 talks about the Authentication-Results header field [RFC7001], which is newer and designed for SPF and other authentication methods ๐
both are in common use, so both are included here ๐ but they were designed for slightly different purposes, so it's important to note that ๐ Received-SPF is meant to include enough info to rebuild the SPF evaluation of the message, while Authentication-Results is only designed to relay the result and related output details that end users are likely to care about (e.g., what property of the message was actually authenticated and what it contained) ๐ leaving the reconstruction work to system logs and the Received field contents ๐ also, Received-SPF relies on agents within the receiving ADMD following the header field ordering rules of [RFC5321] and [RFC5322], while Authentication-Results has some provisions to protect against non-compliant implementations ๐ก๏ธ
an SPF verifier operator could use both to serve different downstream agents ๐ but in that case, be careful that both fields are conveying the same details, or unexpected results can happen ๐ญ
9.1. The Received-SPF Header Field ๐ฆ
the Received-SPF header field is a trace field (see [RFC5322], Section 3.6.7) and SHOULD be prepended to the existing header, above the Received: field that is generated by the SMTP receiver ๐ธ it MUST appear above all other Received-SPF fields in the message ๐ the header field has this format:
header-field = "Received-SPF:" [CFWS] result FWS [comment FWS]
[ key-value-list ] CRLF
result = "pass" / "fail" / "softfail" / "neutral" /
"none" / "temperror" / "permerror"
key-value-list = key-value-pair *( ";" [CFWS] key-value-pair )
[";"]
key-value-pair = key [CFWS] "=" ( dot-atom / quoted-string )
key = "client-ip" / "envelope-from" / "helo" /
"problem" / "receiver" / "identity" /
"mechanism" / name
identity = "mailfrom" ; for the "MAIL FROM" identity
/ "helo" ; for the "HELO" identity
/ name ; other identities
dot-atom = <unquoted word as per [RFC5322]>
quoted-string = <quoted string as per [RFC5322]>
comment = <comment string as per [RFC5322]>
CFWS = <comment or folding white space as per [RFC5322]>
FWS = <folding white space as per [RFC5322]>
CRLF = <standard end-of-line token as per [RFC5322]>
the header field SHOULD include a "(...)" style comment after the result ๐ with supporting info for the result, like <ip>, <sender>, and <domain> โจ
the following key-value pairs are designed for later machine parsing ๐ค SPF verifiers SHOULD give enough info so that the SPF results can be verified, so at minimum "client-ip", "helo", and, if the "MAIL FROM" identity was checked, "envelope-from" ๐ซถ
client-ip: the IP address of the SMTP clientenvelope-from: the envelope sender mailboxhelo: the host name given in the HELO or EHLO commandmechanism: the mechanism that matched (if no mechanisms matched, substitute the word "default")problem: if an error was returned, details about the errorreceiver: the host name of the SPF verifieridentity: the identity that was checked; see the <identity> ABNF rule
other keys MAY be defined by SPF verifiers ๐
SPF verifiers MUST make sure that the Received-SPF header field does not contain invalid characters, is not excessively long (see [RFC5322], Section 2.1.1), and does not contain malicious data that has been provided by the sender ๐ก๏ธ this one is a must, no exceptions ๐ค
here are some examples of the header field styles that could be generated ๐ง
Received-SPF: pass (mybox.example.org: domain of
myname@example.com designates 192.0.2.1 as permitted sender)
receiver=mybox.example.org; client-ip=192.0.2.1;
envelope-from="myname@example.com"; helo=foo.example.com;
Received-SPF: fail (mybox.example.org: domain of
myname@example.com does not designate
192.0.2.1 as permitted sender)
identity=mailfrom; client-ip=192.0.2.1;
envelope-from="myname@example.com";
Received-SPF: pass (mybox.example.org: domain of
myname@example.com designates 192.0.2.1 as permitted sender)
receiver=mybox.example.org; client-ip=192.0.2.1;
mechanism=ip4:192.0.2.1; envelope-from="myname@example.com";
helo=foo.example.com;
9.2. SPF Results in the Authentication-Results Header Field ๐
as mentioned in Section 9, the Authentication-Results header field is designed to communicate lists of tests a border MTA did and their results ๐ธ the specified elements of this field give less info than Received-SPF does, like so:
Authentication-Results: myhost.example.org; spf=pass
smtp.mailfrom=example.net
Received-SPF: pass (myhost.example.org: domain of
myname@example.com designates 192.0.2.1 as permitted sender)
receiver=mybox.example.org; client-ip=192.0.2.1;
envelope-from="myname@example.com"; helo=foo.example.com;
it is, however, possible to add CFWS in the "reason" part of an Authentication-Results header field and provide the equivalent info if you want ๐
for example, an expanded Authentication-Results header field might look like this (for a "MAIL FROM" check in this example) โจ
Authentication-Results: myhost.example.org; spf=pass
reason="client-ip=192.0.2.1; smtp.helo=foo.example.com"
smtp.mailfrom=user@example.net
10. Effects on Infrastructure ๐๏ธ
this section outlines the major implications that adopting this protocol will have on various entities involved in Internet email ๐ฌ it's meant to make clear where this protocol knowingly affects how those entities operate. this section is not a "how-to" manual, or a "best practices" document, and it is not a comprehensive list of what such entities ought to do in light of this specification ๐ โโ๏ธ
this section gives operational advice and instruction only ๐ก it is non-normative ๐ท
[RFC5598] describes the Internet email architecture ๐ and this section is organized based on the different segments of that architecture ๐งฉ
10.1. Sending Domains ๐ค
originating ADMDs (ADministrative Management Domains -- Sections 2.2.1 and 2.3 of [RFC5598]) that want to be compliant with this specification will need to figure out the list of relays ([RFC5598], Section 2.2.2) that they allow to use their domain name in the "HELO" and "MAIL FROM" identities when relaying to other ADMDs ๐ซถ it's recognized that forming this list is not just a simple technical exercise, because it involves policy decisions with both technical and administrative considerations ๐ง
10.1.1. DNS Resource Considerations ๐ง
minimizing the DNS resources needed for SPF lookups can be done by choosing directives that require less DNS info, and by placing lower-cost mechanisms earlier in the SPF record โก
Section 4.6.4 specifies the limits receivers have to use ๐ง it is essential to publish records that do not exceed these requirements ๐ค and it is also required to carefully weigh the cost and the maintainability of licit solutions ๐
for example, consider a domain set up like this ๐
example.com. IN MX 10 mx.example.com.
IN MX 20 mx2.example.com.
mx.example.com. IN A 192.0.2.1
mx2.example.com. IN A 192.0.2.129
say the administrative point is to authorize (pass) mx and mx2 while failing every other host ๐ then compare these solutions:
Best record:
example.com. IN TXT "v=spf1 ip4:192.0.2.1 ip4:192.0.2.129 -all"
Good record:
$ORIGIN example.com.
@ IN TXT "v=spf1 a:authorized-spf.example.com -all"
authorized-spf IN A 192.0.2.1
IN A 192.0.2.129
Expensive record:
example.com. IN TXT "v=spf1 mx:example.com -all"
Wasteful, bad record:
example.com. IN TXT "v=spf1 ip4:192.0.2.0/24 mx -all"
10.1.2. Administrator's Considerations ๐
Okay bestie, admin stuff time ๐
There might be some administrative considerations here. Using "a" over "ip4" or "ip6" lets hosts get renumbered super easily, but it costs one DNS query per receiver ๐ฅฒ. Using "mx" over "a" lets you swap out your set of mail hosts without drama. Unless those changes happen a lot, it's better to go with the less resource-intensive mechanisms, like "ip4" and "ip6" over "a", or "a" over "mx" ๐ง
In some specific cases, standard advice on record content is appropriate. Publishing SPF records for domains that send no mail is a well-established best practice โจ The record for a domain that sends no mail is:
www.example.com. IN TXT "v=spf1 -all"
Publishing SPF records for individual hosts is also best practice ๐ซถ The host name is generally the identity used in the 5321.HELO/.EHLO command. For messages with a null 5321.MailFrom, that host name is used as the domain for 5321.MailFrom SPF checks, in addition to being used for 5321.HELO/.EHLO-based SPF checks. The standard SPF record for an individual host that is involved in mail processing is:
relay.example.com. IN TXT "v=spf1 a -all"
Validating correct deployment is difficult ๐ญ [RFC6652] describes one mechanism for soliciting feedback on SPF failures. Another suggestion can be found in Appendix C.
Regardless of the method you use, understanding the ADMD's outbound mail architecture is essential to effective deployment. It's the main character of your rollout ๐
10.1.3. Bounces ๐
As explained in Section 2.4, [RFC5321] allows the MAIL FROM to be null, which is typical of some Delivery Status Notifications [RFC3464], commonly called email bounces. In this case, the only entity available for performing an SPF check is the "HELO" identity defined in Section 1.1.4. SPF functionality gets a glow-up when administrators make sure this identity is set correctly and has an appropriate SPF record โจ It's totally normal to have the "HELO" identity set to the host name instead of the domain. Zone file generation for significant numbers of hosts can be consolidated using the "redirect" modifier and scripted for initial deployment. Specific deployment advice is given above in Section 10.1.2 ๐
10.2. Receivers ๐ธ
SPF results can be used in combination with other methods to determine the final local disposition (either positive or negative) of a message. It can also be considered dispositive on its own.
An attempt to have one organization (sender) direct the email-handling policies of another (receiver) is inherently challenging and often controversial ๐ As stated elsewhere in this document, there is no comprehensive normative requirement for specific handling of a message based on SPF results. The information presented in Section 8 and in Appendix G is offered for receiver consideration when forming local handling policies.
The primary considerations are that SPF might return "pass" for mail that is ultimately harmful. For example, spammers can arrange for SPF to pass using disposable domain names, or virus or spam outbreaks can come from within trusted sources ๐ฌ SPF might also return "fail" for mail that is ultimately legitimate, like legitimate mail that has traversed a mail alias. It is important to take both of these cases into consideration when establishing local handling policy ๐ง
10.3. Mediators ๐ฆ
Mediators are a type of User Actor [RFC5598]. That means a mediator takes 'delivery' of a message and posts a 'submission' of a new message. The mediator can make the newly posted message as similar to or as different from the original message as they wish. Examples include mailing lists (see Section 5.3 of [RFC5598]) and ReSenders (Section 5.2 of [RFC5598]). This is discussed in [RFC5321], Section 3.9. For the operation of SPF, the essential concern is the email address in the 5321.MailFrom command for the new message.
Because SPF evaluation is based on the IP address of the "last" sending SMTP server, the address of the mediator will be used, rather than the address of the SMTP server that sent the message to the mediator. Some mediators keep the email address from the original message, while others use a new address.
If the address is the same as for the original message, and the original message had an associated SPF record, then the SPF evaluation will fail unless mitigations such as those described in Appendix D are used. Yikes, that's a plot twist ๐ญ
11. Security Considerations โ ๏ธ
11.1. Processing Limits ๐จ
As with most aspects of email, there are a number of ways malicious parties could use the protocol as an avenue for a DoS attack. The processing limits outlined in Section 4.6.4 are designed to prevent attacks such as the following:
- A malicious party could create an SPF record with many references to a victim's domain and send many emails to different SPF verifiers. Those SPF verifiers would then create a DoS attack. In effect, the SPF verifiers are being used to amplify the attacker's bandwidth by using fewer octets in the SMTP session than are used by the DNS queries. Using SPF verifiers also allows the attacker to hide the true source of the attack. This potential attack is based on large volumes of mail being transmitted.
- Whereas implementations of check_host() are supposed to limit the number of DNS lookups, malicious domains could publish records that exceed these limits in an attempt to waste computation effort at their targets when they send them mail. Malicious domains could also design SPF records that cause particular implementations to use excessive memory or CPU, or to trigger bugs. If a receiver is configured to accept mail with an SPF result of "temperror", such an attack might result in mail that would otherwise have been rejected due to an SPF "fail" result being accepted. This potential attack is based on specially crafted SPF records being used to exhaust DNS resources of the victim.
- Malicious parties could send a large volume of mail purporting to come from the intended target to a wide variety of legitimate mail hosts. These legitimate machines would then present a DNS load on the target as they fetched the relevant records.
- Malicious parties could, in theory, use SPF records as a vehicle for DNS lookup amplification for a DoS attack. In this scenario, the attacker publishes an SPF record in its own DNS that uses "a" and "mx" mechanisms directed toward the intended victim, e.g.,
"a:example.com a:foo.example.com a:bar.example.com ...". The attacker then distributes mail with a MAIL FROM value including its own domain in large volume to a wide variety of destinations. Any such destination operating an SPF verifier will begin querying all of the names associated with the "a" mechanisms in that record. The names used in the record needn't exist for the attack to be effective. Operational experience since the publication of [RFC4408] suggests that mitigation of this class of attack can be accomplished with minimal impact on the deployed base by having the verifier abort processing and return "permerror" (Section 2.6.7) as soon as more than two "void lookups" have been encountered (defined in Section 4.6.4).
Of these, the case of a third party referenced in the SPF record is the easiest for a DoS attack to effectively exploit. As a result, limits that might seem reasonable for an individual mail server can still allow an unreasonable amount of bandwidth amplification. Therefore, the processing limits need to be quite low ๐ฅ
11.2. SPF-Authorized Email May Contain Other False Identities ๐ญ
The "MAIL FROM" and "HELO" identity authorizations do not provide assurance about the authorization or authenticity of other identities used in the message. It is entirely possible for a malicious sender to inject a message using their own domain in the identities used by SPF, and have that domain's SPF record authorize the sending host. Yet the message can easily list other identities in its header. Unless the user or the MUA takes care to note that the authorized identity does not match the other, more commonly presented identities (such as the From: header field), the user might be lulled into a false sense of security ๐
11.3. Spoofed DNS and IP Data ๐ต๏ธโโ๏ธ
There are two aspects of this protocol that malicious parties could exploit to undermine the validity of the check_host() function:
- The evaluation of check_host() relies heavily on DNS. A malicious attacker could attack the DNS infrastructure and cause check_host() to see spoofed DNS data, and then return incorrect results. This could include returning "pass" for an <ip> value where the actual domain's record would evaluate to "fail". See [RFC3833] for a description of DNS weaknesses, and see [RFC4033] for a countermeasure.
- The client IP address, <ip>, is assumed to be correct. In a modern, correctly configured system, the risk of this not being true is nil ๐
11.4. Cross-User Forgery ๐ โโ๏ธ
By definition, SPF policies just map domain names to sets of authorized MTAs, not whole email addresses to sets of authorized users. Although the "l" macro (Section 7) provides a limited way to define individual sets of authorized MTAs for specific email addresses, it is generally impossible to verify, through SPF, the use of specific email addresses by individual users of the same MTA.
It is up to mail services and their MTAs to directly prevent cross-user forgery. Based on SMTP AUTH ([RFC4954]), users have to be restricted to using only those email addresses that are actually under their control (see Section 6.1 of [RFC6409]). Another means to verify the identity of individual users is message cryptography, such as Pretty Good Privacy (PGP) ([RFC4880]) or S/MIME ([RFC5751]) ๐
11.5. Untrusted Information Sources ๐ฉ
An SPF-compliant receiver gathers information from the SMTP commands it receives and from the published DNS records of the sending domain holder. Examples include the "HELO" domain name, the "MAIL FROM" address from the envelope, and SPF DNS records published by the domain holder. These parameters are not validated in the SMTP process ๐ฌ
All of this information is generated by actors outside of the authority of the receiver, so it is not guaranteed to be accurate or legitimate. Trust but verify, bestie ๐ง
11.5.1. Recorded Results ๐
This information, passed to the receiver in the Received-SPF: or Authentication-Results: trace fields, can be returned to the client MTA as an SMTP rejection message. If such an SMTP rejection message is generated, the information from the trace fields has to be checked for problems such as invalid characters and excessively long lines. Don't let the trace fields come for you ๐
11.5.2. External Explanations
okay so when the authorization check fails, the reject response can include an explanation string ๐ . both the sender AND the rejecting receiver need to remember that the publisher of the SPF record they checked wrote that text, not the receiver. ngl that means it can have sketchy URLs in it, or be rude or misleading ๐
explanations sent back to sender domains because of "exp" modifiers (Section 6.2) came from the sender policy the domain holders wrote themselves ๐ซถ. as long as those messages only get bounced with non-delivery notifications ([RFC3464]) to domains that publish the explanation strings in their own SPF DNS records, the only people affected are the original publishers of those SPF records.
in practice though, those notifications can get misdirected ๐ญ. for example, an MTA might accept an email and only later send the notification to a forged address, or an email forwarder might not send the bounce back to the original sender.
11.5.3. Macro Expansion
macros (Section 7) let senders stick arbitrary text (any non-null [US-ASCII] character) into the DNS queries that receivers send ๐ง. so receivers gotta be ready for hostile or unexpected content ๐
11.6. Privacy Exposure
checking SPF records makes DNS queries go out to the domain owner ๐. those queries can reveal who is sending email and probably which MTA it's heading to, especially when they're triggered by the "exists" mechanism. that can raise some privacy concerns, and how much they matter depends on local laws and on how the ADMD relates to the person sending the email ๐ธ
11.7. Delivering Mail Producing a "Fail" Result
operators who choose to deliver mail that gets a "fail" result from SPF are basically admitting content the purported sender never authorized ๐ฌ. yes, there are known failure modes that count as "false negatives", but choosing to let those messages in still increases end-user exposure to likely harm ๐ฅบ. this is extra true for domains belonging to known good actors who are typically well-behaved. unauthorized mail from them might deserve way more skepticism and content analysis ๐
SPF doesn't have the capacity to tell good actors from bad ones, though, and it doesn't handle the idea of known actors versus unknown ones either ๐คทโโ๏ธ. those notions are out of scope for this specification โจ
12. Collected ABNF
this section is normative ๐, so if anything in the earlier text doesn't match these ABNF fragments, this grammar wins ๐. see [RFC5234] for ABNF notation ๐. quick tea: literal text strings in quotes are case-insensitive, so "mx" matches "mx", "MX", "mX", and "Mx" ๐
record = version terms *SP
version = "v=spf1"
terms = *( 1*SP ( directive / modifier ) )
directive = [ qualifier ] mechanism
qualifier = "+" / "-" / "?" / "~"
mechanism = ( all / include
/ a / mx / ptr / ip4 / ip6 / exists )
all = "all"
include = "include" ":" domain-spec
a = "a" [ ":" domain-spec ] [ dual-cidr-length ]
mx = "mx" [ ":" domain-spec ] [ dual-cidr-length ]
ptr = "ptr" [ ":" domain-spec ]
ip4 = "ip4" ":" ip4-network [ ip4-cidr-length ]
ip6 = "ip6" ":" ip6-network [ ip6-cidr-length ]
exists = "exists" ":" domain-spec
modifier = redirect / explanation / unknown-modifier
redirect = "redirect" "=" domain-spec
explanation = "exp" "=" domain-spec
unknown-modifier = name "=" macro-string
; where name is not any known modifier
ip4-cidr-length = "/" ("0" / %x31-39 0*1DIGIT) ; value range 0-32
ip6-cidr-length = "/" ("0" / %x31-39 0*2DIGIT) ; value range 0-128
dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ]
ip4-network = qnum "." qnum "." qnum "." qnum
qnum = DIGIT ; 0-9
/ %x31-39 DIGIT ; 10-99
/ "1" 2DIGIT ; 100-199
/ "2" %x30-34 DIGIT ; 200-249
/ "25" %x30-35 ; 250-255
; conventional dotted-quad notation, e.g., 192.0.2.0
ip6-network = <as per Section 2.2 of [RFC4291]>
; e.g., 2001:db8::cd30
domain-spec = macro-string domain-end
domain-end = ( "." toplabel [ "." ] ) / macro-expand
toplabel = ( *alphanum ALPHA *alphanum ) /
( 1*alphanum "-" *( alphanum / "-" ) alphanum )
; LDH rule plus additional TLD restrictions
; (see Section 2 of [RFC3696] for background)
alphanum = ALPHA / DIGIT
explain-string = *( macro-string / SP )
macro-string = *( macro-expand / macro-literal )
macro-expand = ( "%{" macro-letter transformers *delimiter "}" )
/ "%%" / "%_" / "%-"
macro-literal = %x21-24 / %x26-7E
; visible characters except "%"
macro-letter = "s" / "l" / "o" / "d" / "i" / "p" / "h" /
"c" / "r" / "t" / "v"
transformers = *DIGIT [ "r" ]
delimiter = "." / "-" / "+" / "," / "/" / "_" / "="
name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." )
header-field = "Received-SPF:" [CFWS] result FWS [comment FWS]
[ key-value-list ] CRLF
result = "pass" / "fail" / "softfail" / "neutral" /
"none" / "temperror" / "permerror"
key-value-list = key-value-pair *( ";" [CFWS] key-value-pair )
[";"]
key-value-pair = key [CFWS] "=" ( dot-atom / quoted-string )
key = "client-ip" / "envelope-from" / "helo" /
"problem" / "receiver" / "identity" /
"mechanism" / name
identity = "mailfrom" ; for the "MAIL FROM" identity
/ "helo" ; for the "HELO" identity
/ name ; other identities
sender = Mailbox
ip = ip4-network / ip6-network
ALPHA = <A-Z / a-z as per [RFC5234]>
DIGIT = <0-9 as per [RFC5234]>
SP = <space character as per [RFC5234]>
dot-atom = <unquoted word as per [RFC5322]>
quoted-string = <quoted string as per [RFC5322]>
comment = <comment string as per [RFC5322]>
CFWS = <comment or folding white space as per [RFC5322]>
FWS = <folding white space as per [RFC5322]>
CRLF = <standard end-of-line token as per [RFC5322]>
13. Contributors and Acknowledgements
this document is largely built on the work of Meng Weng Wong, Mark Lentczner, and Wayne Schlitt ๐. even though lots of people helped, Meng, Mark, and Wayne did a huge chunk of the writing and editing ๐
this design owes a debt of parentage to [RMX] by Hadmut Danisch and to [DMP] by Gordon Fecyk ๐งฌ. the idea of using a DNS record to check whether an email address is legit has roots going further back, through messages on the namedroppers mailing list by Paul Vixie [Vixie] (based on a suggestion from Jim Miller) and by David Green [Green] ๐ฆ
Philip Gladstone brought in the concept of macros, which multiplied how expressive the language is and made per-user and per-IP lookups possible ๐ฅ
the authors of this document and [RFC4408] also want to thank the literally hundreds of people who helped develop this design ๐ซถ. there are way too many to name, but they include:
- The participants in the SPFbis working group.
- The folks on the spf-discuss mailing list.
- The folks on the SPAM-L mailing list.
- The folks on the IRTF ASRG mailing list.
- The folks on the IETF MARID mailing list.
- The folks on #perl.
14. IANA Considerations
14.1. The SPF DNS Record Type
per [RFC4408], IANA assigned the Resource Record Type and Qtype from the "Domain Name System (DNS) Parameters" registry for the SPF RR type with code 99 ๐. the format of this type is identical to the TXT RR [RFC1035]. the character content of the record is encoded as [US-ASCII].
okay, the tea is that studies have shown RRTYPE 99 hasn't seen any real use ๐, and its existence and the mechanism defined in [RFC4408] led to some interoperability issues. so its use is no longer appropriate for SPF version 1, and implementations are not to use it ๐ซ
IANA updated the "Resource Record (RR) TYPEs" registry to show that this document is the reference document for that RRTYPE โจ
14.2. The Received-SPF Mail Header Field
per [RFC3864], the "Received-SPF:" header field was added to the IANA "Permanent Message Header Field Names" registry ๐. here's the registration template:
Header field name: Received-SPF Applicable protocol: mail
([RFC5322]) Status: standard Author/Change controller: IETF
Specification document(s): RFC 7208
14.3. SPF Modifier Registry
IANA changed the reference for the "exp" and "redirect" modifiers in the "Modifier Names" registry, under Sender Policy Framework Parameters, from [RFC4408] to this document ๐ง. their status stays the same ๐
15. References
the reference entries weren't included in the part of the text I was given, so I'm not listing them here ๐
15.1. Normative References โจ
okay, these are the docs this spec needs to make sense, so they're basically its homework list ๐
[RFC1035] Mockapetris, P., "Domain names - implementation and
specification", STD 13, RFC 1035, November 1987.
[RFC1123] Braden, R., "Requirements for Internet Hosts - Application
and Support", STD 3, RFC 1123, October 1989.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
[RFC3463] Vaudreuil, G., "Enhanced Mail System Status Codes",
RFC 3463, January 2003.
[RFC3864] Klyne, G., Nottingham, M., and J. Mogul, "Registration
Procedures for Message Header Fields", BCP 90, RFC 3864,
September 2004.
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
Resource Identifier (URI): Generic Syntax", STD 66,
RFC 3986, January 2005.
[RFC4291] Hinden, R. and S. Deering, "IP Version 6 Addressing
Architecture", RFC 4291, February 2006.
[RFC5234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
Specifications: ABNF", STD 68, RFC 5234, January 2008.
[RFC5321] Klensin, J., "Simple Mail Transfer Protocol", RFC 5321,
October 2008.
[RFC5322] Resnick, P., Ed., "Internet Message Format", RFC 5322,
October 2008.
[RFC5598] Crocker, D., "Internet Mail Architecture", RFC 5598,
July 2009.
[RFC5890] Klensin, J., "Internationalized Domain Names for
Applications (IDNA): Definitions and Document Framework",
RFC 5890, August 2010.
[RFC7001] Kucherawy, M., "Message Header Field for Indicating
Message Authentication Status", RFC 7001, September 2013.
[US-ASCII]
American National Standards Institute (formerly United
States of America Standards Institute), "USA Code for
Information Interchange, X3.4", 1968.
ANSI X3.4-1968 has been replaced by newer versions with
slight modifications, but the 1968 version remains
definitive for the Internet.
15.2. Informative References ๐ฆ
these ones are the extra reading. helpful, but not required to get the core spec ๐
[BATV] Levine, J., Crocker, D., Silberman, S., and T. Finch,
"Bounce Address Tag Validation (BATV)", Work in Progress,
May 2008.
[DMP] Fecyk, G., "Designated Mailers Protocol", Work in
Progress, May 2004.
[Green] Green, D., "Domain-Authorized SMTP Mail", June 2002,
<http://www.mhonarc.org/archive/html/ietf-asrg/2003-03/
msg01525.html>.
[RFC1034] Mockapetris, P., "Domain names - concepts and facilities",
STD 13, RFC 1034, November 1987.
[RFC1983] Malkin, G., "Internet Users' Glossary", RFC 1983,
August 1996.
[RFC2308] Andrews, M., "Negative Caching of DNS Queries (DNS
NCACHE)", RFC 2308, March 1998.
[RFC2671] Vixie, P., "Extension Mechanisms for DNS (EDNS0)",
RFC 2671, August 1999.
[RFC2782] Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR for
specifying the location of services (DNS SRV)", RFC 2782,
February 2000.
[RFC3464] Moore, K. and G. Vaudreuil, "An Extensible Message Format
for Delivery Status Notifications", RFC 3464,
January 2003.
[RFC3696] Klensin, J., "Application Techniques for Checking and
Transformation of Names", RFC 3696, February 2004.
[RFC3833] Atkins, D. and R. Austein, "Threat Analysis of the Domain
Name System (DNS)", RFC 3833, August 2004.
[RFC3834] Moore, K., "Recommendations for Automatic Responses to
Electronic Mail", RFC 3834, August 2004.
[RFC4033] Arends, R., Austein, R., Larson, M., Massey, D., and S.
Rose, "DNS Security Introduction and Requirements",
RFC 4033, March 2005.
[RFC4408] Wong, M. and W. Schlitt, "Sender Policy Framework (SPF)
for Authorizing Use of Domains in E-Mail, Version 1",
RFC 4408, April 2006.
[RFC4632] Fuller, V. and T. Li, "Classless Inter-domain Routing
(CIDR): The Internet Address Assignment and Aggregation
Plan", BCP 122, RFC 4632, August 2006.
[RFC4880] Callas, J., Donnerhacke, L., Finney, H., Shaw, D., and R.
Thayer, "OpenPGP Message Format", RFC 4880, November 2007.
[RFC4954] Siemborski, R. and A. Melnikov, "SMTP Service Extension
for Authentication", RFC 4954, July 2007.
[RFC5507] IAB, Faltstrom, P., Austein, R., and P. Koch, "Design
Choices When Expanding the DNS", RFC 5507, April 2009.
[RFC5751] Ramsdell, B. and S. Turner, "Secure/Multipurpose Internet
Mail Extensions (S/MIME) Version 3.2 Message
Specification", RFC 5751, January 2010.
[RFC5782] Levine, J., "DNS Blacklists and Whitelists", RFC 5782,
February 2010.
[RFC6409] Gellens, R. and J. Klensin, "Message Submission for Mail",
STD 72, RFC 6409, November 2011.
[RFC6647] Kucherawy, M. and D. Crocker, "Email Greylisting: An
Applicability Statement for SMTP", RFC 6647, June 2012.
[RFC6648] Saint-Andre, P., Crocker, D., and M. Nottingham,
"Deprecating the "X-" Prefix and Similar Constructs in
Application Protocols", BCP 178, RFC 6648, June 2012.
[RFC6652] Kitterman, S., "Sender Policy Framework (SPF)
Authentication Failure Reporting Using the Abuse Reporting
Format", RFC 6652, June 2012.
[RFC6686] Kucherawy, M., "Resolution of the Sender Policy Framework
(SPF) and Sender ID Experiments", RFC 6686, July 2012.
[RFC6891] Damas, J., Graff, M., and P. Vixie, "Extension Mechanisms
for DNS (EDNS(0))", STD 75, RFC 6891, April 2013.
[RMX] Danisch, H., "The RMX DNS RR and method for lightweight
SMTP sender authorization", Work in Progress, May 2004.
[Vixie] Vixie, P., "Repudiating MAIL FROM", 2002,
<http://marc.info/?l=namedroppers&m=102298170127004&w=4>.
Appendix A. Extended Examples ๐
okay this is the fun part, the examples ๐ they're all built on one little DNS setup, so let's peek at that first ๐
; A domain with two mail servers, two hosts, and two servers
; at the domain name
$ORIGIN example.com.
@ MX 10 mail-a
MX 20 mail-b
A 192.0.2.10
A 192.0.2.11
amy A 192.0.2.65
bob A 192.0.2.66
mail-a A 192.0.2.129
mail-b A 192.0.2.130
www CNAME example.com.
; A related domain
$ORIGIN example.org.
@ MX 10 mail-c
mail-c A 192.0.2.140
; The reverse IP for those addresses
$ORIGIN 2.0.192.in-addr.arpa.
10 PTR example.com.
11 PTR example.com.
65 PTR amy.example.com.
66 PTR bob.example.com.
129 PTR mail-a.example.com.
130 PTR mail-b.example.com.
140 PTR mail-c.example.org.
; A rogue reverse IP domain that claims to be
; something it's not
$ORIGIN 0.0.10.in-addr.arpa.
4 PTR bob.example.com.
A.1. Simple Examples ๐
these show different records that could be published for example.com, and which values of <ip> would make check_host() return "pass" ๐ the <domain> in every case is "example.com", so keep that in mind as you read ๐
v=spf1 +all
-- any <ip> passes
v=spf1 a -all
-- hosts 192.0.2.10 and 192.0.2.11 pass
v=spf1 a:example.org -all
-- no sending hosts pass since example.org has no A records
v=spf1 mx -all
-- sending hosts 192.0.2.129 and 192.0.2.130 pass
v=spf1 mx:example.org -all
-- sending host 192.0.2.140 passes
v=spf1 mx mx:example.org -all
-- sending hosts 192.0.2.129, 192.0.2.130, and 192.0.2.140 pass
v=spf1 mx/30 mx:example.org/30 -all
-- any sending host in 192.0.2.128/30 or 192.0.2.140/30 passes
v=spf1 ptr -all
-- sending host 192.0.2.65 passes (reverse DNS is valid and is
in example.com)
-- sending host 192.0.2.140 fails (reverse DNS is valid, but not
in example.com)
-- sending host 10.0.0.4 fails (reverse IP is not valid)
v=spf1 ip4:192.0.2.128/28 -all
-- sending host 192.0.2.65 fails
-- sending host 192.0.2.129 passes
A.2. Multiple Domain Example ๐
these show what happens when records are related to each other ๐ซถ
example.org: "v=spf1 include:example.com include:example.net -all"
this record would be used if mail from example.org actually came through servers at example.com and example.net. example.org's designated servers are the union of example.com's and example.net's designated servers ๐ฅ
la.example.org: "v=spf1 redirect=example.org"
ny.example.org: "v=spf1 redirect=example.org"
sf.example.org: "v=spf1 redirect=example.org"
these records let a bunch of domains that all use the same mail system share that system's record ๐ง so only the mail system's record needs updating when the mail setup changes, and these domains' records never have to change โจ
A.3. DNS Blacklist (DNSBL) Style Example ๐ค
imagine that, on top of the domain records above, there are these too (see [RFC5782]):
$ORIGIN _spf.example.com.
mary.mobile-users A 127.0.0.2
fred.mobile-users A 127.0.0.2
15.15.168.192.joel.remote-users A 127.0.0.2
16.15.168.192.joel.remote-users A 127.0.0.2
the following records describe users at example.com who mail from arbitrary servers, or who mail from personal servers ๐ฆ
example.com:
v=spf1 mx
include:mobile-users._spf.%{d}
include:remote-users._spf.%{d}
-all
mobile-users._spf.example.com:
v=spf1 exists:%{l1r+}.%{d}
remote-users._spf.example.com:
v=spf1 exists:%{ir}.%{l1r+}.%{d}
A.4. Multiple Requirements Example ๐
say your sender policy needs both that the IP address is within a certain range AND that the reverse DNS for the IP matches ๐ there are a few ways to do this, including the following:
example.com. SPF ( "v=spf1 "
"-include:ip4._spf.%{d} "
"-include:ptr._spf.%{d} "
"+all" )
ip4._spf.example.com. SPF "v=spf1 -ip4:192.0.2.0/24 +all"
ptr._spf.example.com. SPF "v=spf1 -ptr +all"
this example shows how the "-include" mechanism can be useful, how an SPF record that ends in "+all" can be very restrictive, and the use of De Morgan's Law ๐ง โจ
That's the end of the appendix, the end of the spec, and honestly the end of this wild ride, bestie ๐๐
Appendix B. Changes in Implementation Requirements from RFC 4408 โจ
okay bestie, this is the "what changed since last time" tea ๐ซ every change from [RFC4408] is either (a) fixing an actual mistake in [RFC4408], or (b) adding docs based on what people learned from running SPF in the real world since it came out. no random vibes, just receipts ๐งพ
- Use of DNS RR type SPF (99) has been removed from the protocol; see [RFC6686] for background. ๐ bye bye type 99, go read [RFC6686] if you want the lore
- A new DNS-related processing limit based on "void lookups" has been added (Section 4.6.4). ๐ซง new guardrail just dropped
- Use of the ptr mechanism and the %p macro has been strongly discouraged (Sections 5.5 and 7.2). The ptr mechanism and the %p macro remain part of the protocol because they were found to be in use, but records ought to be updated to avoid them. ๐ not allowed to be a main character anymore, but it's still in the cast because people were using it
- Use of the "Authentication-Results" header field [RFC7001] as a possible alternative to use of the "Received-SPF" header field is discussed (Section 9.2). ๐ a lil side quest with a possible alternative header
- There have been a number of minor corrections to the ABNF to make it more clear and correct (Section 12). SPF library implementers should give the revised ABNF a careful review to determine if implementation changes are needed. ๐ library devs, pls look at the ABNF again, it got cleaned up
- Use of X- fields in the ABNF has been removed; see [RFC6648] for background. ๐๏ธ the
X-fields are gone from the ABNF, [RFC6648] explains why - Ambiguity about how to deal with invalid <domain-spec> after macro expansion has been documented. Depending on one specific behavior has to be avoided (Section 4.8). ๐ค the vague bit got written down, and relying on one specific behavior is a no-go
- General operational information has been updated and expanded based on eight years of post-[RFC4408] operations experience. See Section 10 and Appendices D through G below. ๐ eight years of real operations experience went into this, so see Section 10 and Appendices D through G
- Security considerations have been reviewed and updated (Section 11). ๐ the security section got a full glow-up, read it
Appendix C. Further Testing Advice ๐งช
okay so here's a cute trick. you can publish records that include a "tracking exists:" mechanism. then you look at your name server logs and boom, you get a rough list of who's checking you ๐ for example:
v=spf1 exists:_h.%{h}._l.%{l}._o.%{o}._i.%{i}._spf.%{d} ?all
the macro expansion here makes the sending HELO domain, the local-part of the sending email address, the domain part of the sending email address, and the IP address the connection came from all get embedded in an SPF query and logged in the sender's DNS logs ๐
this has been around since super early in the SPF project and lets senders collect their own data to check that their SPF records are correct ๐ง the best part? it doesn't need any special cooperation from SPF verifiers, unlike the newer feedback mechanisms ๐ a similar example, one of the earliest SPF records ever published, can still be found as of this writing at altavista.net ๐ธ
Appendix D. SPF/Mediator Interactions ๐ฆ
there are three places where techniques can be used to ameliorate unintended SPF failures with mediators. let's go in order ๐
D.1. Originating ADMDs
the beginning, when email is first sent:
- "Neutral" results could be given for IP addresses that might be forwarders, instead of "fail" results based on a list of known reliable forwarders. For example:
"v=spf1 mx ?exists:%{ir}.whitelist.example.org -all"
this would cause a lookup on a DNS White List (DNSWL) and cause a result of "fail" only for email not coming from either the domain's mx host(s) (SPF pass) or whitelisted sources (SPF neutral). โจ in effect, this outsources an element of sender policy to the maintainer of the whitelist
- The "MAIL FROM" identity could have additional information in the local-part that cryptographically identifies the mail as coming from an authorized source. In this case, an SPF record such as the following could be used:
"v=spf1 mx exists:%{l}._spf_verify.%{d} -all"
then, a specialized DNS server can be set up to serve the _spf_verify subdomain that validates the local-part. ๐ although this requires an extra DNS lookup, it only happens when the email would otherwise be rejected as not coming from a known good source
โ ๏ธ due to the 63-character limit for domain labels, this approach only works reliably if the local-part signature scheme is guaranteed to either only produce local-parts with a maximum of 63 characters or gracefully handle truncated local-parts.
the method used to secure the local-part is a local implementation issue; it need not be standard. an example of one way to do it can be found in [BATV] ๐งฉ
- Similarly, a specialized DNS server could be set up that will rate-limit the email coming from unexpected IP addresses. ๐ฆ
"v=spf1 mx exists:%{ir}._spf_rate.%{d} -all"
- SPF allows the creation of per-user policies for special cases. For example, the following SPF record and appropriate wildcard DNS records can be used: ๐
"v=spf1 mx redirect=%{l1r+}._at_.%{o}._spf.%{d}"
D.2. Mediators
the middle, when email is forwarded:
- Mediators can solve the problem by rewriting the "MAIL FROM" to be in their own domain. This means mail rejected from the external mailbox will have to be forwarded back to the original sender by the forwarding service. Various schemes to do this exist, though they vary widely in complexity and resource requirements on the part of the mediator. ๐
- Several popular MTAs can be forced from "alias" semantics to "mailing list" semantics by configuring an additional alias with "owner-" prepended to the original alias name (e.g., an alias of
friends: george@example.com, fred@example.orgwould need another alias of the formowner-friends: localowner). ๐ญ - Mediators could reject mail that would "fail" SPF if forwarded using an SMTP reply code of 551, User not local (see Section 3.4 of [RFC5321]) to communicate the correct target address to resend the mail to. ๐ฌ
D.3. Receiving ADMDs
the end, when email is received:
- If the owner of the external mailbox wishes to trust the mediator, they can direct the external mailbox's MTA to skip SPF tests when the client host belongs to the mediator. ๐ซถ
- Tests against other identities, such as the "HELO" identity, can be used to override a failed test against the "MAIL FROM" identity. ๐ช
- For larger domains, it might not be possible to have a complete or accurate list of forwarding services used by the owners of the domain's mailboxes. In such cases, whitelists of generally recognized forwarding services could be employed. ๐
Appendix E. Mail Services โ๏ธ
MSPs (Mail Service Providers -- Section 2.3 of [RFC5598]) that offer mail services to third-party domains, such as the sending of bulk mail, might want to adjust their configurations in light of the authorization check described in this document. If the domain part of the "MAIL FROM" identity used for such email uses one of the MSP's domains, then the provider needs only to ensure that its sending host is authorized by its own SPF record, if any. ๐
if the "MAIL FROM" identity does not use the MSP's domain, then extra care has to be taken. The SPF record format has several options for the third-party domain to authorize the service provider's MTAs to send mail on its behalf. ๐ซฃ for MSPs, such as ISPs, that have a wide variety of customers using the same MTA, steps are required to mitigate the risk of cross-customer forgery (see Section 11.4). ๐จ
Appendix F. MTA Relays ๐
relays are described in [RFC5598], Section 2.2.2. the authorization check generally precludes the use of arbitrary MTA relays between the sender and receiver of an email message. ๐
within an organization, MTA relays can be effectively deployed. However, for the purposes of this document, such relays are effectively transparent. The SPF authorization check is a check between border MTAs of different ADMDs. ๐งฑ
for mail senders, this means published SPF records have to authorize any MTAs that actually send across the Internet. Usually, these are just the border MTAs, as internal MTAs simply forward mail to these MTAs for relaying. ๐ค
the receiving ADMD will generally want to perform the authorization check at the boundary MTAs, including all secondary MXs. Internal MTAs (including MTAs that might serve as both boundary MTAs and internal relays from secondary MXs when they are processing the relayed mail stream) then do not perform the authorization test. ๐ to perform the authorization test other than at the boundary, the host that first transferred the message to the receiving ADMD has to be determined, which can be difficult to extract from the message header because (a) header fields can be forged or malformed, and (b) there's no standard way to encode that information such that it can be reliably extracted. Testing other than at the boundary is likely to produce unreliable results. ๐ฌ this is described further in Appendix D of [RFC7001].
Appendix G. Local Policy Considerations โ๏ธ
SPF results can be used in combination with other methods to determine the final local disposition (either positive or negative) of a message. It can also be considered dispositive on its own. ๐
G.1. Policy for SPF Pass
SPF "pass" results can be used in combination with "whitelists" of known "good" domains to bypass some or all additional pre-delivery email checks. Exactly which checks and how to determine appropriate whitelist entries have to be based on local conditions and requirements. ๐ท
G.2. Policy for SPF Fail
SPF "fail" results can be used to reject messages during the SMTP transaction based on either "MAIL FROM" or "HELO" identity results. This reduces resource requirements for various content-filtering methods and conserves bandwidth, since rejection can be done before the SMTP content is transferred. It also gives immediate feedback to the sender, who might then be able to resolve the issue. ๐ Due to some of the issues described in this section (Appendix G), SPF-based rejection does present some risk of rejecting legitimate email when rejecting email based on "MAIL FROM" results. ๐ญ
SPF "fail" results can alternately be used as one input into a larger set of evaluations that might, based on a combination of SPF "fail" results with other evaluation techniques, result in the email being marked negatively in some way (this might be via delivery to a special spam folder, modifying subject lines, or other locally determined means). Developing the details of such an approach has to be based on local conditions and requirements. Using SPF results in this way does not have the advantages of resource conservation and immediate feedback to the sender associated with SMTP rejection, but could produce fewer undesirable rejections in a well-designed system. Such an approach might result in email that was not authorized by the sending ADMD being unknowingly delivered to end users. ๐ซ
either general approach can be used, as they both leave a clear disposition of emails: either they are delivered in some manner or the sender is notified of the failure. Other dispositions, such as "dropping" or deleting email after acceptance, are inappropriate because they leave uncertainty and reduce the overall reliability and utility of email across the Internet. ๐ซ๐ง
G.3. Policy for SPF Permerror ๐จ
okay so, the permerror result (see Section 2.6.7) means the SPF processing module on the receiving end figured out that the SPF policy record it grabbed couldn't be interpreted ๐ญ. That's it. It tells you nothing solid about whether the envelope data is actually authorized, so it's giving "no idea" energy ๐
Like with every result, implementers get to decide what happens to a message that lands here ๐ . SMTP only gives you a few basic options, bestie.
Rejecting the message is one option, and honestly it's the only move a receiver has to make the difficulty visible while still protecting itself from messages that don't have a definite SPF result of any kind ๐ก๏ธ. BUT, if the SPF implementation is buggy and spitting out fake permerror results, only the sender finds out (because their mail got bounced), and the receiver who's actually using SPF stays totally in the dark ๐. Not iconic.
The less intrusive choice is to deliver the message, maybe with some kind of annotation about the problem and/or logging of a similar sort ๐. Lowkey though, this won't be the vibe for SPF verifier operators who want to check as strictly as possible, and this kind of quiet reporting usually isn't super effective either ๐ฅฒ.
You can also hand the decision to the SPF verifier operator instead of the implementer ๐. This makes sense because the choice is usually a matter of local policy rather than something with one universal answer. The catch is it adds one more layer of complexity to an already messy environment ๐ตโ๐ซ.
Bottom line: implementers AND SPF verifier operators both need to be super careful about every choice and outcome when handling SPF results ๐ซถโจ.
G.4. Policy for SPF Temperror โณ
so the temperror result (see Section 2.6.6) is what you get when the SPF processing module at the receiver couldn't fetch an SPF policy record because of a (probably) transient condition ๐ง๏ธ. Again, no real info about whether the envelope data is authorized. Main character energy? Nope, just a shrug ๐คทโโ๏ธ.
Same deal as always: implementers have to pick what to do with a message that gets this result, and SMTP only offers a few basic options ๐.
Deferring the message is an option, and it's the one thing a receiver can do to flag the problem while protecting itself from messages without a definite SPF result ๐ฆ. HOWEVER, if the SPF implementation is glitchy and keeps returning spurious temperror results, only the sender finds out, and only after the mail times out of the sending queue and gets rejected ๐ฉ. The receiver using SPF? Still clueless ๐.
Also, queue lifetimes can be really long, so mail might get deferred over and over for several days ๐. That means any awareness the sender has about the problem could be super delayed ๐ข. If temperrors stick around across multiple delivery attempts, it might be better to treat the error as permanent so the message isn't stuck in transit forever ๐โโ๏ธ๐จ.
The less intrusive move is, once again, to deliver the message with maybe an annotation and/or some logging ๐. Not ideal for operators who want the strictest checking possible, and passive reporting isn't usually very effective ๐ฌ.
And yes, you can give this decision to the SPF verifier operator instead of the implementer, since it's usually local policy and not a one-size-fits-all situation ๐ง. It just adds another piece of complexity to an already non-trivial environment ๐.
So, same as before: both implementers and SPF verifier operators need to be cautious of all choices and outcomes when handling SPF results ๐ ๐.
Author's Address โจ
Scott Kitterman
Kitterman Technical Services
3611 Scheel Dr.
Ellicott City, MD 21042
United States of America
EMail: scott@kitterman.com
๐ฆ where this page comes from
- ietf: rfc7208 2014-04
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms