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.
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.
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.
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
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.
For example: v=spf1 +mx -all or v=spf1 +mx redirect=_spf.example.com4.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.
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.
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.)
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".
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.
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.
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
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.
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].
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".
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
<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 timestamp7.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
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.
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.
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