Tech-invite3GPPspaceIETFspace
959493929190898887868584838281807978777675747372717069686766656463626160595857565554535251504948474645444342414039383736353433323130292827262524232221201918171615141312111009080706050403020100
in Index   Prev   Next

RFC 7208

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

Pages: 64
Proposed Standard
Errata
Obsoletes:  4408
Updated by:  737285538616
Part 2 of 4 – Pages 14 to 33
First   Prev   Next

Top   ToC   RFC7208 - Page 14   prevText

4. The check_host() Function

This description is not an application programming interface definition, but rather 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 determine whether a particular host is or is not permitted to send mail with a given identity. Receiving ADMDs that perform this check MUST correctly evaluate the check_host() function as described here. Implementations MAY use a different algorithm than the canonical algorithm defined here, so 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 be the same as the <domain> argument when check_host() is initially evaluated. In most other cases it will 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 for all evaluations, not just for a single instance of a recursive evaluation. Note that the <domain> argument might not be a well-formed domain name. For example, if the reverse-path was null, then the EHLO/HELO domain is 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.
Top   ToC   RFC7208 - Page 15

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 be taken is determined by the local policies of the receiver. 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 is not 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 is, in the DNS they are 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

In accordance with how the records are published (see Section 3 above), a DNS query needs to be made for the <domain> name, querying 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 that were returned by the lookup, discard records that do not begin with a version section of exactly "v=spf1". Note that the version section is terminated by either an SP character or the end of the record. As an example, a record with a version section of "v=spf10" does not match and is discarded. If the resultant record set includes no records, check_host() produces the "none" result. If the resultant record set includes more than one record, check_host() produces the "permerror" result.
Top   ToC   RFC7208 - Page 16

4.6. Record Evaluation

The check_host() function parses and interprets the SPF record to find a result for the current test. The syntax of the record is validated first, and if there are any syntax errors anywhere in the record, check_host() returns immediately with the result "permerror", without further interpretation or evaluation.

4.6.1. Term Evaluation

There are two types of terms: mechanisms (defined in Section 5) and modifiers (defined in Section 6). A record contains an ordered list of these as specified in the following Augmented Backus-Naur Form (ABNF). terms = *( 1*SP ( directive / modifier ) ) directive = [ qualifier ] mechanism qualifier = "+" / "-" / "?" / "~" mechanism = ( all / include / a / mx / ptr / ip4 / ip6 / exists ) modifier = redirect / explanation / unknown-modifier unknown-modifier = name "=" macro-string ; where name is not any known modifier name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." ) Most mechanisms allow a ":" or "/" character after the name. Modifiers always contain an equals ('=') character immediately after the name, and before any ":" or "/" characters that might be part of the macro-string. Terms that do not contain any of "=", ":", or "/" are mechanisms, as defined in Section 5. As per the definition of the ABNF notation in [RFC5234], mechanism and modifier names are case-insensitive.

4.6.2. Mechanisms

Each mechanism is considered in turn from left to right. If there are no more mechanisms, the result is the default result as described in Section 4.7. When a mechanism is evaluated, one of three things can happen: it can match, not match, or return an exception.
Top   ToC   RFC7208 - Page 17
   If it matches, processing ends and the qualifier value is returned as
   the result of that record.  If it does not match, processing
   continues with the next mechanism.  If it returns an exception,
   mechanism processing ends and the exception value is returned.

   The possible qualifiers, and the results they cause check_host() to
   return, are as follows:

      "+" pass
      "-" fail
      "~" softfail
      "?" neutral

   The qualifier is optional and defaults to "+".

   When a mechanism matches and the qualifier is "-", then a "fail"
   result is returned and the explanation string is computed as
   described in Section 6.2.

   The specific mechanisms are described in Section 5.

4.6.3. Modifiers

Modifiers are not mechanisms. They do not return match or not-match. Instead, they provide additional information. Although modifiers do not directly affect the evaluation of the record, the "redirect" modifier has an effect after all the mechanisms have been evaluated.

4.6.4. DNS Lookup Limits

