RFC 4408
Sender Policy Framework (SPF) for Authorizing Use of Domains in E-Mail, Version 1
RFC 4408 ๐ Sender Policy Framework (SPF) for Authorizing Use of Domains in E-Mail, Version 1 (Part 1 of 9)
okay bestie, here's the title block tea โ This is RFC 4408, from the Network Working Group, and it's written by M. Wong and W. Schlitt. It's in the Experimental category, dated April 2006. It doesn't list anything it obsoletes or updates, so no drama there ๐ซถ
- RFC number: 4408
- Title: Sender Policy Framework (SPF) for Authorizing Use of Domains in E-Mail, Version 1
- Authors: M. Wong, W. Schlitt
- Category: Experimental
- Date: April 2006
- Obsoletes: none listed
- Updates: none listed
Status of This Memo ๐
this memo defines an Experimental Protocol for the Internet community ๐ฆ it does not specify an Internet standard of any kind, so don't go calling it one lol. they're asking for discussion and suggestions for improvement, and distribution of the memo is unlimited โจ
Copyright Notice ๐
Copyright (C) The Internet Society (2006). that's the whole vibe ๐
IESG Note ๐
ok this part is lowkey important, so read it ๐
the following documents, RFC 4405, RFC 4406, RFC 4407, and RFC 4408, were published at the same time as Experimental RFCs. there is no general technical consensus, and efforts to reconcile the two approaches have failed ๐ so these documents did not get full IETF review. they're published "AS-IS" to document the different approaches as they were considered in the MARID working group ๐ธ
the IESG takes no position on which approach is better, and it warns that there are serious open issues with each approach, plus concerns about using them together ๐ they think documenting the different approaches does less harm than not documenting them, which, honestly, same ๐ซถ
now the spicy bit ๐ถ๏ธ the Sender ID experiment may use DNS records that were created for the current SPF experiment or earlier versions in this set of experiments. depending on what's in the record, Sender-ID heuristics might get applied incorrectly to a message. and depending on what actions the recipient ties to those heuristics, the message may not be delivered, or it may be discarded when it arrives ๐ญ
so participants relying on Sender ID experiment DNS records are warned that they may lose valid messages in this situation. participants publishing SPF experiment DNS records should consider the advice in section 3.4 of RFC 4406, and they may wish to publish both v=spf1 and spf2.0 records to avoid the conflict ๐ง
โ ๏ธ Resent-* header fields: the Sender-ID experiment usesResent-*header fields in a way that results in failure to receive legitimate email when interacting with standards-compliant systems. that includes automatic forwarders that comply with the standards by not addingResent-*headers, and systems that comply with RFC 822 but haven't implemented RFC 2822Resent-*semantics yet. it would be inappropriate to advance Sender-ID on the standards track without resolving this interoperability problem ๐
finally, the community is invited to watch how the two approaches do during the two years after publication, so a community consensus can be reached later ๐
Abstract ๐ฆ
e-mail on the Internet can be forged in a bunch of ways ๐ the big problem is that existing protocols place no restriction on what a sending host can use as the reverse-path of a message, or the domain given on the SMTP HELO/EHLO commands. this document describes version 1 of the Sender Policy Framework (SPF) protocol. with it, a domain can explicitly authorize the hosts that are allowed to use its domain name, and a receiving host can check that authorization โจ
Table of Contents ๐ง
1. Introduction ....................................................4
1.1. Protocol Status ............................................4
1.2. Terminology ................................................5
2. Operation .......................................................5
2.1. The HELO Identity ..........................................5
2.2. The MAIL FROM Identity .....................................5
2.3. Publishing Authorization ...................................6
2.4. Checking Authorization .....................................6
2.5. Interpreting the Result ....................................7
2.5.1. None ................................................8
2.5.2. Neutral .............................................8
2.5.3. Pass ................................................8
2.5.4. Fail ................................................8
2.5.5. SoftFail ............................................9
2.5.6. TempError ...........................................9
2.5.7. PermError ...........................................9
3. SPF Records .....................................................9
3.1. Publishing ................................................10
3.1.1. DNS Resource Record Types ..........................10
3.1.2. Multiple DNS Records ...............................11
3.1.3. Multiple Strings in a Single DNS record ............11
3.1.4. Record Size ........................................11
3.1.5. Wildcard Records ...................................11
4. The check_host() Function ......................................12
4.1. Arguments .................................................12
4.2. Results ...................................................13
4.3. Initial Processing ........................................13
4.4. Record Lookup .............................................13
4.5. Selecting Records .........................................13
4.6. Record Evaluation .........................................14
4.6.1. Term Evaluation ....................................14
4.6.2. Mechanisms .........................................15
4.6.3. Modifiers ..........................................15
4.7. Default Result ............................................16
4.8. Domain Specification ......................................16
5. Mechanism Definitions ..........................................16
5.1. "all" .....................................................17
5.2. "include" .................................................18
5.3. "a" .......................................................19
5.4. "mx" ......................................................20
5.5. "ptr" .....................................................20
5.6. "ip4" and "ip6" ...........................................21
5.7. "exists" ..................................................22
6. Modifier Definitions ...........................................22
6.1. redirect: Redirected Query ................................23
6.2. exp: Explanation ..........................................23
7. The Received-SPF Header Field ..................................25
8. Macros .........................................................27
8.1. Macro Definitions .........................................27
8.2. Expansion Examples ........................................30
9. Implications ...................................................31
9.1. Sending Domains ...........................................31
9.2. Mailing Lists .............................................32
9.3. Forwarding Services and Aliases ...........................32
9.4. Mail Services .............................................34
9.5. MTA Relays ................................................34
10. Security Considerations .......................................35
10.1. Processing Limits ........................................35
10.2. SPF-Authorized E-Mail May Contain Other False
Identities ...............................................37
10.3. Spoofed DNS and IP Data ..................................37
10.4. Cross-User Forgery .......................................37
10.5. Untrusted Information Sources ............................38
10.6. Privacy Exposure .........................................38
11. Contributors and Acknowledgements .............................38
12. IANA Considerations ...........................................39
12.1. The SPF DNS Record Type ..................................39
12.2. The Received-SPF Mail Header Field .......................39
13. References ....................................................39
13.1. Normative References .....................................39
13.2. Informative References ...................................40
Appendix A. Collected ABNF .......................................42
Appendix B. Extended Examples ....................................44
B.1. Simple Examples ..........................................44
B.2. Multiple Domain Example ..................................45
B.3. DNSBL Style Example ......................................46
B.4. Multiple Requirements Example ............................46
1. Introduction โจ
ok so the current E-Mail infrastructure has this property: any host injecting mail into the mail system can claim to be any domain name it wants ๐ญ hosts can do this at a bunch of levels, mainly the session, the envelope, and the mail headers. this is sometimes desirable, but it's a major obstacle to reducing Unsolicited Bulk E-Mail (UBE, aka spam). and honestly? lots of domain name holders are understandably worried about how easily other entities can use their domain names, often with malicious intent ๐ โโ๏ธ
so this document defines a protocol where domain owners can authorize hosts to use their domain name in the "MAIL FROM" or "HELO" identity ๐ compliant domain holders publish Sender Policy Framework (SPF) records that say which hosts are allowed to use their names. compliant mail receivers use those published 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 ๐
and here's the bonus for receivers ๐ซถ once an identity has been verified, local policy decisions about the mail can be made based on the sender's domain, not the host's IP address. that's a big deal because domain name reputation is likely to be more accurate than IP address reputation ๐ and if a claimed identity fails verification, local policy can take stronger action against that email, like rejecting it ๐ฅ
1.1. Protocol Status ๐
so SPF has been in development since summer 2003 and has been deployed beyond the developers since December 2003 ๐ธ its design slowly evolved until spring 2004, and then it stabilized. there have been quite a few forms of SPF, some written up as documents, some submitted as Internet Drafts, and loads discussed in development forums ๐ง
the goal of this document is to clearly document the protocol defined by earlier draft specifications of SPF, as used in existing implementations. this version of SPF is sometimes called "SPF Classic" ๐ฆ it's understood that particular implementations and deployments may differ from this work and build on it. it's hoped that we still captured the common understanding of SPF version 1 ๐
1.2. Terminology ๐โจ
okay so the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document get read the way [RFC2119] describes, so these ten words are the ones with real weight ๐ ๐
This doc is about the bit of a mail message that people call the "envelope sender", "return path", "reverse path", "bounce address", "2821 FROM", or "MAIL FROM" ๐ฌ Those names are either not well defined or get tossed around super casually, so this doc defines the "MAIL FROM" identity properly in Section 2.2. Other terms that look like the common ones, like "reverse-path", only mean what the normative documents say they mean. No mixing it up, bestie ๐ง
2. Operation ๐ฆ
2.1. The HELO Identity ๐
the "HELO" identity comes from either the SMTP HELO or EHLO command (see [RFC2821]) ๐ Those commands tell you who the SMTP client is, aka the sending host for the session. Here's the tea: the rules for what goes in the EHLO or HELO command aren't always clear to the sending party, so SPF clients need to be ready for the "HELO" identity to be malformed or an IP address literal ๐ At the time of this writing, lots of legit E-Mails get delivered with invalid HELO domains, which is so messy but also so real ๐ญ
It is RECOMMENDED that SPF clients not only check the "MAIL FROM" identity, but also check the "HELO" identity separately by running the check_host() function (Section 4) on the "HELO" identity as the <sender> โจ
2.2. The MAIL FROM Identity ๐ฎ
the "MAIL FROM" identity comes from the SMTP MAIL command (see [RFC2821]) ๐ That command gives you the "reverse-path" for a message, which is usually the sender mailbox. It's also where notification messages get sent if the message runs into delivery problems ๐จ
[RFC2821] lets the reverse-path be null (see Section 4.5.5 in RFC 2821) ๐ When it's null, there's no explicit sender mailbox, so the message can be assumed to be a notification from the mail system itself, which is giving "automated and proud of it" ๐ค In that case, this doc defines the "MAIL FROM" identity as the mailbox made up of the localpart "postmaster" and the "HELO" identity (which may or may not have been checked separately before) ๐
SPF clients MUST check the "MAIL FROM" identity. They check it by running the check_host() function on the "MAIL FROM" identity as the <sender> ๐ซถ
2.3. Publishing Authorization ๐ฃ
An SPF-compliant domain MUST publish a valid SPF record as described in Section 3. Main character energy for the domain, honestly ๐ That record authorizes the use of the domain name in the "HELO" and "MAIL FROM" identities by the MTAs it names ๐
If domain owners choose to publish SPF records, it is RECOMMENDED that they end in "-all", or redirect to other records that do, so that a definitive determination of authorization can be made ๐ฅ
Domain holders can publish SPF records that explicitly authorize no hosts if mail should never come from that domain ๐ โโ๏ธ Very dramatic, very valid ๐
When you change SPF records, be careful to leave a transition period, so the old policy stays valid until all the legitimate E-Mail has been checked ๐ฆ Don't yeet the old record too early, bestie ๐ญ
2.4. Checking Authorization ๐
A mail receiver can run a set of SPF checks on every mail message it gets ๐จ An SPF check tests whether a client host is authorized to send mail with a given identity. Usually a receiving MTA does these checks, but they can happen somewhere else in the mail processing chain, as long as the info needed is available and reliable ๐ธ At minimum the "MAIL FROM" identity MUST be checked, and it is RECOMMENDED that the "HELO" identity also gets checked beforehand ๐
Without explicit approval from the domain owner, 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 9.2), but some don't change any other identities in the message. The scenario in Section 9.3, sub-section 1.2, is another example ๐ง Documents that define other identities should define the method for that 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 can affect whether a particular SPF check gets done at all. For example, if the sending host's IP address is on a local white list, that might skip all the other tests and let every bit of mail from that host through ๐
When a mail receiver decides to do an SPF check, it MUST use a correctly-implemented check_host() function (Section 4) evaluated with the correct parameters. Even though the test as a whole is optional, once someone's decided to run it, it has to be run as specified so the correct semantics are kept between publisher and receiver ๐ซถ No half-measures, bestie ๐ค
To make the test, the mail receiver MUST evaluate the check_host() function with the arguments set like this:
<ip>- the IP address of the SMTP client that is emitting the mail, either IPv4 or IPv6 ๐<domain>- the domain portion of the "MAIL FROM" or "HELO" identity ๐<sender>- the "MAIL FROM" or "HELO" identity ๐
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, along with its associated problems (see Section 2.1). In those cases, check_host() is defined in Section 4.3 to return a "None" result ๐ฆ
Invalid, malformed, or non-existent domains make SPF checks return "None", because no SPF record can be found ๐ซ Even so, it's long been the policy of many MTAs to reject E-Mail from those domains, especially when the "MAIL FROM" is invalid ๐ โโ๏ธ To stop people getting around SPF records, rejecting E-Mail from invalid domains should be considered โจ
Implementations must take care to correctly pull the <domain> out of the data given with the SMTP MAIL FROM command, because lots of MTAs will still accept things like source routes (see [RFC2821], Appendix C), the %-hack (see [RFC1123]), and bang paths (see [RFC1983]) ๐ These archaic features have been used maliciously to get around security systems, so this is a serious one, bestie ๐ญ๐ฅ
2.5. Interpreting the Result ๐
This section explains how software that does the authorization should read the results of the check_host() function ๐ The authorization check SHOULD be done while the SMTP transaction that sends the mail is being processed. That way, errors can be sent straight back to the sending MTA through SMTP replies ๐
Doing the authorization after the SMTP transaction is finished can cause problems, like: (1) it can be hard to pull the required info accurately out of potentially deceptive headers; (2) legit E-Mail can fail because the sender's policy may have changed since then ๐
Generating non-delivery notifications to forged identities that failed the authorization check is generally abusive, and it goes against the explicit wishes of the identity owner ๐ซ Not it, forgers ๐
2.5.1. None ๐ซ๏ธ
A "None" result means that either no records were published by the domain, or no checkable sender domain could be figured out from the given identity. The checking software can't tell whether or not the client host is authorized ๐คทโโ๏ธ
2.5.2. Neutral ๐
The domain owner has explicitly said they cannot or do not want to state whether or not the IP address is authorized ๐ A "Neutral" result MUST be treated exactly like the "None" result. The only difference is for informational purposes. Treating "Neutral" more harshly than "None" would discourage domain owners from testing the use of SPF records (see Section 9.1), and nobody wants that ๐งช
2.5.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 go ahead with confidence in the legit use of the identity ๐
2.5.4. Fail โ
A "Fail" result is an explicit statement that the client is NOT authorized to use the domain in the given identity ๐ซ Hard no, bestie. The checking software can choose to mark the mail based on this, or reject it outright ๐
If the checking software chooses to reject the mail during the SMTP transaction, it SHOULD use an SMTP reply code of 550 (see [RFC2821]) and, if it's supported, the 5.7.1 Delivery Status Notification (DSN) code (see [RFC3464]), along with an appropriate reply text โจ The check_host() function can return either a default explanation string, or one from the domain that published the SPF records (see Section 6.2). If the info didn't come from the checking software, it should be made clear that the text was 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
2.5.5. SoftFail ๐ท
A "SoftFail" result should be treated as somewhere between a "Fail" and a "Neutral" ๐ฅน The domain believes the host is not authorized, but it isn't willing to make that strong of a statement. Receiving software SHOULD NOT reject the message based solely on this result, but it MAY subject the message to closer scrutiny than normal ๐
The domain owner wants to discourage use of this host, so they want limited feedback when a "SoftFail" result happens ๐คซ For example, the recipient's Mail User Agent (MUA) could highlight the "SoftFail" status ๐ Or the receiving MTA could use a technique called "greylisting", where it issues an SMTP reply code of 451 (4.3.0 DSN code) with a note the first time the message comes in, but accepts it the second time ๐ง
2.5.6. TempError โณ
A "TempError" result means the SPF client ran into a transient error while doing the check โ๏ธ The checking software can choose to accept the message or temporarily reject it. If the message gets rejected during the SMTP transaction for this reason, the software SHOULD use an SMTP reply code of 451 and, if it's supported, the 4.4.3 DSN code ๐ฆ
2.5.7. PermError ๐ง
A "PermError" result means the domain's published records couldn't be interpreted correctly. This is an error condition that needs manual intervention to be resolved, unlike the TempError result ๐ ๏ธ Be aware that if the domain owner uses macros (Section 8), this result might be because the checked identities have an unexpected format ๐
3. SPF Records ๐
An SPF record is a DNS Resource Record (RR) that declares which hosts are, and are not, authorized to use a domain name for the "HELO" and "MAIL FROM" identities ๐ Loosely, the record splits all hosts into permitted and not-permitted sets, though some hosts might land in neither category ๐ซง
The SPF record is a single string of text ๐ An example record is the following:
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" ๐ Version, directives, done, bestie ๐
3.1. Publishing ๐ฎ
okay bestie, so domain owners who want to be SPF compliant HAVE to publish SPF records for the hosts used in the "MAIL FROM" and "HELO" identities ๐ The records go at the host name they're about, NOT at a subdomain under it (unlike SRV records, which do the subdomain thing ๐). And it's the same whether you use the TXT or the SPF RR type (more on that in Section 3.1.1) โจ
The example from Section 3 could get published with these lines in a domain zone file ๐ธ
example.com. TXT "v=spf1 +mx a:colo.example.com/28 -all"
smtp-out.example.com. TXT "v=spf1 a -all"
โ ๏ธ If you publish with TXT records, watch out for OTHER TXT records already sitting there for other reasons ๐ญ They can cause size-limit drama (see Section 3.1.4).
3.1.1. DNS Resource Record Types ๐
This doc makes a brand-new DNS RR type called SPF, code 99 ๐ฆ It has the exact same format as the TXT RR [RFC1035], and for either type, the record's characters are encoded as [US-ASCII] ๐
Real talk: using a TXT record is NOT the ideal situation ๐ But it's necessary, because a bunch of DNS server and resolver implementations in common use can't handle the new RR type yet. The two-type setup is basically a forward path toward the better solution, which is using an RR type that's reserved just for this purpose ๐ง
An SPF-compliant domain name SHOULD have SPF records of BOTH RR types ๐ A compliant domain name MUST have a record of at least one type. And if a domain has both types, they MUST have identical content ๐ซถ So instead of publishing just one record like in Section 3.1, it's better to do this:
example.com. IN TXT "v=spf1 +mx a:colo.example.com/28 -all"
example.com. IN SPF "v=spf1 +mx a:colo.example.com/28 -all"
The example RRs in this doc are shown with the TXT type, but they could be published with the SPF type or with both types โจ
3.1.2. Multiple DNS Records ๐ซ
A domain name MUST NOT have multiple records that would make an authorization check select more than one record ๐ญ Check Section 4.5 for the selection rules.
3.1.3. Multiple Strings in a Single DNS record ๐งฉ
As defined in [RFC1035] sections 3.3.14 and 3.3, a single text DNS record (either TXT or SPF RR types) can be made up of more than one string ๐ If a published record has multiple strings, the record MUST be treated as if those strings are glued together with NO spaces added between them ๐ฅ For example:
IN TXT "v=spf1 .... first" "second string..."
MUST be treated as equivalent to
IN TXT "v=spf1 .... firstsecond string..."
Multi-string SPF or TXT records are super useful for building records that would otherwise go over the 255-byte max length of one string inside a single TXT or SPF RR ๐
3.1.4. Record Size ๐
The published SPF record for a given domain name SHOULD stay small enough that the query result fits within 512 octets ๐ That keeps even older DNS implementations from falling over to TCP ๐ฉ The answer size depends on a lot of stuff outside this doc's scope, so all we can give you is this guideline: if the combined length of the DNS name and the text of ALL the records of a given type (TXT or SPF) is under 450 characters, the DNS answers should fit in UDP packets ๐ฆ Also, when you're computing sizes for TXT-format queries, remember to count any OTHER TXT records published at that domain name ๐ Records that are too long to fit in a single UDP packet MAY be silently ignored by SPF clients ๐
3.1.5. Wildcard Records ๐
Using wildcard records for publishing is not recommended, and you gotta be careful if you do ๐ If a domain publishes wildcard MX records, it may want to publish wildcard declarations too, subject to the same requirements and problems. The big one: the declaration must be repeated for any host that has ANY RR records at all, and for subdomains of that host. For example, the example in [RFC1034], Section 4.3.3, could be extended with the following:
X.COM. MX 10 A.X.COM
X.COM. TXT "v=spf1 a:A.X.COM -all"
*.X.COM. MX 10 A.X.COM
*.X.COM. TXT "v=spf1 a:A.X.COM -all"
A.X.COM. A 1.2.3.4
A.X.COM. MX 10 A.X.COM
A.X.COM. TXT "v=spf1 a:A.X.COM -all"
*.A.X.COM. MX 10 A.X.COM
*.A.X.COM. TXT "v=spf1 a:A.X.COM -all"
Notice that SPF records have to be repeated TWICE for every name in the domain: once for the name itself, and once with a wildcard to cover the tree under that name ๐ญ๐ซถ
Wildcards are generally discouraged, bestie, because they make every name under the domain exist, so queries for random names will never return RCODE 3 (Name Error) ๐ฅฒ
4. The check_host() Function โจ
The check_host() function fetches SPF records, parses them, and interprets them to figure out whether a particular host is or isn't permitted to send mail with a given identity ๐ต๏ธโโ๏ธ Mail receivers that do this check MUST correctly evaluate the check_host() function as described here ๐
Implementations MAY use a different algorithm than the canonical one 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.
The domain portion of <sender> will usually be the same as the <domain> argument when check_host() is first evaluated. However, this will generally NOT be true for recursive evaluations (see Section 5.2 below) ๐
Actual implementations of check_host() may need additional arguments ๐ธ
4.2. Results ๐ฏ
The check_host() function can return one of several results, described in Section 2.5. Based on the result, the receiver's local policies decide what action gets taken ๐
4.3. Initial Processing ๐ฆ
If the <domain> is malformed (label longer than 63 characters, zero-length label not at the end, etc.) or is not a fully qualified domain name, OR if the DNS lookup returns "domain does not exist" (RCODE 3), check_host() immediately returns the result "None" ๐ โโ๏ธ
If the <sender> has no localpart, substitute the string "postmaster" for the localpart ๐ฌ
4.4. Record Lookup ๐
Based on how the records are published (see Section 3.1 above), a DNS query needs to be made for the <domain> name, asking for RR type TXT, SPF, or both ๐ง If both SPF and TXT RRs are looked up, the queries MAY be done in parallel โก
If ALL the DNS lookups that get made return a server failure (RCODE 2), or some other error (RCODE other than 0 or 3), or they time out, then check_host() exits 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, record selection goes through two steps ๐ซง
- Records that do NOT begin with a version section of exactly "v=spf1" get discarded. Note that the version section is terminated either by an SP character or the end of the record. A record with a version section of "v=spf10" does not match and must be discarded ๐
- If any records of type SPF are in the set, then ALL records of type TXT get discarded ๐
After those steps, there should be exactly one record left, and then evaluation can proceed โจ If there are two or more records left, check_host() exits immediately with the result of "PermError" ๐
If no matching records come back, an SPF client MUST assume that the domain makes no SPF declarations. SPF processing MUST stop and return "None" ๐ซถ
4.6. Record Evaluation ๐ฌ
After one SPF record has been selected, check_host() parses and interprets it to find a result for the current test ๐ If there are any syntax errors, check_host() returns immediately with the result "PermError" ๐ญ
Implementations MAY choose to parse the entire record first and return "PermError" if the record isn't syntactically well formed ๐ However, in ALL cases, any syntax errors anywhere in the record MUST be detected ๐ฅ
4.6.1. Term Evaluation ๐ง
There are two types of terms: mechanisms and modifiers ๐ฆ A record has an ordered list of these, written in the following Augmented Backus-Naur Form (ABNF) ๐
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
name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." )
Most mechanisms allow a ":" or "/" character after the name ๐
Modifiers ALWAYS have an equals ('=') character immediately after the name, and before any ":" or "/" characters that may be part of the macro-string ๐
Terms that don't contain any of "=", ":", or "/" are mechanisms, as defined in Section 5 โจ
As per the definition of the ABNF notation in [RFC4234], mechanism and modifier names are case-insensitive ๐
4.6.2. Mechanisms ๐ ๏ธ
Each mechanism gets considered in turn, from left to right. If there are no more mechanisms, the result is specified in Section 4.7 ๐ธ
When a mechanism is evaluated, one of three things can happen: it can match, not match, or throw an exception ๐ฒ
If it matches, processing ends and the qualifier value is returned as the result of that record. If it doesn't match, processing continues with the next mechanism. If it throws an exception, mechanism processing ends and the exception value is returned ๐ฅ
The possible qualifiers and the results they return are:
"+" Pass
"-" Fail
"~" SoftFail
"?" Neutral
The qualifier is optional and defaults to "+" ๐ซถ
When a mechanism matches and the qualifier is "-", a "Fail" result is returned, and the explanation string is computed as described in Section 6.2 ๐
The specific mechanisms are described in Section 5 ๐
4.6.3. Modifiers ๐ง
Modifiers aren't mechanisms: they don't return match or not-match. Instead, they provide additional information ๐ Even though modifiers don't directly affect how the record is evaluated, the "redirect" modifier does have an effect after ALL the mechanisms have been evaluated ๐
4.7. Default Result ๐
If none of the mechanisms match and there's no "redirect" modifier, check_host() returns a result of "Neutral", just as if "?all" were specified as the last directive ๐ซง If there IS a "redirect" modifier, check_host() proceeds as defined in Section 6.1 โจ
Note that records SHOULD always use either a "redirect" modifier or an "all" mechanism to explicitly terminate processing ๐
For example:
v=spf1 +mx -all
or
v=spf1 +mx redirect=_spf.example.com
4.8. Domain Specification ๐ง
Okay bestie, so several of these mechanisms and modifiers have a <domain-spec> section. That <domain-spec> string gets macro expanded (go check Section 8 ๐
). The result is the common presentation form of a fully-qualified DNS name, which is a series of labels separated by periods. For the rest of the doc, we call this domain the <target-name> ๐.
Note: The result of the macro expansion is not subject to any further escaping. Hence, this facility cannot produce all characters that are legal in a DNS label (e.g., the control characters). However, this facility is powerful enough to express legal host names and common utility labels (such as "_spf") that are used in DNS. ๐
For several mechanisms the <domain-spec> is optional. If it's not provided, the <domain> gets used as the <target-name> โจ.
5. Mechanism Definitions ๐
This section defines two types of mechanisms, and honestly the difference is so cute ๐ฆ.
Basic mechanisms contribute to the language framework. They do not specify a particular type of authorization scheme. ๐ธ
all
include
Designated sender mechanisms are used to designate a set of <ip> addresses as being permitted or not permitted to use the <domain> for sending mail ๐.
a
mx
ptr
ip4
ip6
exists
The following conventions apply to all mechanisms that perform a comparison between <ip> and an IP address at any point ๐:
If no CIDR-length is given in the directive, then <ip> and the IP address are compared for equality. (Here, CIDR is Classless Inter-Domain Routing ๐ง .)
If a CIDR-length is specified, then only the specified number of high-order bits of <ip> and the IP address are compared for equality ๐ฅ.
When any mechanism fetches host addresses to compare with <ip>, A records are fetched when <ip> is an IPv4 address, and AAAA records are fetched when <ip> is an IPv6 address. Even if the SMTP connection is via IPv6, an IPv4-mapped IPv6 IP address (see [RFC3513], Section 2.5.5) MUST still be considered an IPv4 address. Main character energy, no notes ๐.
Several mechanisms rely on information fetched from DNS. For these DNS queries, except where noted, if the DNS server returns an error (RCODE other than 0 or 3) or the query times out, the mechanism throws the exception "TempError" ๐ญ. If the server returns "domain does not exist" (RCODE 3), then evaluation of the mechanism continues as if the server returned no error (RCODE 0) and zero answer records. Lowkey the most chill outcome ever ๐ซถ.
5.1. "all"
all = "all"
The "all" mechanism is a test that always matches ๐ฏ. It's used as the rightmost mechanism in a record to provide an explicit default.
For example:
v=spf1 a mx -all
Mechanisms after "all" will never be tested ๐ซ. Any "redirect" modifier (Section 6.1) has no effect when there is an "all" mechanism. Iconic behaviour, no cap ๐ .
5.2. "include"
include = "include" ":" domain-spec
The "include" mechanism triggers a recursive evaluation of check_host() ๐. The domain-spec is expanded as per Section 8. Then check_host() is evaluated with the resulting string as the <domain>. The <ip> and <sender> arguments remain the same as in the current evaluation of check_host() ๐.
In hindsight, the name "include" was poorly chosen ๐. Only the evaluated result of the referenced SPF record is used, rather than acting as if the referenced SPF record was literally included in the first. For example, evaluating a "-all" directive in the referenced record does not terminate the overall processing and does not necessarily result in an overall "Fail". (Better names for this mechanism would have been "if-pass", "on-pass", etc. ๐)
The "include" mechanism makes it possible for one domain to designate multiple administratively-independent domains ๐ง. For example, a vanity domain "example.net" might send mail using the servers of administratively-independent domains example.com and example.org.
Example.net could say:
IN TXT "v=spf1 include:example.com include:example.org -all"
This would direct check_host() to, in effect, check the records of example.com and example.org for a "Pass" result. Only if the host were not permitted for either of those domains would the result be "Fail" โจ.
Whether this mechanism matches, does not match, or throws 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 | throw TempError |
| | |
| PermError | throw PermError |
| | |
| None | throw PermError |
+---------------------------------+---------------------------------+
The "include" mechanism is intended for crossing administrative boundaries ๐. Although it is possible to use includes to consolidate multiple domains that share the same set of designated hosts, domains are encouraged to use redirects where possible, and to minimize the number of includes within a single administrative domain. For example, if example.com and example.org were managed by the same entity, and if the permitted set of hosts for both domains was "mx:example.com", it would be possible for example.org to specify "include:example.com", but it would be preferable to specify "redirect=example.com" or even "mx:example.com" ๐.
5.3. "a"
This mechanism matches if <ip> is one of the <target-name>'s IP addresses ๐ฏ.
A = "a" [ ":" domain-spec ] [ dual-cidr-length ]
An address lookup is done on the <target-name>. The <ip> is 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 performs an MX lookup on the <target-name>. Then it performs an address lookup on each MX name returned. The <ip> is compared to each returned IP address. To prevent Denial of Service (DoS) attacks, more than 10 MX names MUST NOT be looked up during the evaluation of an "mx" mechanism (see Section 10) ๐ก๏ธ. If any address matches, the mechanism matches.
Note regarding implicit MXs: If the <target-name> has no MX records, check_host() MUST NOT pretend the target is its single MX, and MUST NOT default to an A lookup on the <target-name> directly. This behavior breaks with the legacy "implicit MX" rule. See [RFC2821], Section 5. If such behavior is desired, the publisher should specify an "a" directive ๐
โโ๏ธ.
5.5. "ptr"
This mechanism tests whether the DNS reverse-mapping for <ip> exists and correctly points to a domain name within a particular domain ๐.
PTR = "ptr" [ ":" domain-spec ]
First, the <ip>'s name is looked up using this procedure: perform a DNS reverse-mapping for <ip>, looking up the corresponding PTR record in "in-addr.arpa." if the address is an IPv4 one and in "ip6.arpa." if it is an IPv6 address. For each record returned, validate the domain name by looking up its IP address. To prevent DoS attacks, more than 10 PTR names MUST NOT be looked up during the evaluation of a "ptr" mechanism (see Section 10). If <ip> is among the returned IP addresses, then that domain name is validated. In pseudocode:
sending-domain_names := ptr_lookup(sending-host_IP); if more than 10
sending-domain_names are found, use at most 10. for each name in
(sending-domain_names) {
IP_addresses := a_lookup(name);
if the sending-domain_IP is one of the IP_addresses {
validated-sending-domain_names += name;
} }
Check all validated domain names to see if they end in the <target-name> domain. If any do, this mechanism matches. If no validated domain name can be found, or if none of the validated domain names end in the <target-name>, this mechanism fails to match. If a DNS error occurs while doing the PTR RR lookup, then this mechanism fails to match. If a DNS error occurs while doing an A RR lookup, then that domain name is skipped and the search continues ๐.
Pseudocode:
for each name in (validated-sending-domain_names) {
if name ends in <domain-spec>, return match.
if name is <domain-spec>, return match.
}
return no-match.
This mechanism matches if the <target-name> is either an ancestor of a validated domain name or if 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: Use of this mechanism is discouraged because it is slow, it is not as reliable as other mechanisms in cases of DNS errors, and it places a large burden on the arpa name servers. If used, proper PTR records must be in place for the domain's hosts and the "ptr" mechanism should be one of the last mechanisms checked. Ngl, this one is giving "use with caution" energy ๐ญ๐.
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 = "/" 1*DIGIT
ip6-cidr-length = "/" 1*DIGIT
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 [RFC 3513], section 2.2>
; e.g., 2001:DB8::CD30
The <ip> is compared to the given network. If CIDR-length high-order bits match, the mechanism matches ๐ฏ.
If ip4-cidr-length is omitted, it is taken to be "/32". If ip6-cidr-length is omitted, it is taken to be "/128". It is not permitted to omit parts of the IP address instead of using CIDR notations. That is, use 192.0.2.0/24 instead of 192.0.2 ๐. No shortcuts, bestie, the full address hits different โจ.
5.7. "exists" โจ
okay bestie, "exists" is literally so unhinged in the best way ๐ It builds a whole custom domain name out of pieces of the mail envelope and then does a DNS A record lookup on it. If anything comes back, the mechanism matches. It's giving full choose-your-own-adventure for who gets to send mail ๐ฆ
exists = "exists" ":" domain-spec
The domain-spec gets expanded using Section 8 ๐ธ The result is the domain name used for the DNS A RR lookup. If any A record comes back, the mechanism matches. And here's the twist: the lookup type is A even when the connection is IPv6 ๐ no cap, that one's a plot twist.
Domains can use this to write super complicated queries ๐ง For example, say example.com publishes this record:
v=spf1 exists:%{ir}.%{l1r+-}._spf.%{d} -all
The <target-name> might expand to "1.2.0.192.someuser._spf.example.com" ๐ That means you can make really fine-grained decisions based on the user and the client IP address. Lowkey iconic โจ
This mechanism also lets you copy the style of tests that existing anti-spam DNS blacklists (DNSBL) use ๐ฅ So if you've ever seen a DNSBL, this is giving familiar vibes with way more control.
6. Modifier Definitions ๐
okay so modifiers are name/value pairs that add extra info ๐ They always have an "=" between the name and the value, no exceptions bestie ๐ซถ
The modifiers defined in this document ("redirect" and "exp") MAY appear anywhere in the record, but they SHOULD appear at the end, after all the mechanisms ๐ Their order doesn't matter. And each of these two MUST NOT appear in a record more than once ๐ญ If they do, check_host() exits with a result of "PermError". Not it ๐
Unrecognized modifiers MUST be ignored no matter where they show up in a record, or how often ๐ This is what lets implementations of this document gracefully handle records with modifiers that come from other specifications. Main character energy for future-proofing โจ
6.1. redirect: Redirected Query ๐ฆ
If all the mechanisms fail to match, and a "redirect" modifier is there, then processing goes like this:
redirect = "redirect" "=" domain-spec
The domain-spec in the redirect gets expanded using the macro rules in Section 8 ๐ธ Then check_host() gets evaluated again, using the resulting string as the <domain>. The <ip> and <sender> arguments stay the same as the current evaluation of check_host().
Whatever result that new check_host() evaluation gives is considered the result of the current evaluation ๐ The one exception: if no SPF record is found, or the target-name is malformed, the result is a "PermError" instead of "None". Plot twist, but a fair one ๐
Note that the newly-queried domain may itself specify redirect processing. Redirects inside redirects, we are SO in it ๐
This is meant 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"
In this example, mail from any of the three domains is described by the same record. Administratively, that's a whole vibe ๐
Note: In general, the domain "A" cannot reliably use a redirect to another domain "B" that isn't under the same administrative control ๐ Since the <sender> stays the same, there's no guarantee that the record at domain "B" will work correctly for mailboxes in domain "A", especially if domain "B" uses mechanisms involving localparts. An "include" directive may be more appropriate, bestie.
For clarity, it is RECOMMENDED that any "redirect" modifier appears as the very last term in a record ๐ Clean and easy to read, periodt โจ
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 the way described below ๐ฅ If there's no "exp" modifier, then either a default explanation string or an empty explanation string may be returned.
The <domain-spec> is macro expanded (see Section 8) and becomes the <target-name> ๐ธ Then the DNS TXT record for the <target-name> is fetched.
If the <domain-spec> is empty, or there are any DNS processing errors (any RCODE other than 0), or no records come back, or more than one record comes back, or there are syntax errors in the explanation string, then proceed as if no exp modifier was given ๐ญ Not a crash, just a vibe reset.
The strings in the fetched TXT record are concatenated with no spaces, then treated as an <explain-string>, which gets macro-expanded ๐ฆ That final result is the explanation string. Implementations MAY limit the length of the resulting explanation string to allow for other protocol constraints and/or reasonable processing limits ๐ And since the explanation string is meant for an SMTP response, and [RFC2821] Section 2.4 says responses are in [US-ASCII], the explanation string is also limited to US-ASCII. Keep it simple, bestie ๐
Software evaluating check_host() can use this string to pass along info from the publishing domain, like a short message or a URL โจ Software SHOULD make it clear that the explanation string comes from a third party ๐ซถ For example, it can prepend the macro string "%{o} explains: " to the explanation, like what's shown in Section 2.5.4.
Say example.com has this record:
v=spf1 mx -all exp=explain._spf.%{d}
Here are some examples of 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 ๐ โโ๏ธ On the flip side, when executing a "redirect" modifier, an exp= modifier from the original domain MUST NOT be used. No mixing the explanations, bestie ๐
7. The Received-SPF Header Field ๐ฌ
It is RECOMMENDED that SMTP receivers record the result of SPF processing in the message header ๐ If an SMTP receiver chooses to do so, it SHOULD use the "Received-SPF" header field defined here for each identity that was checked. This info is meant for the recipient. (Info meant for the sender is in Section 6.2, Explanation.)
The Received-SPF header field is a trace field (see [RFC2822] Section 3.6.7) and it SHOULD be prepended to the existing header, above the Received: field that the SMTP receiver generates ๐ฆ It MUST appear above all other Received-SPF fields in the message. Here's the 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 / "x-" name / name
identity = "mailfrom" ; for the "MAIL FROM" identity / "helo" ; for the "HELO" identity / name ; other identities
dot-atom = <unquoted word as per [RFC2822]> quoted-string = <quoted string as per [RFC2822]> comment = <comment string as per [RFC2822]> CFWS = <comment or folding white space as per [RFC2822]> FWS = <folding white space as per [RFC2822]> CRLF = <standard end-of-line token as per [RFC2822]>
The header field SHOULD include a "(...)" style <comment> after the result ๐ It conveys supporting info for the result, like <ip>, <sender>, and <domain>.
The following key-value pairs are designed for later machine parsing ๐ค SPF clients SHOULD give enough info so the SPF results can be verified. That means at least "client-ip", "helo", and, if the "MAIL FROM" identity was checked, "envelope-from" ๐ซถ
client-ip the IP address of the SMTP client
envelope-from the envelope sender mailbox
helo the host name given in the HELO or EHLO command
mechanism the mechanism that matched (if no mechanisms matched, substitute the word "default")
problem if an error was returned, details about the error
receiver the host name of the SPF client
identity the identity that was checked; see the <identity> ABNF rule
Other keys may be defined by SPF clients ๐ธ Until a new key name becomes widely accepted, new key names should start with "x-".
SPF clients MUST make sure that the Received-SPF header field doesn't contain invalid characters, isn't excessively long, and doesn't contain malicious data that was provided by the sender ๐ญ This one's serious, bestie, no shade, just safety ๐ฅ
Here are some examples of the header styles that could get 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>;
8. Macros โจ
8.1. Macro Definitions โจ
okay so macros are where the magic happens ๐ฎ Lots of mechanisms and modifiers do macro expansion on part of their term, and this is the part of the spec that explains exactly how it all works bestie ๐
Here's the grammar, copy-pasted exactly so nobody can accuse me of altering it ๐ซถ
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 [RFC3696], Section 2)
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"
transformers = *DIGIT [ "r" ]
delimiter = "." / "-" / "+" / "," / "/" / "_" / "="
A literal "%" is written as %% ๐ธ
%_ expands to a single " " space.
%- expands to a URL-encoded space, viz., "%20".
These 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>
v = the string "in-addr" if <ip> is ipv4, or "ip6" if <ip> is ipv6
h = HELO/EHLO domain
These ones are only allowed in "exp" text, so don't try them anywhere else ๐
c = SMTP client IP (easily readable format)
r = domain name of host performing the check
t = current timestamp
Now the big one: a % that isn't followed by a {, %, -, or _ is a syntax error ๐ So this is WRONG and will make check_host() return a "PermError" ๐ญ
-exists:%(ir).sbl.spamhaus.example.org
Instead, do this ๐
-exists:%{ir}.sbl.spamhaus.example.org
The optional transformers are:
*DIGIT = zero or more digits
'r' = reverse value, splitting on dots by default
If you use transformers or delimiters, the replacement value for a macro letter gets split into parts ๐งฉ After any reversal and/or removal of left-hand parts, the parts get rejoined with "." (not the original splitting characters, so pay attention โจ)
By default, strings split on "." (dots). Note that there's no special treatment for leading, trailing, or consecutive delimiters, so the list of parts might contain empty strings ๐ซ Older implementations of SPF prohibit trailing dots in domain names, so trailing dots shouldn't be published by domain owners, 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 means reversal ๐ If the client IP address were 192.0.2.1, then %{i} expands to "192.0.2.1" and %{ir} expands to "1.2.0.192" ๐ญ iconic
The DIGIT transformer tells you how many right-hand parts to use, after the optional reversal. If a DIGIT is specified, the value MUST be nonzero. If no DIGITs are specified, or if the value asks for more parts than are available, then all the available parts get used. If the DIGIT was 5 but only 3 parts were available, the macro interpreter pretends the DIGIT was 3 ๐ Implementations MUST support at least a value of 128, since that's the maximum number of labels in a domain name ๐
The "s" macro expands to the <sender> argument. It's an E-Mail address with a localpart, an "@" character, and a domain. The "l" macro expands to just the localpart, 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 localpart, the localpart was set to "postmaster" during 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 meant for use in %{ir} ๐ง The "c" macro may expand to any of the hexadecimal colon-format addresses specified in [RFC3513], Section 2.2. 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 may be used. If there are no validated domain names, or if a DNS error occurs, the string "unknown" is 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 MUA), or if policy restrictions dictate otherwise, the word "unknown" SHOULD be substituted ๐ The domain name might 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). It's the same value POSIX time() gives you in most standards-compliant libraries ๐ฐ๏ธ
When the result of macro expansion gets used in a domain name query, and the expanded domain name is longer than 253 characters (the maximum length of a domain name), the left side gets truncated to fit ๐ช You do this by removing successive domain labels until the total length doesn't exceed 253 characters.
Uppercased macros expand exactly like their lowercased equivalents, and then get URL escaped ๐ URL escaping must be performed for characters not in the "uric" set, which is defined in [RFC3986].
Note: Care must be taken so that macro expansion for legitimate E-Mail doesn't go over the 63-character limit on DNS labels. The localpart of E-Mail addresses, in particular, can have more than 63 characters between dots ๐ญ
Note: Domains should avoid using the "s", "l", "o", or "h" macros in conjunction with any mechanism directive. Even though these macros are powerful and allow per-user records to be published, they seriously limit how well implementations can cache results of check_host(), and they reduce the effectiveness of DNS caches ๐
Implementations should be aware that 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 based on <domain> and <ip> alone, for as long as the shortest Time To Live (TTL) of all the DNS records involved ๐ง โจ
8.2. Expansion Examples ๐
Here's the setup for all the examples, so follow along ๐
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.
And here's the expansion table, which is honestly so satisfying ๐คฉ
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
9. Implications ๐
This section lays out the big implications that adopting this document will have on the various entities involved in Internet E-Mail ๐จ It's meant to make it clear where this document knowingly affects how those entities work. This section is NOT a "how-to" manual or a "best practices" document, and it isn't a comprehensive list of what those entities should do because of this document ๐ โโ๏ธ
This section is non-normative. ๐
9.1. Sending Domains ๐
Domains that want to be compliant with this specification will need to figure out the list of hosts they allow to use their domain name in the "HELO" and "MAIL FROM" identities. It's recognized that forming that list isn't just a simple technical exercise. It involves policy decisions with both technical and administrative considerations ๐ง
It can be helpful to publish records that include a "tracking exists:" mechanism ๐ต๏ธโโ๏ธ By looking at the name server logs, you can then generate a rough list. For example:
v=spf1 exists:_h.%{h}._l.%{l}._o.%{o}._i.%{i}._spf.%{d} ?all
9.2. Mailing Lists ๐ฌ
Mailing lists must be aware of how they re-inject mail that's sent to the list. Mailing lists MUST comply with the requirements in [RFC2821], Section 3.10, and [RFC1123], Section 5.3.6, which say that the reverse-path MUST be changed to be the mailbox of a person or other entity who administers the list ๐ซก The reasons for changing the reverse-path are many and long-standing, but SPF adds enforcement to this requirement ๐ช
In practice, almost all mailing list software in use already complies with this requirement ๐ Mailing lists that don't comply may or may not run into problems, depending on how access to the list is restricted. Lists that are entirely internal to a domain (where only people in the domain can send to or receive from the list) aren't affected ๐ธ
9.3. Forwarding Services and Aliases โจ
Okay so forwarding services take mail that lands in a mailbox and pass it along to some external mailbox ๐ Right now, basically every forwarding service re-sends the message using the original "MAIL FROM" when it delivers to that external mailbox ๐ซ [RFC1123] and [RFC2821] call this an "alias" instead of a "mail list". So the external mailbox's MTA sees all this mail coming in on a connection from a host that belongs to the forwarding service, which means the "MAIL FROM" identity generally won't pass authorization ๐
There are three spots where you can try to fix this ๐ ๏ธ
- The beginning, when E-Mail is first sent.
- "Neutral" results could be given for IP addresses that might be forwarders, instead of "Fail" results. For example:
"v=spf1 mx -exists:%{ir}.sbl.spamhaus.example.org ?all"This would do a lookup on an anti-spam DNS blacklist (DNSBL) and only give a "Fail" result for E-Mail from listed sources. Everything else, including E-Mail sent through forwarders, gets a "Neutral" result. Checking the DNSBL after the known good sources means problems from incorrect DNSBL listings are greatly reduced ๐ - The
"MAIL FROM"identity could carry extra info in the localpart that cryptographically proves the mail came from an authorized source ๐ In that case, an SPF record like"v=spf1 mx exists:%{l}._spf_verify.%{d} -all"could be used. Then a specialized DNS server can be set up to serve the_spf_verifysubdomain and validate the localpart. Yes, this needs an extra DNS lookup, but it only happens when the E-Mail would otherwise be rejected as not coming from a known good source โจ
- "Neutral" results could be given for IP addresses that might be forwarders, instead of "Fail" results. For example:
Note that because domain labels are capped at 63 characters, this approach only works reliably if the localpart signature scheme is guaranteed either to only produce localparts with a maximum of 63 characters or to gracefully handle truncated localparts. ๐
- Similarly, a specialized DNS server could be set up to rate-limit E-Mail coming from unexpected IP addresses ๐ฆ
"v=spf1 mx exists:%{ir}._spf_rate.%{d} -all" - SPF allows per-user policies for special cases ๐ For example, this SPF record plus appropriate wildcard DNS records can do the job:
"v=spf1 mx redirect=%{l1r+}._at_.%{o}._spf.%{d}"
- The middle, when E-Mail is forwarded.
- Forwarding services can solve the problem by rewriting the
"MAIL FROM"to be in their own domain. The catch is that mail bounced from the external mailbox will have to be re-bounced by the forwarding service ๐ Various schemes to do this exist, though they vary widely in complexity and in the resource requirements on the forwarding service's side. - Several popular MTAs can be forced from "alias" semantics to "mailing list" semantics by adding an extra alias with "owner-" prepended to the original alias name ๐ง For example, an alias of
"friends: george@example.com, fred@example.org"would need another alias of the form"owner-friends: localowner"
- Forwarding services can solve the problem by rewriting the
- The end, when E-Mail is received.
- If the owner of the external mailbox trusts the forwarding service, they can tell the external mailbox's MTA to skip SPF tests when the client host belongs to that forwarding service ๐ซถ
- Tests against other identities, such as the "HELO" identity, may be used to override a failed test against the
"MAIL FROM"identity ๐ - For larger domains, it may not be possible to have a complete or accurate list of the forwarding services used by the owners of the domain's mailboxes ๐ตโ๐ซ In those cases, whitelists of generally-recognized forwarding services could be employed ๐
9.4. Mail Services
Service providers that offer mail services to third-party domains, like sending bulk mail ๐ฌ, may want to adjust their setup in light of the authorization check described in this document. If the "MAIL FROM" identity used for that E-Mail uses the provider's own domain, then the provider only needs to make sure its sending host is authorized by its own SPF record, if it has one ๐
If the "MAIL FROM" identity does not use the mail service provider's domain, then extra care must 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 mail service providers, like ISPs, that have a wide variety of customers using the same MTA, steps should be taken to prevent cross-customer forgery (see Section 10.4) ๐จ
9.5. MTA Relays
The authorization check generally rules out arbitrary MTA relays between the sender and receiver of an E-Mail message ๐ซ
Inside an organization, MTA relays can be effectively deployed ๐ข For the purposes of this document, though, those relays are effectively transparent. The SPF authorization check is a check between border MTAs of different domains ๐
For mail senders, this means that published SPF records must authorize any MTAs that actually send across the Internet ๐ค Usually, these are just the border MTAs, since internal MTAs simply forward mail to them for delivery.
Mail receivers will generally want to perform the authorization check at the border MTAs, specifically including all secondary MXs ๐ This lets mail that fails get rejected during the SMTP session rather than bounced. Internal MTAs then skip the authorization test. To perform the test anywhere other than the border, the host that first transferred the message into the organization has to be determined, and that can be tricky to pull out of the message header ๐ซ Testing other than at the border is not recommended ๐
10. Security Considerations ๐จ
10.1. Processing Limits
Like most aspects of E-Mail, there are a bunch of ways bad actors could use the protocol as an avenue for a Denial-of-Service (DoS) attack ๐ The processing limits outlined here are designed to prevent attacks like these:
- A malicious party could create an SPF record with many references to a victim's domain and send many E-Mails to different SPF clients. Those SPF clients would then create a DoS attack. In effect, the SPF clients get used to amplify the attacker's bandwidth, since they use fewer bytes in the SMTP session than the DNS queries do. Using SPF clients also lets the attacker hide the true source of the attack ๐ต๏ธ
- Whereas implementations of check_host() are supposed to limit the number of DNS lookups, malicious domains could publish records that exceed these limits 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 ๐
- Malicious parties could send a large volume of mail purporting to come from the intended target to a wide variety of legitimate mail hosts. Those legitimate machines would then put a DNS load on the target as they fetch the relevant records ๐
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 may 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 โฌ๏ธ
SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check, including any lookups caused by the use of the "include" mechanism or the "redirect" modifier. If this number is exceeded during a check, a PermError MUST be returned. The "include", "a", "mx", "ptr", and "exists" mechanisms as well as the "redirect" modifier do count against this limit. The "all", "ip4", and "ip6" mechanisms do not require DNS lookups and therefore do not count against this limit. The "exp" modifier does not count against this limit because the DNS lookup to fetch the explanation string occurs after the SPF record has been evaluated โฑ๏ธ
When evaluating the "mx" and "ptr" mechanisms, or the %{p} macro, there MUST be a limit of no more than 10 MX or PTR RRs looked up and checked ๐ข
SPF implementations SHOULD limit the total amount of data obtained from the DNS queries. For example, when DNS over TCP or EDNS0 are available, there may need to be an explicit limit to how much data will be accepted, to prevent excessive bandwidth usage or memory usage and DoS attacks ๐ฆ
MTAs or other processors MAY also impose a limit on the maximum amount of elapsed time to evaluate check_host(). Such a limit SHOULD allow at least 20 seconds. If such a limit is exceeded, the result of authorization SHOULD be "TempError" โณ
Domains publishing records SHOULD try to keep the number of "include" mechanisms and chained "redirect" modifiers to a minimum. Domains SHOULD also try to minimize the amount of other DNS information needed to evaluate a record. This can be done by choosing directives that require less DNS information and by placing lower-cost mechanisms earlier in the SPF record ๐งน
For example, consider a domain set up as follows:
example.com. IN MX 10 mx.example.com.
mx.example.com. IN A 192.0.2.1
a.example.com. IN TXT "v=spf1 mx:example.com -all"
b.example.com. IN TXT "v=spf1 a:mx.example.com -all"
c.example.com. IN TXT "v=spf1 ip4:192.0.2.1 -all"
Evaluating check_host() for the domain "a.example.com" requires the MX records for "example.com", and then the A records for the listed hosts. Evaluating for "b.example.com" requires only the A records. Evaluating for "c.example.com" requires none ๐ง
However, there may be administrative considerations: using "a" over "ip4" allows hosts to be renumbered easily, and using "mx" over "a" allows the set of mail hosts to be changed easily ๐
10.2. SPF-Authorized E-Mail May Contain Other False Identities
The "MAIL FROM" and "HELO" identity authorizations must not be construed to provide more assurance than they do ๐ซฃ It's entirely possible for a malicious sender to inject a message using their own domain in the identities used by SPF, get that domain's SPF record to authorize the sending host, and 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 may be lulled into a false sense of security ๐ฌ
10.3. Spoofed DNS and IP Data
okay bestie, there are two spots where a baddie could mess with check_host() ๐
- The whole check leans super hard on DNS ๐. An attacker could go after the DNS infrastructure so
check_host()sees spoofed data and returns wrong results. That could even meanPassfor an<ip>where the domain's real record would sayFail๐. For a rundown of DNS weaknesses, see [RFC3833]. - The client IP address,
<ip>, is assumed to be correct ๐ซฃ. An attacker could spoof TCP sequence numbers so mail looks like it's coming from a permitted host, for a domain they're impersonating ๐
10.4. Cross-User Forgery
SPF policies are only about domain names. They map domains to sets of authorized MTAs, not whole E-Mail addresses to sets of authorized users ๐ฎ The l macro (Section 8) gives you a limited way to define separate sets of authorized MTAs for specific E-Mail addresses, but it's generally impossible to verify through SPF that individual users of the same MTA are really the ones using specific addresses ๐คทโโ๏ธ
So mail services and their MTAs have to stop cross-user forgery directly ๐ก๏ธ Based on SMTP AUTH ([RFC2554]), users should only be allowed to use E-Mail addresses that are actually under their control (see [RFC4409], Section 6.1). Another way to verify individual users is message cryptography, like PGP ([RFC2440]) or S/MIME ([RFC3851]) ๐
10.5. Untrusted Information Sources
SPF runs on info from third parties, like the HELO domain name, the MAIL FROM address, and SPF records ๐ฌ That info then gets passed to the receiver in the Received-SPF: trace fields and maybe sent back to the client MTA as part of an SMTP rejection message. This info must be checked for invalid characters and excessively long lines ๐งน
When the authorization check fails, an explanation string may be included in the reject response. Both the sender and the rejecting receiver need to be aware that the explanation was written by the publisher of the SPF record that got checked, and in general not by the receiver ๐ฉ The explanation may contain malicious URLs, or it may be offensive or misleading ๐
This is probably less of a concern than it first seems ๐ Those messages go back to the sender, and the explanation strings come from the sender policy published by the domain in the identity that very sender claimed. As long as the DSN isn't redirected to someone other than the actual sender, the only people who see malicious explanation strings are people whose messages claim to be from domains that publish those strings in their SPF records. In practice, DSNs can get misdirected, like when an MTA accepts an E-Mail and then later generates a DSN to a forged address, or when an E-Mail forwarder doesn't send the DSN back to the original sender ๐จ
10.6. Privacy Exposure
Checking SPF records sends DNS queries to the domain owner ๐ Those queries, especially the ones caused by the exists mechanism, can reveal who is sending E-Mail and likely which MTA the E-Mail is going to ๐ซข That can raise some privacy concerns. How much of an issue it is depends on local laws and on the relationship between the domain owner and the person sending the E-Mail โ๏ธ
11. Contributors and Acknowledgements
This document is largely based on the work of Meng Weng Wong and Mark Lentczner ๐ Lots of people contributed, but a very large portion of the writing and editing is thanks to Meng and Mark ๐ซถ
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 E-Mail address is legit has roots even further back, in messages on the namedroppers mailing list by Paul Vixie [Vixie] (based on a suggestion by Jim Miller) and by David Green [Green] ๐ฆ
Philip Gladstone added the concept of macros to the specification โจ That multiplied how expressive the language is and made per-user and per-IP lookups possible ๐คฏ
The authors 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 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.
12. IANA Considerations
12.1. The SPF DNS Record Type
IANA assigned a new Resource Record Type and Qtype from the DNS Parameters Registry for the SPF RR type, with code 99 ๐
12.2. The Received-SPF Mail Header Field
Per [RFC3864], the Received-SPF: header field is added to the IANA Permanent Message Header Field Registry ๐ Here's the registration template:
Header field name: Received-SPF
Applicable protocol: mail ([RFC2822])
Status: Experimental
Author/Change controller: IETF
Specification document(s): RFC 4408
Related information:
Requesting SPF Council review of any proposed changes and
additions to this field are recommended. For information about
the SPF Council see http://www.openspf.org/Council
13. References
13.1. Normative References
[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.
[RFC2821] Klensin, J., "Simple Mail Transfer Protocol", RFC 2821,
April 2001.
[RFC2822] Resnick, P., "Internet Message Format", RFC 2822, April
2001.
[RFC3464] Moore, K. and G. Vaudreuil, "An Extensible Message Format
for Delivery Status Notifications", RFC 3464, January
2003.
[RFC3513] Hinden, R. and S. Deering, "Internet Protocol Version 6
(IPv6) Addressing Architecture", RFC 3513, April 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.
[RFC4234] Crocker, D. and P. Overell, "Augmented BNF for Syntax
Specifications: ABNF", RFC 4234, October 2005.
[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.
13.2 Informative References
[RFC1034] Mockapetris, P., "Domain names - concepts and facilities",
STD 13, RFC 1034, November 1987.
[RFC1983] Malkin, G., "Internet Users' Glossary", RFC 1983, August
1996.
[RFC2440] Callas, J., Donnerhacke, L., Finney, H., and R. Thayer,
"OpenPGP Message Format", RFC 2440, November 1998.
[RFC2554] Myers, J., "SMTP Service Extension for Authentication",
RFC 2554, March 1999.
[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.
[RFC3851] Ramsdell, B., "Secure/Multipurpose Internet Mail
Extensions (S/MIME) Version 3.1 Message Specification",
RFC 3851, July 2004.
[RFC4409] Gellens, R. and J. Klensin, "Message Submission for Mail",
RFC 4409, April 2006.
[RMX] Danish, H., "The RMX DNS RR Type for light weight sender
authentication", Work In Progress
[DMP] Fecyk, G., "Designated Mailers Protocol", Work In Progress
[Vixie] Vixie, P., "Repudiating MAIL FROM", 2002.
[Green] Green, D., "Domain-Authorized SMTP Mail", 2002.
Appendix A. Collected ABNF โจ
okay so this section is normative, which means it's the final word ๐ if any ABNF fragment earlier in the doc doesn't match this grammar, this one wins, no debate. ๐ฅ
quick heads up: this is ABNF (the notation from [RFC4234]), and literal text in quotes is case-insensitive. so "mx" matches "mx", "MX", "mX", and "Mx" ๐ literally every version slays.
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
ip4-cidr-length = "/" 1*DIGIT
ip6-cidr-length = "/" 1*DIGIT
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 [RFC 3513], section 2.2>
; 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 [RFC3696], Section 2)
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"
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 / "x-" name / name
identity = "mailfrom" ; for the "MAIL FROM" identity
/ "helo" ; for the "HELO" identity
/ name ; other identities
dot-atom = <unquoted word as per [RFC2822]>
quoted-string = <quoted string as per [RFC2822]>
comment = <comment string as per [RFC2822]>
CFWS = <comment or folding white space as per [RFC2822]>
FWS = <folding white space as per [RFC2822]>
CRLF = <standard end-of-line token as per [RFC2822]>
Appendix B. Extended Examples ๐ง
time for the fun part ๐ these examples all run on one shared DNS setup, so here's the zone we're working with:
; 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.
B.1. Simple Examples
these show different records you could publish for example.com, and which values of <ip> would make check_host() return "Pass". โจ just note that <domain> is "example.com" here.
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
B.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. basically example.org's designated servers are the union of example.com's and example.net's designated servers. iconic teamwork ๐ซถ
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 when the mail setup changes, only the mail system's record needs updating. these domains' records never have to change. lowkey genius ๐ง
B.3. DNSBL Style Example
imagine that, on top of the domain records we already looked at, these exist too:
$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}
B.4. Multiple Requirements Example
say your sender policy needs BOTH that the IP address sits inside a certain range AND that the reverse DNS for the IP matches ๐ฅน there are a few ways to set that up, and here's one of them:
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. galaxy brain moment ๐
Authors' Addresses ๐ฆ
meet the authors ๐
Meng Weng Wong
Singapore
EMail: mengwong+spf@pobox.com
Wayne Schlitt
4615 Meredeth #9
Lincoln Nebraska, NE 68506
United States of America
EMail: wayne@schlitt.net
URI: http://www.schlitt.net/spf/
Full Copyright Statement ๐
Copyright (C) The Internet Society (2006).
this document is subject to the rights, licenses and restrictions contained in BCP 78, and except as set forth therein, the authors retain all their rights.
this document and the information contained herein are provided on an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
the disclaimer above is serious business. the document is provided "AS IS", with no warranties at all, express or implied.
Intellectual Property ๐ฅ
the IETF takes no position on the validity or scope of any Intellectual Property Rights or other rights that might be claimed to pertain to the implementation or use of the technology described in this document, or on whether any license under such rights might or might not be available. it also doesn't claim to have made any independent effort to identify any such rights. info on the procedures for rights in RFC documents can be found in BCP 78 and BCP 79.
copies of IPR disclosures made to the IETF Secretariat, any assurances of licenses to be made available, or the result of an attempt to get a general license or permission for implementers or users to use these proprietary rights can be found in the IETF online IPR repository at http://www.ietf.org/ipr.
the IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology required to implement this standard. send that info to the IETF at ietf-ipr@ietf.org ๐ฌ
Acknowledgement ๐ซถ
funding for the RFC Editor function is provided by the IETF Administrative Support Activity (IASA). thank you, IASA, for keeping the lights on โจ
๐ฆ where this page comes from
- ietf: rfc4408 2006-04
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms