Howto Define an SPF Record
From: https://www.zytrax.com/books/dns/ch9/spf.html
HOWTO - Define an SPF Record
This page defines configuration of a Sender Policy Framework (SPF) record
for a domain and its mail servers. DKIM/ADSP is an alternative or
complimentary approach involving the signing of mail to authenticate the
sender domain. DKIM is significantly more complex than SPF but provides
significantly more functionality. Finally, the DMARC RR provides a method by
which a domain can advise the mail receiver whether mail originating from a
domain will have DKIM, SPF or both, what to do with mail that fails either
DKIM or SPF checks and whether it wishes failure reports.
The macro feature of SPF was incorrectly documented using parenthesis,
these should have been braces ({}).
SPF Introduction and Overview
SPF was initiated by Meng Weng Wong of pobox.com to enable validation of
legitimate sources of email for a domain and is an IETF standard (RFC
7208).
Briefly, the design intent of the SPF resource record (RR) is to allow a
receiving MTA (Message Transfer Agent) to interrogate the Name Server (DNS)
of the domain which appears in the email (the sender) and determine if the
originating IP of the mail (the source) is authorized to send mail for the
sender's domain. The mail sender is required to publish an SPF TXT RR
(documented here) in the DNS zone file for their domain but this is
transparent to the sending MTA. That is, the sending MTA does not use the
sending domain's SPF RR(s) but the receiving domain's MTA will interrogate
and use the sending domain's SPF RR(s).
The SPF information must be defined using a standard TXT resource record
(RR).
Note:
The current SPF RFC (RFC 7208) has essentially moved the SPF RR
(defined by RFC 4408) to a deprecated status. The logic behind this advice
is that while your DNS may support the SPF RR capability, it's the receiving
MTA's DNS (and the MTA's SPF handler) which determines SPF processing and it
may not support the SPF RR type but will ALWAYS support the TXT RR type. For
the foreseeable future if a domain has both a SPF TXT RR and an SPF RR it
will do no harm to leave the SPF RR in place but it should be removed at the
earliest sensible opportunity. No mail domains should be using an SPF RR
only since this has never been a recomended practice. This page has removed
all references to the SPF RR except for this explanation. The term SPF TXT
RR, while a little long-winded, is used throughout to mean a TXT RR with the
SPF-specific data encoded within its text field.
If a SPF TXT RR exists and authorizes the source IP address the mail can be
accepted by the MTA. If the SPF TXT RR does not authorize the IP address,
the mail can be bounced - it did not originate from an authorized source for
the sender's domain.
Most Open Source MTAs (as well as commercial mail software) have been
modified (or have plug-ins) to use the SPF record. There is no down-side
(assuming you get the SPF TXT RR right) and plenty of potential up-side
(elimination/reduction of some spam categories) in implementing the SPF
record.
We use the following terminology to try to simplify the descriptions below:
- sender - the full email address of the originator of the mail item
(typically uses return-path in the actual SPF checks), for instance,
me@example.com
- source-ip - the IP address of the SMTP server trying to send this message
- sender-domain the domain name part of the sender's email address, for
instance, assume the sender is info@example.com the sender-domain is example.com.
The SPF record defines one or more tests to carry out to verify the sender.
Each test returns a condition code (pre below). The first test to pass will
terminate SPF processing.
Configuration of an SPF record may look complicated (all those blasted
options) but in practise tends to be quite simple once you understand what
it is trying to do. While we suggest you slog your way through the
description below, those of a more impatient disposition may want to simply
jump to the examples, select the one that most closely fits the
circumstances and then go back and read the appropriate text to understand
what it is doing.
SPF TXT RR Format - Gruesome Details
The standard SPF TXT resource record format is defined as:
name ttl class TXT "SPF-specific text"
The SPF TXT RR is a standard TXT resource record with SPF-specific data
encoded within the text field and is defined by RFC 7208. The TXT RR is the
only RR type currently defined to support SPF functionality. The previous
version of the RFC (RFC 4408) defined, and allowed use of, an SPF RR type.
The SPF RR type has, essentially, been deprecated but should continue to
work in DNS software that supports it (not all do) for the foreseeable
future but should be removed from the zone file at the earliest sensible
opportunity.
The SPF data is entirely contained in the text field (a quoted string). SPF
defines the contents of the quoted string as follows:
v=spf1 [[pre] type ] ... [mod]
As may be seen the SPF TXT RR allows multiple type arguments to be included.
Processing cycles from left to right until either a conclusive result is
obtained or until the type arguments have been exhausted. The last type
argument should be all which terminates the validation sequence.
Where:
v=spf1
Mandatory. Defines the version being used. Currently the only version
supported is spf1.
pre
Optional (defaults to +). May take one of the following values:
Value
| Description
|
+ | Default. Pass. Domain owner deems this a conclusive test.
|
- | Fail. Domain owner deems this a conclusive test.
|
~ | Softfail. Domain owner deems this an inconclusive test - mail should be treated as if no SPF existed.
|
? | Neutral. Domain owner deems this an inconclusive test - mail should be accepted.
|
Each sender mechanism returns either a match (the test was successful) or a
no match (the test failed) condition. pre defines what happens according to
the following matrix:
Sender Mechanism returns
| pre
| Result
| Notes
|
match | + | Pass | Final result. Processing stops.
|
match | - | Fail | Final result. Processing stops.
|
match | ? or ~ | Indeterminate | Intermediate result. Processing continues.
|
In all cases if no match is returned processing continues with the next
type.
Thus, if a sender mechanism test is conclusive either add + or omit
(defaults to +). If a test might not be conclusive use "?" or "~" (tilde)
processing will continue with the next type. "-"(minus) is typically only used
with -all to indicate that if we have had no previous matches - fail but
could be used as a negative test such as "if this test matches fail the mail...".
type
Defines the mechanism type to use for verification of the sender. May take one
of the following values:
Basic Mechanisms
These types do NOT define a verification mechanism but affect the verification
sequence.
include - Recurse (restart) testing using supplied domain. The sender-domain
is replaced with the included domain name. Example:
; spf record for example.com
$ORIGIN example.com.
....
example.com. IN TXT "v=spf1 include:example.net -all"
; use the SPF details for example.net
; in the above case to replace example.com's SPF
; or
example.com. IN TXT "v=spf1 mx include:example.net -all"
; additive - use MX RR for example.com
; AND if that fails use example.nets's SPF
all - The all type terminates processing (but may be optionally followed by
a mod value). The all type is defined to be optional but it is a Good
Thing™ to include since it explicitly defines the end of the argument
chain. It is normally present in the form -all to signify that if processing
reaches this point without a prior match the result will be fail (the
receiving MTA should reject the mail item). But if you are not sure that the
tests are conclusive you could use ?all (Neutral result and the default if
no all type is present and there is no redirect) which could (depending on
the receiving MTA policy) allow mail to be accepted even if all previous
checks failed. If +all is present this indicates that, even if every test
has failed, the receiving MTA should still accept the mail item. In summary,
this means the sender has no idea where the mail is coming from and (other
than initial testing) is pretty pointless.
Sender Mechanisms
These types define a verification mechanism.
| |
|
ip4 | - | use IP Version 4 addresses, for example, 192.168.3.0 for
verification
|
ip6 | - | use IP Version 6 addresses for verification, for example,
2001:db8::10 for verification
|
a | - | use DNS A RRs for verification
|
mx | - | use DNS MX RRs for verification
|
ptr | - | use DNS PTR RRs for verification
|
exists | - | test for existence of domain
|
Value
| Description
|
a
a:domain
a:domain/cidr
a/cidr
|
In its base form this uses the sender-domain to find an A RR(s) to verify
the source. This form relies on an A RR for the domain as shown:
; fragment for example.com
$ORIGIN example.com.
....
example.com. IN TXT "v=spf1 a -all"
; the above could have been written as either
@ IN TXT "v=spf1 a -all"
IN TXT "v=spf1 a -all"
; needs domain A record
@ IN A 192.168.0.3
; functionally the same as
example.com. IN A 192.168.0.3
The form a/cidr applies the test to the cidr (IP prefix or slash notation) range of the
sender-domain's A RR. For more info on CIDR.
The form a:domain replaces sender-domain with domain's A RR for verification. This does NOT
use domain's SPF record(s) (use include for that). The domain form may use macro-expansion
features. Example:
; fragment for example.net
$ORIGIN example.net.
....
@ IN TXT "v=spf1 a:example.com -all"
; will use a single A query to example.com
; which may not yield the result expected unless
; example.com has an A record as below
@ IN A 192.168.0.3
; functionally the same as
example.com. IN A 192.168.0.3
....
can take a host name format as shown below:
; fragment for example.net
$ORIGIN example.net.
....
@ IN TXT "v=spf1 a:mail.example.com -all"
; will use a single A query for mail.example.com
....
The form a:domain/cidr applies the cidr range to the IP address obtained
from the A query e.g.
; fragment for example.net
$ORIGIN example.net.
....
@ IN TXT "v=spf1 a:mail.example.com/27 -all"
; will use a single A query for mail.example.com
....
Any of the 32 IP addresses that contain mail.example.com will pass. e.g. if
the source-ip is 192.168.0.25 and the A RR for mail.example.net is
192.168.0.2 then the test will pass.
|
mx
mx:domain
mx:domain/cidr
mx/cidr
|
This basic form without any extensions uses the MX RR of the sender-domain
to verify the mail source-ip. The MX record(s) return a host name from which
the A record(s) can be obtained and compared with the source-ip. The form
mx/cidr applies the IP Prefix or slash range to the A RR address. For more
info on CIDR. With any of the domain extensions the MX record of the
designated (substituted) domain is used for verification. The domain form
may use macro-expansion features.
Warning Remember the MX RR defines the receiving MTA for the domain. If this
is not the same host(s) as the sending (SMTP) MTA then tests based on an mx
type will fail. We have also received a report that mx on its own is
rejected by certain SPF libraries. We regard this as an error and are trying
to contact the library developer to clarify the issue.
Examples:
; fragment for example.com
$ORIGIN example.com.
....
@ IN TXT "v=spf1 mx:example.net -all"
; verify sender using example.net MX and A RRs
; fragment for example.com
$ORIGIN example.com.
....
@ IN TXT "v=spf1 mx/26 -all"
; verify sender using example.com MX and A RRs
; and use 64 IP address range
ptr
ptr:domain
|
Use the source-ip's PTR RR and a reverse map query. The A/AAAA RR for the
host is then obtained. If this IP matches the sender-ip AND the sender
-domain is the same as the domain name of the host obtained from the PTR RR
then the test passes. The form ptr:domain replaces the sender-domain with
domain in the final check for a valid domain name. The domain form may use
macro-expansion features. Note: The ptr sender mechanism is strongly
discouraged by RFC 7208 which even goes so far as to suggest its immediate
removal for performance reasons since it places a load on the IN-ADDR.ARPA
(IPv4) or IP6.ARPA reverse-map domains which generally have less capacity
than the gTLD and ccTLD domains. Examples:
; fragment for example.com
$ORIGIN example.com.
....
@ IN TXT "v=spf1 ptr -all"
; the effect is to allow any host which is
; reverse mapped in the domain to send mail
; fragment for example.net
$ORIGIN example.net.
....
@ IN TXT "v=spf1 ptr:example.com -all"
; the effect is to allow any host which is
; reverse mapped in the domain example.com to send mail
; for example.net
....
|
ip4:ipv4
ip4:ipv4/cidr
|
In its basic form defines an explicit ipv4 address to verify the mail source
-ip. If the source-ip is the same as ipv4 the test passes. May optionally
take the form ip4:ipv4/cidr to define a valid IP address range using the
slash or IP Prefix notation. For more info on CIDR. Since this type incurs
the least additional load on the DNS the current draft of the proposed RFC
recommends this format. Examples:
; fragment for example.com
$ORIGIN example.com.
....
@ IN TXT "v=spf1 ip4:192.168.0.2 -all"
; if source-ip is 192.168.0.2 test passes
; cidr format
@ IN TXT "v=spf1 ip4:192.168.0/27 -all"
; if source-ip is in range 192.168.0.1
; to 192.168.0.31 test passes
....
Notes:
When writing a cidr format, only the number of IP address components
required need to be present. Thus, to extract 32 addresses starting from
192.168.0.0 the above format is all that is required. However, it could also
have been written as ip4:192.168.0.23/27 or any other IP address that lies
within the required subnet. The value ip4:192.168.0.132/27 will validate the
32 IP address range from 192.168.0.128 to 192.168.0.159.
|
ip6:ipv6
ip6:ipv6/cidr
|
In its basic form defines an explicit ipv6 address to verify the mail source
-ip. If the source-ip is the same as ipv6 the test passes. May optionally
take the form ipv6/cidr to define a valid IP address range. For more info on
CIDR. Since this type incurs the least additional load on the DNS the RFC
recommends this format. Examples:
; fragment for example.com
$ORIGIN example.com.
....
@ IN TXT "v=spf1 ip6:2001:db8::10 -all"
; if source-ip is 2001:db8:0:0:0:0:0:10 test passes
; cidr format
@ IN TXT "v=spf1 ip6:2001:db8::10/120 -all"
; if source-ip is in range 2001:db8:0:0:0:0:0:0
; to 2001:db8:0:0:0:0:0:FF test passes
....
|
exists:domain
|
The existence (any valid A RR) of the specified domain allows the test to pass.
Domain may use macro-expansion features.
Note:
RFC 7208 Appendix C suggests a method by which the exists sender mechanism (in
conjunction with a macro expansion) may be used to log DNS queries for every
mail transaction and which may help in diagnosing SPF problems. While this
indeed may be the case, it also may, depending on the mail volume being sent,
generate significant DNS loads. Your choice.
| |
mod
Two optional record modifiers are defined. If present they should follow the
last type directive i.e. after the all. The current values defined are as
follows:
Modifier
| Description
|
redirect=domain
| Redirects verification to use the SPF records of the defined domain.
Functionally equivalent to include but can appear on its own (without a
terminating all) or can placed after the all which means "if all the previous
test fail try this redirect".
Examples:
; fragment for example.com
$ORIGIN example.com.
....
; multi test redirect format
@ IN TXT "v=spf1 ip4:192.168.0.2 -all redirect=example.net"
; if source-ip is 192.168.0.2 the test passes
; if it fails redirect to example.net
; OR single redirect format
@ IN TXT "v=spf1 redirect=example.net"
; only use example.net SPF TXT RR
|
exp=txt-rr
|
The exp record if present should come last in a SPF record (after the all if
present). It defines a DNS name whose TXT record's text may be returned with
any failure message. Example:
; example.com fragment
$ORIGIN example.com.
....
; domain SPF record
example.com. IN TXT "v=spf1 mx -all exp=getlost.example.com"
....
; the getlost TXT record
getlost IN TXT "Not allowed to send mail for domain"
Notes:
1. The syntax allowed by this record is significantly more complex (see
macro-expansion below.
|
Macro-Expansion
SPF defines a number of macro-expansion features as defined below:
Note: all macro-expansion delimiters use braces {}.
Modifier
| Description
|
%{c} | Only allowed in TXT records referenced by the exp field. The IP of the
receiving MTA.
|
%{d} | The current domain, normally the sender-domain %{o} but replaced by the
value of any domain argument in the sender mechanism type.
|
%{h} | The domain name supplied on HELO or EHLO, normally the hostname of the
sending SMTP server.
|
%{i} | sender-ip The IP of SMTP server sending mail for user, say,
info@example.com.
|
%{l} | replace with local part of sender, for instance, if sender is
info@example.com, the local part is info.
|
%{o} | The sender-domain, for instance, if email address is info@example.com
the sender-domain is example.com.
|
%{p} | The validated domain name. The name obtained using the PTR RR of the
sender-ip. Use of this macro will require an additional query unless a ptr
sender mechanism is used. Note: Both the %p and the ptr sender mechanism are
strongly discouraged by RFC 7208 which even goes so far as to suggest their
immediate removal for performance reasons. ooh.
|
%{r} | Only allowed in TXT records referenced by the exp field. The name of
the host performing the SPF check. Normally the same as the receiving MTA.
|
%{t} | Only allowed in TXT records referenced by the exp field. Current
timestamp.
|
%{s} | Replace with sender email address, for instance, info@example.com
|
%{v} | Replaced with "in-addr" if sender-ip is an IPv4 address and "ip6" if an IPv6 address.
Used to construct reverse map strings.
|
The above macros may take one or more additional arguments as follows:
- r - Indicates reverse the order of the field, for instance, %{or} would
display example.com as com.example and %{ir} would display 192.168.0.2 as
2.0.168.192. The normal split uses "." (dot) as the separator but any other
character may be used to define the split but a "." (dot) is always used when
rejoining so, for instance, %{sr@} would display info@example.com as
example.com.info.
- digit - the presence of a digit (range 1 to 128) limits the number of right
most elements displayed, for instance, %{d1} displays only com only from
example.com but %{d5} would display five right hand elements up to the
maximum available, in this case it will display example.com since that is
all that is available.
Examples
Note:
In the various example fragments shown below we use the terms 'domain SPF' and
'mail host SPF'. The domain SPF can be placed anywhere at the top, or apex, of
the domain according to taste, say, immediately after the SOA RR or most
typically after the MX RR ('cos it just makes sense). A mail host SPF is an
optional and defensive strategy since some SPF library routines can use the
full name of the sending MTA (from the HELO/EHLO) to look for the SPF TXT RR.
This optional RR will normally be placed immediately after the A or AAAA RR
which defines the mail server.
Example 1
Example 1: Assumes a single mail server which both sends and receives mail
for the domain.
; zone file fragment for example.com
$ORIGIN example.com.
....
IN MX 10 mail.example.com.
; SPF stuff
; domain SPF
example.com. IN TXT "v=spf1 mx -all"
# this could have been writen as either
@ IN TXT "v=spf1 mx -all"
IN TXT "v=spf1 mx -all"
....
mail IN A 192.168.0.4
; defensive mail host SPF
mail IN TXT "v=spf1 a -all"
Notes:- The SPF record states that the A record of mail.example.com alone is
permitted to send mail for the domain.
- The domain SPF is returned from a sender-domain query constructed from the
sender's email address. Thus, if the sender = info@example.com, then sender
-domain = example.com. The SPF record only allows the MX host to send for
the domain.
- The mail host SPF is present in case the receiving MTA uses a reverse query
to obtain the source-ip host name and then does a query for the SPF record
of that host.
- If the domain contains multiple MX servers the domain SPF would stay the
same but each mail host should have a mail host SPF/TXT RR.
Example 2
Example 2: Assumes the domain will send mail through an offsite mail server,
for instance, an ISP:
; zone file fragment for example.com
$ORIGIN example.com.
....
IN MX 10 mail.offsite.com.
; SPF stuff
; domain SPF
example.com. IN TXT "v=spf1 include:offsite.com -all"
; the above could also have been written as either
@ IN TXT "v=spf1 include:offsite.com -all"
IN TXT "v=spf1 include:offsite.com -all"
; WARNING: offsite.com MUST have a valid SPF definition
....
Notes:- This format should be used IF AND ONLY IF you know that offsite.com has a
valid SPF configuration.
- include recurses (restarts) verification using the SPF records for
offsite.com. Mail configuration changes are localised at offsite.com which
may simplify administration.
- include could have been replaced with redirect.
- In the case where offsite.com does NOT have an SPF TXT RR then an ip4
variant can be used as a weak alternative. Assume, in the example above that
the SMTP server of offsite.com is smtp.offsite.com (pretty creative!) whose
IPv4 address is 192.168.3.7 then we could use the following SPF TXT
formats:
; SPF stuff
; domain SPF
example.com. IN TXT "v=spf1 ip4:192.168.3.7 -all"
The above is a weak alternative because offsite.com may in turn relay or
send mail from other host names or even use other host domain names. It
should be used only in extreme cases or when the ISPs mail architecture is
well known.
Example 3
Example 3: Assumes we are the host for a number of virtual mail domains AND
that we can send mail from any host in our subnet.
Zone file fragment for one of the virtual mail domains:
; zone file fragment for vhost1.com
$ORIGIN vhost1.com.
....
IN MX 10 mail.example.com.
; SPF stuff
; domain SPF
vhost1.com. IN TXT "v=spf1 include:example.com -all"
; the above could also have been written as either
@ IN TXT "v=spf1 include:example.com -all"
IN TXT "v=spf1 include:example.com -all"
....
Notes:- The domain SPF is returned from a sender-domain query constructed from the
sender's email address. Thus, if the sender = info@vhost1.com, then sender
-domain = vhost1.com. The SPF TXT RR recurses to the domain example.com for
verification. That is, the receiving MTA will read the SPF TXT RR from
example.com to verify the mail.
Zone file for example.com
; zone file fragment for example.com
....
IN MX 10 mail.example.com.
; SPF stuff
; domain SPF - any host from
; 192.168.0.1 to 192.168.0.30 (32 - bcast and mcast = 30)
; can send mail
example.com. IN TXT "v=spf1 ip4:192.168.0/27 -all"
; the above could have been written as either
@ IN TXT "v=spf1 ip4:192.168.0/27 -all"
IN TXT "v=spf1 ip4:192.168.0/27 -all"
....
; defensive mail host SPF
mail IN TXT "v=spf1 ip4:192.168.0/27 -all"
Notes:
- This SPF record allows any host in the 32 address subnet which contains
192.168.0 to send mail for this and any host virtual domain, for instance,
vhost1.com in the above example. NOTE: while /27 allows 32 IP addresses
subnet rules remove 192.168.0.0 and 192.168.0.31 as the multicast and
broadcast addresses respectively. [read more about IPv4 Classes]
- The domain SPF is returned from a sender-domain query constructed from the
sender's email address. Thus, if the sender = info@example.com sender-domain
= example.com.
- In the above scenario we could have used a slightly shorter version such
as:
example.com. IN TXT "v=spf1 mx/27 -all"
- This record has the same effect as a:192.168.0/27 above but will cost a
further DNS look up operation whereas the IP is already available.
- The above scenario relies on the fact that customers will only send mail
via the domain example.com, that is, they will NOT send via another ISP at
home or when travelling. If you are not sure if this is the case you can
terminate the sequence with ?all which says kinda pass (soft fail) and let
the mail go through - perhaps using the DMARC RR to have such instances
reported to you by the receiving MTA.
- If the domain contains multiple MX servers the domain SPF would stay the
same but each mail host should have a defensive SPF record.
Example 4
Example 4: Assumes that the domain never sends mail from ANY location -
ever. Typically you would do this to prevent bogus mail for everyone else -
it is a supreme act of self-sacrifice!
; zone file fragment for example.com
; zone does NOT contain MX record(s)
...
; SPF stuff
; domain SPF
example.com. IN TXT "v=spf1 -all"
; the above could have been written as either
@ IN TXT "v=spf1 -all"
IN TXT "v=spf1 -all"
....
Notes:- This SPF test will always fail since the only condition it tests is the all
which results in a fail.
- You will get a warm inner glow for the rest of your life if you implement
this type of SPF TXT RR. We're talking a serious incentive here folks.
- If your domain does not receive mail then, due to the somewhat strange
procedures defined in RFC 5321 (section 5), simply not publishing an MX RR
may still result in A/AAAA lookups thus generating unwanted and useless
traffic. Instead a NULL MX RR should be published (see RFC 7505 with the
following format:.
# zone file fragment for example.com
# which will not accept any incoming email
...
$ORIGIN example.com.
...
# NULL MX RR
# the dot (.) signifies a null name
# and indicates definitively this domain cannot receive email
# use of zero in pref field indicates the highest preference
# This MUST be the only MX RR for this domain
MX 0 .
...
Example 5
Example 5: Illustrates various macro expansion features:
; zone file fragment for example.com
$ORIGIN example.org.
....
IN MX 10 mail.example.com.
; SPF records
; domain SPF
@ IN TXT "v=spf1 exists:%{ir}.%{v}.arpa -all exp=badguy.example.com"
...
badguy IN TXT "The email from %{s} using SMTP server at %{i}
was rejected by %{c} (%{r}) at %{t} because it failed
the SPF records check for the domain %{p}.
Please visit http://abuse.example.com/badguys.html
for more information"
....
Notes:- The badguy TXT above is split across multiple lines for presentation
reasons only and should appear on a single line in the zone file.
- The exists:%{ir}.%{v}.arpa test is a great example BUT IT WILL NOT WORK
(sigh!) because the exists type checks for an A RR, whereas a reverse lookup
is defined using a PTR RR. But it does show the power of macro-expansion and
we could not think of a better example or use. However, Stuart Gatham could
and suggested using the reversed IP address in a DNS Black List (DNSBL) as
shown here:
- @ IN TXT "v=spf1 exists:%{ir}.blacklist.example.com -all exp=badguy.example.com"
- If using this method you may also want to change the text in the
badguy.example.com record to reflect the new failure. Many thanks for the
suggestion.