Some mechanisms and modifiers (collectively, "terms") cause DNS queries at the time of evaluation, and some do not. The following terms cause DNS queries: the "include", "a", "mx", "ptr", and "exists" mechanisms, and the "redirect" modifier. SPF implementations MUST limit the total number of those terms to 10 during SPF evaluation, to avoid unreasonable load on the DNS. If this limit is exceeded, the implementation MUST return "permerror". The other terms -- the "all", "ip4", and "ip6" mechanisms, and the "exp" modifier -- do not cause DNS queries at the time of SPF evaluation (the "exp" modifier only causes a lookup at a later time), and their use is not subject to this limit. When evaluating the "mx" mechanism, the number of "MX" resource records queried is included in the overall limit of 10 mechanisms/ modifiers that cause DNS lookups as described above. In addition to that limit, the evaluation of each "MX" record MUST NOT result in
Top   ToC   RFC7208 - Page 18
   querying more than 10 address records -- either "A" or "AAAA"
   resource records.  If this limit is exceeded, the "mx" mechanism MUST
   produce a "permerror" result.

   When evaluating the "ptr" mechanism or the %{p} macro, the number of
   "PTR" resource records queried is included in the overall limit of 10
   mechanisms/modifiers that cause DNS lookups as described above.  In
   addition to that limit, the evaluation of each "PTR" record MUST NOT
   result in querying more than 10 address records -- either "A" or
   "AAAA" resource records.  If this limit is exceeded, all records
   other than the first 10 MUST be ignored.

   The reason for the disparity is that the set of and contents of the
   MX record are under control of the publishing ADMD, while the set of
   and contents of PTR records are under control of the owner of the IP
   address actually making the connection.

   These limits are per mechanism or macro in the record, and are in
   addition to the lookup limits specified above.

   MTAs or other processors SHOULD impose a limit on the maximum amount
   of elapsed time to evaluate check_host().  Such a limit SHOULD allow
   at least 20 seconds.  If such a limit is exceeded, the result of
   authorization SHOULD be "temperror".

   As described at the end of Section 11.1, there may be cases where it
   is useful to limit the number of "terms" for which DNS queries return
   either a positive answer (RCODE 0) with an answer count of 0, or a
   "Name Error" (RCODE 3) answer.  These are sometimes collectively
   referred to as "void lookups".  SPF implementations SHOULD limit
   "void lookups" to two.  An implementation MAY choose to make such a
   limit configurable.  In this case, a default of two is RECOMMENDED.
   Exceeding the limit produces a "permerror" result.

4.7. Default Result

If none of the mechanisms match and there is no "redirect" modifier, then the check_host() returns a result of "neutral", just as if "?all" were specified as the last directive. If there is a "redirect" modifier, check_host() proceeds as defined in Section 6.1. It is better to use either a "redirect" modifier or an "all" mechanism to explicitly terminate processing. Although there is an implicit "?all" at the end of every record that is not explicitly terminated, it aids debugging efforts when it is explicitly provided.
Top   ToC   RFC7208 - Page 19
   For example:

      v=spf1 +mx -all

   or

      v=spf1 +mx redirect=_spf.example.com

4.8. Domain Specification

Several of these mechanisms and modifiers have a <domain-spec> section. The <domain-spec> string is subject to macro expansion (see Section 7). The resulting string is the common presentation form of a fully qualified DNS name: a series of labels separated by periods. This domain is called the <target-name> in the rest of this document. Note: The result of the macro expansion is not subject to any further escaping. Hence, this facility cannot produce all characters that are legal in a DNS label (e.g., the control characters). However, this facility is powerful enough to express legal host names and common utility labels (such as "_spf") that are used in DNS. For several mechanisms, the <domain-spec> is optional. If it is not provided, the <domain> from the check_host() arguments (see Section 4.1) is used as the <target-name>. "domain" and <domain-spec> are syntactically identical after macro expansion. "domain" is an input value for check_host(), while <domain-spec> is computed by check_host(). The result of evaluating check_host() with a syntactically invalid domain is undefined. Note: This document and its predecessors make no provisions for defining correct handling of a syntactically invalid <domain-spec> (which might be the result of macro expansion), per [RFC1035]. Examples include names with empty labels, such as "foo..example.com", and labels that are longer than 63 characters. Some implementations choose to treat such errors as not-match and therefore ignore such names, while others return a "permerror" exception.
Top   ToC   RFC7208 - Page 20

5. Mechanism Definitions

This section defines two types of mechanisms: basic language framework mechanisms and designated sender mechanisms. Basic mechanisms contribute to the language framework. They do not specify a particular type of authorization scheme. The basic mechanisms are as follows: all include Designated sender mechanisms are used to identify a set of <ip> addresses as being permitted or not permitted to use the <domain> for sending mail. The designated sender mechanisms are as follows: a mx ptr (do not use) ip4 ip6 exists The following conventions apply to all mechanisms that perform a comparison between <ip> and an IP address at any point: If no CIDR prefix length is given in the directive, then <ip> and the IP address are compared for equality. (Here, CIDR is Classless Inter-Domain Routing, described in [RFC4632].) If a CIDR prefix length is specified, then only the specified number of high-order bits of <ip> and the IP address are compared for equality. When any mechanism fetches host addresses to compare with <ip>, when <ip> is an IPv4, "A" records are fetched; when <ip> is an IPv6 address, "AAAA" records are 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 are only listed in an SPF record using the "ip4" mechanism. Several mechanisms rely on information fetched from the DNS. For these DNS queries, except where noted, if the DNS server returns an error (RCODE other than 0 or 3) or the query times out, the mechanism stops and the topmost check_host() returns "temperror". If the server returns "Name Error" (RCODE 3), then evaluation of the mechanism continues as if the server returned no error (RCODE 0) and zero answer records.
Top   ToC   RFC7208 - Page 21

