RFC 7208

Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1

๐Ÿ“… published April 2014, from ietf

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 (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:

  1. commonly used
  2. 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() ๐Ÿ”โœจ

  1. the <domain-spec> gets expanded as per Section 7 ๐ŸŒธ
  2. check_host() is evaluated with the resulting string as the <domain>. the <ip> and <sender> arguments stay the same as in the current evaluation of check_host() ๐Ÿ’…
  3. the recursive evaluation hands back match, not-match, or an error ๐ŸŽฏ
  4. if it returns match, the appropriate result for the include mechanism is used. so include or +include gives a "pass" result, and -include gives "fail" ๐Ÿฆ‹
  5. 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 in in-addr.arpa. if the address is an IPv4 address, and in ip6.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 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 verifier
  • identity: 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.org would need another alias of the form owner-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