Sender Policy Framework (SPF) is a simple email-validation system designed to detect email spoofing. It does this by providing a mechanism to allow receiving mail exchanges to check that incoming mail from a domain comes from a host authorized by that domain's administrators.
Reasons to implement:
If a domain publishes an SPF record, spammers and phishers are less likely to forge emails pretending to be from that domain, because the forged emails are more likely to be caught in spam filters which check the SPF record.
SPF Generating Resources:
SPF Verification tools:
SPF Mechanisms:
Mechanisms can be prefixed with one of four qualifiers:
"+" | Pass |
"-" | Fail |
"~" | SoftFail |
"?" | Neutral |
If a mechanism results in a hit, its qualifier value is used. The default qualifier is "+", i.e. "Pass". For example:
"v=spf1 -all" "v=spf1 a -all" "v=spf1 a mx -all" "v=spf1 +a +mx -all"If a domain has no SPF record at all, the result is "None". If a domain has a temporary error during DNS processing, you get the result "TempError" (called "error" in earlier iterations). If some kind of syntax or evaluation error occurs (e.g. the domain specifies an unrecognised mechanism) the result is "PermError" (formerly "unknown").
Evaluation of an SPF record can return any of these results:
Result | Explanation | Intended action |
Pass | The SPF record designates the host to be allowed to send | accept |
Fail | The SPF record has designated the host as NOT being allowed to send | reject |
SoftFail | The SPF record has designated the host as NOT being allowed to send but is in transition | accept but mark |
Neutral | The SPF record specifies explicitly that nothing can be said about validity | accept |
None | The domain does not have an SPF record or the SPF record does not evaluate to a result | accept |
PermError | A permanent error has occurred (eg. badly formatted SPF record) | unspecified |
TempError | A transient error has occurred | accept or reject |
The "IP4" mechanism:
ip4:<ip4-address> ip4:<ip4-address>/prefix-length>
The argument to the "ip4:" mechanism is an IPv4 network range. If noprefix-length is given, /32 is assumed (singling out an individual host address).
Examples:
"v=spf1 ip4:192.168.0.1/16 -all"
Allow any IP address between 192.168.0.1 and 192.168.255.255.
"v=spf1 ip4:192.168.0.1 -all"Allow from only 192.168.0.1
The "IP6" mechanism:
ip6:<ip6-address> ip6:<ip6-network>/<prefix-length>The argument to the "ip6:"mechanism is an IPv6 network range. If noprefix-length is given, /128 is assumed (singling out an individual host address).
Examples:
"v=spf1 ip6:1080::8:800:200C:417A/96 -all"Allow any IPv6 address between 1080::8:800:0000:0000 and 1080::8:800:FFFF:FFFF.
"v=spf1 ip6:1080::8:800:68.0.3.1/96 -all"The "a" mechanism:
a/<prefix-length> a:<domain>/<prefix-length>
All the A records for domains are tested. If the client IP is found among them, this mechanism matches.
If a domain is not specified, the current-domain is used.
The A records have to match the client IP exactly, unless a prefix-length is provided, in which case each IP address returned by the A lookup will be expanded to its corresponding CIDR prefix, and the client IP will be sought within that subnet.
"v=spf1 a -all"The current-domain is used:
"v=spf1 a:example.com -all"Equivalent if the current-domain is example.com.
"v=spf1 a:mailers.example.com -all"Perhaps example.com has chosen to explicitly list all the outbound mailers in a special A record under mailers.example.com.
"v=spf1 a/24 a:offsite.example.com/24 -all"If example.com resolves to 192.0.2.1, the entire class C of 192.0.2.0/24 would be searched for the client IP. Similarly for offsite.example.com. If more than one A record were returned, each one would be expanded to a CIDR subnet.
The "ptr" mechanism:
ptr:<domain>The hostname or hostnames for the client IP are looked up using PTR queries. The hostnames are then validated: at least one of the A records for a PTR hostname must match the original client IP. Invalid hostnames are discarded. If a valid hostname ends in domain, this mechanism matches.
If a domain is not specified, the current-domain is used.
If at all possible, you should avoid using this mechanism in your SPF record, because it will result in a larger number of expensive DNS lookups.
Examples:
"v=spf1 ptr -all"A domain which directly controls all its machines (unlike a dial up or broadband ISP) allows all its servers to send mail. For example, paypal.com might do this.
"v=spf1 ptr:otherdomain.com -all"Any server whose hostname ends in otherdomain.com is designated.
The "include" mechanism:
include:<domain>The specified domain is searched for a match. If the lookup does not return a match or an error, processing proceeds to the next directive. Warning: If the domain does not have a valid SPF record, the result is a permanent error. Some mail receivers will reject based on a "PermError".
Examples:
In the following example, the client IP is 1.2.3.4 and the current-domain is example.com.
"v=spf1 include:example.com -all"If example.com has no SPF record, the result is "PermError".
Suppose example.com's SPF record were "v=spf1 a -all".
Look up the A record for example.com. If it matches 1.2.3.4, return "Pass".
If there is no match, other than the included domain's "-all", the include as a whole fails to match; the eventual result is still "Fail" from the outer directive set in this example.
Trust relationships: The include: mechanism crosses administrative boundaries and should only be used with domains you trust, as it authorizes their SPF policy to send mail on your behalf. It returns a "Pass" if the included domain validates the sender. Using ?include: is not recommended, as it results in a "Neutral" outcome and can cause legitimate senders to fail SPF when combined with mechanisms like -all. The example above would be:
"v=spf1 ?include:example.com -all"In hindsight, the name "include" was poorly chosen. Only the evaluated result of the referenced SPF record is used, rather than acting as if the referenced SPF record was literally included 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-pass", "on-pass", etc.)