5.1. "all"

all = "all" The "all" mechanism is a test that always matches. It is used as the rightmost mechanism in a record to provide an explicit default. For example: v=spf1 a mx -all Mechanisms after "all" will never be tested. Mechanisms listed after "all" MUST be ignored. Any "redirect" modifier (Section 6.1) MUST be ignored when there is an "all" mechanism in the record, regardless of the relative ordering of the terms.

5.2. "include"

include = "include" ":" domain-spec The "include" mechanism triggers a recursive evaluation of check_host(). 1. The <domain-spec> is expanded as per Section 7. 2. check_host() is evaluated with the resulting string as the <domain>. The <ip> and <sender> arguments remain the same as in the current evaluation of check_host(). 3. The recursive evaluation returns match, not-match, or an error. 4. If it returns match, then the appropriate result for the "include" mechanism is used (e.g., include or +include produces a "pass" result and -include produces "fail"). 5. If it returns not-match or an error, the parent check_host() resumes processing as per the table below, with the previous value of <domain> restored. In hindsight, the name "include" was poorly chosen. Only the evaluated result of the referenced SPF record is used, rather than literally including the mechanisms of the referenced record in the first. For example, evaluating a "-all" directive in the referenced record does not terminate the overall processing and does not necessarily result in an overall "fail". (Better names for this mechanism would have been "if-match", "on-match", etc.)
Top   ToC   RFC7208 - Page 22
   The "include" mechanism makes it possible for one domain to designate
   multiple administratively independent domains.  For example, a vanity
   domain "example.net" might send mail using the servers of
   administratively independent domains example.com and example.org.

   Example.net could say

      IN TXT "v=spf1 include:example.com include:example.org -all"

   This would direct check_host() to, in effect, check the records of
   example.com and example.org for a "pass" result.  Only if the host
   were not permitted for either of those domains would the result be
   "fail".

   Whether this mechanism matches, does not match, or 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 intended for crossing administrative
   boundaries.  When remaining within one administrative authority,
   "include" is usually not the best choice.  For example, if
   example.com and example.org were managed by the same entity, and if
   the permitted set of hosts for both domains was "mx:example.com", it
   would be possible for example.org to specify "include:example.com",
   but it would be preferable to specify "redirect=example.com" or even
   "mx:example.com".
Top   ToC   RFC7208 - Page 23
   With the "include" mechanism, an administratively external set of
   hosts can be authorized, but determination of sender policy is still
   a function of the original domain's SPF record (as determined by the
   "all" mechanism in that record).  The "redirect" modifier is more
   suitable for consolidating both authorizations and policy into a
   common set to be shared within an ADMD.  Redirect is much more like a
   common code element to be shared among records in a single ADMD.  It
   is possible to control both authorized hosts and policy for an
   arbitrary number of domains from a single record.

5.3. "a"

This mechanism matches if <ip> is one of the <target-name>'s IP addresses. For clarity, this 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) appropriate for the connection type (IPv4 or IPv6). The <ip> is compared to the returned address(es). If any address matches, the mechanism matches.

5.4. "mx"

This mechanism matches if <ip> is one of the MX hosts for a domain name. mx = "mx" [ ":" domain-spec ] [ dual-cidr-length ] check_host() first performs an MX lookup on the <target-name>. Then it performs an address lookup on each MX name returned. The <ip> is compared to each returned IP address. To prevent denial-of-service (DoS) attacks, the processing limits defined in Section 4.6.4 MUST be followed. If the MX lookup limit is exceeded, then "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.

5.5. "ptr" (do not use)

