tech-invite   World Map     

IETF     RFCs     Groups     SIP     ABNFs    |    3GPP     Specs     Gloss.     Arch.     IMS     UICC    |    Misc.    |    search     info

RFC 7208

 
 
 

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

Part 2 of 4, p. 14 to 33
Prev RFC Part       Next RFC Part

 


prevText      Top      Up      ToC       Page 14 
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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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      Up      ToC       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 RFC Part