This mechanism tests whether the DNS reverse-mapping for <ip> exists and correctly points to a domain name within a particular domain. This mechanism SHOULD NOT be published. See the note at the end of this section for more information.
Top   ToC   RFC7208 - Page 24
   ptr              = "ptr"    [ ":" domain-spec ]

   The <ip>'s name is looked up using this procedure:

   o  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 is an IPv6 address.

   o  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 are
      exceeded, processing is terminated and the mechanism does not
      match.

   o  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 the validated domain names match or are a
   subdomain of the <target-name>, this mechanism fails to match.  If a
   DNS error occurs while doing the PTR RR lookup, then this mechanism
   fails to match.  If a DNS error occurs while doing an A RR lookup,
   then that domain name is skipped and the search continues.

   This mechanism matches if

   o  the <target-name> is a subdomain of a validated domain name, or

   o  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 is not as reliable as other
   mechanisms in cases of DNS errors, and it places a large burden on
   the .arpa name servers.  If used, proper PTR records 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 has been concluded that it is unnecessary and more
   reliable alternatives should be used instead.  It is, however, still
   in use as part of the SPF protocol, so compliant check_host()
   implementations MUST support it.
Top   ToC   RFC7208 - Page 25

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> is compared to the given network. If CIDR prefix length high-order bits match, the mechanism matches. If ip4-cidr-length is omitted, it is taken to be "/32". If ip6-cidr-length is omitted, it is taken to be "/128". It is not permitted to omit parts of the IP address instead of using CIDR notations. That is, use 192.0.2.0/24 instead of 192.0.2.

5.7. "exists"

This mechanism is used to construct an arbitrary domain name that is used for a DNS A record query. It allows for complicated schemes involving arbitrary parts of the mail envelope to determine what is permitted. 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
Top   ToC   RFC7208 - Page 26
   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 client IP address.

6. Modifier Definitions

Modifiers are name/value pairs that provide additional information. Modifiers 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 appear anywhere in the record. Ordering of these two modifiers does not 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, or how often, they appear in a record. This allows implementations conforming to this document to gracefully handle records with modifiers that are defined in other specifications.

6.1. redirect: Redirected Query

The "redirect" modifier is intended for consolidating both authorizations and policy into a common set to be shared within a single ADMD. It is possible to control both authorized hosts and policy for an arbitrary number of domains from a single record. redirect = "redirect" "=" domain-spec If all mechanisms fail to match, and a "redirect" modifier is present, then processing proceeds as follows: The <domain-spec> portion of the redirect section is expanded as per the macro rules in Section 7. Then check_host() is evaluated with the resulting string as the <domain>. The <ip> and <sender> arguments remain the same as in the current evaluation of check_host(). The result of this new evaluation of check_host() is then considered the result of the current evaluation with the exception that if no SPF record is found, or if the <target-name> is malformed, the result is a "permerror" rather than "none". Note that the newly queried domain can itself specify redirect processing.
Top   ToC   RFC7208 - Page 27
   This facility is intended for use by organizations that wish to apply
   the same record to multiple domains.  For example:

     la.example.com. TXT "v=spf1 redirect=_spf.example.com"
     ny.example.com. TXT "v=spf1 redirect=_spf.example.com"
     sf.example.com. TXT "v=spf1 redirect=_spf.example.com"
   _spf.example.com. TXT "v=spf1 mx:example.com -all"

   In this example, mail from any of the three domains is described by
   the same record.  This can be an administrative advantage.

   Note: In general, the domain "A" cannot reliably use a redirect to
   another domain "B" not under the same administrative control.  Since
   the <sender> stays the same, there is no guarantee that the record at
   domain "B" will correctly work for mailboxes in domain "A",
   especially if domain "B" uses mechanisms involving local-parts.  An
   "include" directive will generally be more appropriate.

   For clarity, any "redirect" modifier SHOULD appear as the very last
   term in a record.  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" due to a mechanism match (such as "-all"), and the "exp" modifier is present, then the explanation string returned is computed as described below. If no "exp" modifier is present, then either a default explanation string or an empty explanation string MUST be returned to the calling application. The <domain-spec> is macro expanded (see Section 7) and becomes the <target-name>. 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 are returned, or if more than one record is returned, or if there are syntax errors in the explanation string, then proceed as if no "exp" modifier was given. The fetched TXT record's strings are concatenated with no spaces, and then treated as an explain-string, which is macro-expanded. This final result is the explanation string. Implementations MAY limit the length of the resulting explanation string to allow for other protocol constraints and/or reasonable processing limits. Since the explanation string is intended for an SMTP response and Section 2.4 of [RFC5321] says that responses are in [US-ASCII], the explanation string MUST be limited to [US-ASCII].
Top   ToC   RFC7208 - Page 28
   Software evaluating check_host() can use this string to communicate
   information from the publishing domain in the form of a short message
   or URL.  Software SHOULD make it clear that the explanation string
   comes from a third party.  For example, it can prepend the macro
   string "%{o} explains: " to the explanation, as shown in the example
   in Section 8.4.

   Suppose example.com has this record:

      v=spf1 mx -all exp=explain._spf.%{d}

   Here are some examples of possible explanation TXT records at
   explain._spf.example.com:

      "Mail from example.com should only be sent by its own servers."

         -- a simple, constant message

      "%{i} is not one of %{d}'s designated mail servers."

         -- a message with a little more information, including the
            IP address that failed the check

      "See http://%{d}/why.html?s=%{S}&i=%{I}"

         -- a complicated example that constructs a URL with the
            arguments to check_host() so that a web page can be
            generated with detailed, custom instructions

   Note: During recursion into an "include" mechanism, an "exp" modifier
   from the <target-name> MUST NOT be used.  In contrast, when executing
   a "redirect" modifier, an "exp" modifier from the original domain
   MUST NOT be used.  This is because "include" is meant to cross
   administrative boundaries and the explanation provided should be the
   one from the receiving ADMD, while "redirect" is meant to operate as
   a tool to consolidate policy records within an ADMD so the redirected
   explanation is the one that ought to have priority.

7. Macros

When evaluating an SPF policy record, certain character sequences are intended to be replaced by parameters of the message or of the connection. These character sequences are referred to as "macros".
Top   ToC   RFC7208 - Page 29

7.1. Formal Specification

The ABNF description for a macro is as follows: 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 is subject to the letter-digit-hyphen (LDH) rule plus additional top-level domain (TLD) restrictions. See Section 2 of [RFC3696] for background. Some special cases: o A literal "%" is expressed by "%%". o "%_" expands to a single " " space. o "%-" expands to a URL-encoded space, viz., "%20".

7.2. Macro Definitions

The following macro letters are 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
Top   ToC   RFC7208 - Page 30
   <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

A '%' character not followed by a '{', '%', '-', or '_' character is a syntax error. So: -exists:%(ir).sbl.example.org is incorrect and will cause check_host() to yield a "permerror". Instead, the following is legal: -exists:%{ir}.sbl.example.org Optional transformers are the following: *DIGIT = zero or more digits 'r' = reverse value, splitting on dots by default If transformers or delimiters are provided, the replacement value for a macro letter is split into parts separated by one or more of the specified delimiter characters. After performing any reversal operation and/or removal of left-hand parts, the parts are rejoined using "." and not the original splitting characters. By default, strings are split on "." (dots). Note that no special treatment is given to leading, trailing, or consecutive delimiters in input strings, and 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 specify delimiter characters that are used instead of ".". The "r" transformer indicates a reversal operation: if the client IP address were 192.0.2.1, the macro %{i} would expand to "192.0.2.1" and the macro %{ir} would expand to "1.2.0.192". The DIGIT transformer indicates the number of right-hand parts to use, after optional reversal. If a DIGIT is specified, the value MUST be nonzero. If no DIGITs are specified, or if the value specifies more parts than are available, all the available parts are
Top   ToC   RFC7208 - Page 31
   used.  If the DIGIT was 5, and only 3 parts were available, the macro
   interpreter would pretend the DIGIT was 3.  Implementations MUST
   support at least a value of 127, as that is 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.  It is an email
   address with a local-part, an "@" character, and a domain.  The "l"
   macro expands to just the local-part.  The "o" macro expands to just
   the domain part.  Note that these values remain the same during
   recursive and chained evaluations due to "include" and/or "redirect".
   Note also that 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; it
   is 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 is intended for humans to read.

   The "p" macro expands to the validated domain name of <ip>.  The
   procedure for finding the 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 (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 used.

   The "r" macro expands to the name of the receiving MTA.  This SHOULD
   be a fully qualified domain name, but if one does not exist (as when
   the checking is done by a Mail User Agent (MUA)) or if policy
   restrictions dictate 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.
Top   ToC   RFC7208 - Page 32
   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.  This is the same value as
   the value that is returned by the Portable Operating System Interface
   (POSIX) time() function in most standards-compliant libraries.

   When the result of macro expansion is used in a domain name query, if
   the expanded domain name exceeds 253 characters (the maximum length
   of a domain name in this format), the left side is truncated to fit,
   by removing successive domain labels (and their following dots) until
   the total length does not exceed 253 characters.

   Uppercase macros expand exactly as their lowercase equivalents, and
   are then URL escaped.  URL escaping MUST be performed for characters
   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 does not 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 is better if sending
   ADMDs avoid using the "s", "l", "o", or "h" macros in conjunction
   with any mechanism directive.  Although these macros are powerful and
   allow per-user records to be published, 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

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.
Top   ToC   RFC7208 - Page 33
   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



(page 33 continued on part 3)

Next Section