Current section
Files
Jump to
Current section
Files
README.md
# Spfcheck[](https://github.com/hertogp/spfcheck/actions/workflows/elixir.yml)[](https://hex.pm/packages/spfcheck)[](https://hexdocs.pm/spfcheck/)[](https://github.com/hertogp/spfcheck/commits/main)[](https://github.com/hertogp/spfcheck/blob/main/LICENSE.md)[](https://hex.pm/packages/spfcheck)<!-- @MODULEDOC -->`spfcheck` is a command line tool to examine and debug SPF records.## Usage```txtUsage: spfcheck [options] [sender ...]where sender = [localpart@]domain and localpart defaults to 'postmaster'Options: -H, --help print this message and exit -a, --author=string sets author in the markdown metadata (default spfcheck) -c, --color use color for log messages on stderr (default is true) -d, --dns=filepath file with DNS RR records to prepopulate the DNS cache -h, --helo=string sending MTA helo/ehlo identity (defaults to nil) -i, --ip=string sending MTA IPv4/IPv6 address (defaults to 127.0.0.1) -m, --markdown use markdown format for output (default depends, see Report flag) -n, --nameserver=string an IPv4/IPv6 address of a nameserver to use -r, --report=string either "all" or one of more letters of "vsewpdat" (see below) -t, --title=string sets title in the markdown metadata (default "SPF report") -v, --verbosity=number set logging noise level (0..5), default is 4 (informational) -w, --width=NUM limits line length to increase readability (defaults to 60) and --no-color turn off colors for log messages --no-markdown turn off markdown formatting for reports```The default is to simply print the verdict and some stats to stdout and printnotification messages to stderr. `spfcheck` passes the[`rfc7208 test suite`](http://www.open-spf.org/Test_Suite)and should be reasonably[`rfc7208`](https://www.rfc-editor.org/rfc/rfc7208.html) compliant.```txt% spfcheck example.com --no-colorexample.com %spf[0]-ctx-info: > sender is 'example.com'example.com %spf[0]-ctx-info: > local part set to 'postmaster'example.com %spf[0]-ctx-info: > domain part set to 'example.com'example.com %spf[0]-ctx-info: > ip is '127.0.0.1'example.com %spf[0]-ctx-info: > helo set to 'example.com'example.com %spf[0]-ctx-info: > DNS cache preloaded with 0 entrie(s)example.com %spf[0]-ctx-info: > verbosity level 4example.com %spf[0]-ctx-info: > created context for 'example.com'example.com %spf[0]-spf-note: > spfcheck(example.com, 127.0.0.1, example.com)example.com %spf[0]-dns-info: > DNS QUERY (1) txt example.com - ["8j5nfqld20zpcyr8xjw0ydcfq9rk8hgm", "v=spf1 -all"]example.com %spf[0]-eval-note: > spf[0] -all - matchesexample.com %spf[0]-dns-info: > DNS QUERY (2) soa example.com - [{"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}]domain : example.comip : 127.0.0.1sender : example.comverdict : failreason : spf[0] -allowner : example.comcontact : noc@dns.icann.orgnum_spf : 1num_dnsm : 0num_dnsq : 1num_dnsv : 0num_checks : 1num_warn : 0num_error : 0duration : 0explanation: ```## BatchmodeIf no `sender` is given on the command line, `spfcheck` will read stdin for thedomains (and options) to check. In this case, the verdict(s) are output onstdout in csv-format as each domain is (sequentially) evaluated.```txt% cat tmp/domains.txtexample.comme@example.net -i 1.2.3.4% cat tmp/domains.txt | spfcheck -v 0domain,ip,sender,verdict,reason,owner,contact,num_spf,num_dnsm,num_dnsq,num_dnsv,num_checks,num_warn,num_error,duration,explanation"example.com","127.0.0.1","example.com",:fail,"spf[0] -all","example.com","noc@dns.icann.org",1,0,1,0,1,0,0,1,"""example.net","1.2.3.4","me@example.net",:fail,"spf[0] -all","example.net","noc@dns.icann.org",1,0,1,0,1,0,0,0,""```## DNS flagThe `-d` flag can be used to either point to local file with RR-records orspecify DNS data on the command line. If the file exists, it is read and usedto prepopulate the cache. Otherwise, the text will be read as DNS data. Thismakes it possible to try out records before publishing them in DNS. That fileshould contain 1 RR record per line using the simple `domain type rdata`format. All `domain`'s are taken to be relative to root ('.').```txt% spfcheck example.com -v 0 -d "example.com TXT v=spf1 +all"domain : example.comip : 127.0.0.1sender : example.comverdict : passreason : spf[0] +allowner : example.comcontact : noc@dns.icann.orgnum_spf : 1num_dnsm : 0num_dnsq : 1num_dnsv : 0num_checks : 1num_warn : 1num_error : 0duration : 0explanation:# Or using a file% cat tmp/zonedata.txt# comments are ignored as are empty linesexample.com TXT v=spf1 -all exp=why.%{d}example.com TXT just another txt recordwhy.example.com TXT %{d}: %{i} is not one of our MTA's% spfcheck example.com -v 0 -d tmp/zonedata.txtdomain : example.comip : 127.0.0.1sender : example.comverdict : failreason : spf[0] -allowner : example.comcontact : noc@dns.icann.orgnum_spf : 1num_dnsm : 0num_dnsq : 2num_dnsv : 0num_checks : 1num_warn : 0num_error : 0duration : 1explanation: example.com: 127.0.0.1 is not one of our MTA's````spfcheck` counts the number of dns mechanisms seen (dnsm), the number ofqueries performed (dnsq) and the number of void dns queries seen (dnsv).If the evaluation took more than `10` dns mechanisms or saw more than `2`void DNS lookups, the verdict is modified accordingly. The soa queriesused to retrieve/find the owner and contact information are not includedin the dns counters.## Helo flagThe `-h` allows for setting the EHLO domain name and defaults to given`sender`. Note that `spfcheck` only checks SPF for `sender`, so this is onlyuseful when checking the expansion of the `%{h}`-macro in a policy.## Ip flagThe `-i` flag is used to set sender's IP to either an IPv4 or an IPv6 address,it defaults to `127.0.0.1` as an unlikely address to be authorized by anyone.The goal is to go down the rabbit hole as far as possible and check the entirenested SPF policy for given `sender`. Notes:- if given an IPv4-mapped IPv6 address, the IPv4 address is extracted and used- if given IP address is invalid, it defaults to 127.0.0.1- the given ip may also be a prefix rather than a full address```txt% spfcheck example.com --no-color -i "::ffff:1.2.3.4"example.com %spf[0]-ctx-info: > sender is 'example.com'example.com %spf[0]-ctx-info: > local part set to 'postmaster'example.com %spf[0]-ctx-info: > domain part set to 'example.com'example.com %spf[0]-ctx-info: > ip is '1.2.3.4'example.com %spf[0]-ctx-note: > '1.2.3.4' was extracted from IPv4-mapped IPv6 address '::ffff:1.2.3.4'example.com %spf[0]-ctx-info: > helo set to 'example.com'example.com %spf[0]-ctx-info: > DNS cache preloaded with 0 entrie(s)example.com %spf[0]-ctx-info: > verbosity level 4example.com %spf[0]-ctx-info: > created context for 'example.com'example.com %spf[0]-spf-note: > spfcheck(example.com, 1.2.3.4, example.com)example.com %spf[0]-dns-info: > DNS QUERY (1) txt example.com - ["8j5nfqld20zpcyr8xjw0ydcfq9rk8hgm", "v=spf1 -all"]example.com %spf[0]-eval-note: > spf[0] -all - matchesexample.com %spf[0]-dns-info: > DNS QUERY (2) soa example.com - [{"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}]domain : example.comip : 1.2.3.4sender : example.comverdict : failreason : spf[0] -allowner : example.comcontact : noc@dns.icann.orgnum_spf : 1num_dnsm : 0num_dnsq : 1num_dnsv : 0num_checks : 1num_warn : 0num_error : 0duration : 0explanation:# or check for some prefix% spfcheck example.com -i 1.1.255.0/24 -d "example.com txt v=spf1 ip4:1.1.0.0/16 -all" --no-colorexample.com %spf[0]-ctx-info: > sender is 'example.com'example.com %spf[0]-ctx-info: > local part set to 'postmaster'example.com %spf[0]-ctx-info: > domain part set to 'example.com'example.com %spf[0]-ctx-info: > ip is '1.1.255.0/24'example.com %spf[0]-ctx-info: > helo set to 'example.com'example.com %spf[0]-ctx-info: > DNS cache preloaded with 1 entrie(s)example.com %spf[0]-ctx-info: > verbosity level 4example.com %spf[0]-ctx-info: > created context for 'example.com'example.com %spf[0]-spf-note: > spfcheck(example.com, 1.1.255.0/24, example.com)example.com %spf[0]-dns-info: > DNS QUERY (1) [cache] txt example.com - ["v=spf1 ip4:1.1.0.0/16 -all"]example.com %spf[0]-eval-note: > spf[0] ip4:1.1.0.0/16 - matches 1.1.255.0/24example.com %spf[0]-dns-info: > DNS QUERY (2) soa example.com - [{"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}]domain : example.comip : 1.1.255.0/24sender : example.comverdict : passreason : spf[0] ip4:1.1.0.0/16owner : example.comcontact : noc@dns.icann.orgnum_spf : 1num_dnsm : 0num_dnsq : 1num_dnsv : 0num_checks : 1num_warn : 0num_error : 0duration : 0explanation: ```## Nameserver flagUse `-n ip` or `--nameserver ip` to specify an IPv4 or IPv6 address of a,possibly external, recursive nameserver to use for an SPF policy evaluationinstead of using the system default settings. Specify multiple nameserversby repeating the option with different IP addresses, in which case they will betried in the order listed.```% spfcheck example.com -n 2001:4860:4860::8888 -v 5 --no-colorexample.com %spf[0]-ctx-info: > sender is 'example.com'example.com %spf[0]-ctx-info: > local part set to 'postmaster'example.com %spf[0]-ctx-info: > domain part set to 'example.com'example.com %spf[0]-ctx-info: > ip set to '127.0.0.1'example.com %spf[0]-ctx-debug: > atype set to 'a'example.com %spf[0]-ctx-info: > helo set to 'example.com'example.com %spf[0]-ctx-debug: > helo defaults to sender valueexample.com %spf[0]-ctx-info: > DNS cache preloaded with 0 entrie(s)example.com %spf[0]-ctx-info: > verbosity level 5example.com %spf[0]-ctx-debug: > DNS timeout set to 2000example.com %spf[0]-ctx-debug: > max DNS mechanisms set to 10example.com %spf[0]-ctx-debug: > max void DNS lookups set to 2example.com %spf[0]-ctx-debug: > verdict defaults to 'neutral'example.com %spf[0]-ctx-debug: > nameservers set to [{{8193, 18528, 18528, 0, 0, 0, 0, 34952}, 53}]example.com %spf[0]-ctx-info: > created context for 'example.com'example.com %spf[0]-spf-note: > spfcheck(example.com, 127.0.0.1, example.com)example.com %spf[0]-dns-debug: > added {example.com, txt} -> "v=spf1 -all"example.com %spf[0]-dns-debug: > added {example.com, txt} -> "8j5nfqld20zpcyr8xjw0ydcfq9rk8hgm"example.com %spf[0]-dns-info: > DNS QUERY (1) txt example.com - ["8j5nfqld20zpcyr8xjw0ydcfq9rk8hgm", "v=spf1 -all"]example.com %spf[0]-eval-note: > spf[0] -all - matchesexample.com %spf[0]-dns-debug: > added {example.com, soa} -> {"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}example.com %spf[0]-dns-info: > DNS QUERY (2) soa example.com - [{"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}]domain : example.comip : 127.0.0.1sender : example.comverdict : failreason : spf[0] -allowner : example.comcontact : noc@dns.icann.orgnum_spf : 1num_dnsm : 0num_dnsq : 1num_dnsv : 0num_checks : 1num_warn : 0num_error : 0duration : 0explanation:```## No color flagThe `--no-color` flag disables the use of colors in log messages, which isbetter when redirecting logging to a file.For example:```txt%cat tmp/domains.txtexample.comme@example.net -i 1.2.3.4% cat tmp/domains.txt | spfcheck -v 5 --no-color 2>tmp/log.txt > tmp/checked.csv% cat tmp/log.txtexample.com %spf[0]-ctx-info: > sender is 'example.com'example.com %spf[0]-ctx-info: > local part set to 'postmaster'example.com %spf[0]-ctx-info: > domain part set to 'example.com'example.com %spf[0]-ctx-info: > ip set to '127.0.0.1'example.com %spf[0]-ctx-debug: > atype set to 'a'example.com %spf[0]-ctx-info: > helo set to 'example.com'example.com %spf[0]-ctx-debug: > helo defaults to sender valueexample.com %spf[0]-ctx-info: > DNS cache preloaded with 0 entrie(s)example.com %spf[0]-ctx-info: > verbosity level 5example.com %spf[0]-ctx-debug: > DNS timeout set to 2000example.com %spf[0]-ctx-debug: > max DNS mechanisms set to 10example.com %spf[0]-ctx-debug: > max void DNS lookups set to 2example.com %spf[0]-ctx-debug: > verdict defaults to 'neutral'example.com %spf[0]-ctx-debug: > nameservers set to defaultexample.com %spf[0]-ctx-info: > created context for 'example.com'example.com %spf[0]-spf-note: > spfcheck(example.com, 127.0.0.1, example.com)example.com %spf[0]-dns-debug: > added {example.com, txt} -> "v=spf1 -all"example.com %spf[0]-dns-debug: > added {example.com, txt} -> "8j5nfqld20zpcyr8xjw0ydcfq9rk8hgm"example.com %spf[0]-dns-info: > DNS QUERY (1) txt example.com - ["8j5nfqld20zpcyr8xjw0ydcfq9rk8hgm", "v=spf1 -all"]example.com %spf[0]-eval-note: > spf[0] -all - matchesexample.com %spf[0]-dns-debug: > added {example.com, soa} -> {"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}example.com %spf[0]-dns-info: > DNS QUERY (2) soa example.com - [{"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}]example.net %spf[0]-ctx-info: > sender is 'me@example.net'example.net %spf[0]-ctx-info: > local part set to 'me'example.net %spf[0]-ctx-info: > domain part set to 'example.net'example.net %spf[0]-ctx-info: > ip set to '1.2.3.4'example.net %spf[0]-ctx-debug: > atype set to 'a'example.net %spf[0]-ctx-info: > helo set to 'me@example.net'example.net %spf[0]-ctx-debug: > helo defaults to sender valueexample.net %spf[0]-ctx-info: > DNS cache preloaded with 0 entrie(s)example.net %spf[0]-ctx-info: > verbosity level 5example.net %spf[0]-ctx-debug: > DNS timeout set to 2000example.net %spf[0]-ctx-debug: > max DNS mechanisms set to 10example.net %spf[0]-ctx-debug: > max void DNS lookups set to 2example.net %spf[0]-ctx-debug: > verdict defaults to 'neutral'example.net %spf[0]-ctx-debug: > nameservers set to defaultexample.net %spf[0]-ctx-info: > created context for 'example.net'example.net %spf[0]-spf-note: > spfcheck(example.net, 1.2.3.4, me@example.net)example.net %spf[0]-dns-debug: > added {example.net, txt} -> "v=spf1 -all"example.net %spf[0]-dns-debug: > added {example.net, txt} -> "5fpl1ghm7scnth0907z0pft8c79lvc8t"example.net %spf[0]-dns-info: > DNS QUERY (1) txt example.net - ["5fpl1ghm7scnth0907z0pft8c79lvc8t", "v=spf1 -all"]example.net %spf[0]-eval-note: > spf[0] -all - matchesexample.net %spf[0]-dns-debug: > added {example.net, soa} -> {"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}example.net %spf[0]-dns-info: > DNS QUERY (2) soa example.net - [{"ns.icann.org", "noc.dns.icann.org", 2021111701, 7200, 3600, 1209600, 3600}]```## Report flagThe `-r` flag can be used to print out some information, topics include:- `v` the verdict and some statistics- `s` the spf records seen and their authority information- `e` the errors seen- `w` the warnings seen- `p` the prefixes collected- `d` DNS information collected- `a` the AST for the (first) SPF record- `t` the tokens for the (last) SPF record seenIn case no `-r` flag is used, spfcheck will simply print out the verdictwithout any markdown formatting. If only one topic is to be reported, thedefault is to omit markdown formatting as well unless requested explicitly withthe `-m` flag.```txt% spfcheck example.com example.net example.org -v 0 -r s[0] example.com -- (example.com, noc@dns.icann.org) v=spf1 -all[0] example.net -- (example.net, noc@dns.icann.org) v=spf1 -all[0] example.org -- (example.org, noc@dns.icann.org) v=spf1 -all```Alternatively a simple markdown report can be generated. Use the `-t` and`-a` flags to customize the title and author information respectively.The report below shows the SPF records used by several example domains. Ifthey had included other SPF records, those would show as well.```txt% spfcheck example.com example.net example.org -v 0 -r s -m \ -t "Spf records used by Example domains" -a mail@example.com --- title: Spf records used by Example domains author: mail@example.com date: 2021-11-28 12:44:30 ... # example.com ## SPF ``` [0] example.com -- (example.com, noc@dns.icann.org) v=spf1 -all ``` # example.net ## SPF ``` [0] example.net -- (example.net, noc@dns.icann.org) v=spf1 -all ``` # example.org ## SPF ``` [0] example.org -- (example.org, noc@dns.icann.org) v=spf1 -all ``````## Verbosity flagThe `-v` flag controls the verbosity level of logging on stderr:- 0 - no messages at all- 1 - errors- 2 - warnings- 3 - notifications- 4 - informational- 5 - debug```txt% spfcheck example.com -v 2 --no-color -d "example.com txt v=spf1 a -a/24 mx +all" example.com %spf[0]-parse-warn: > usage of spf[0] +all is not advisableexample.com %spf[0]-ipt-warn: > spf[0] -a/24 - overlaps with more specific spf[0] aexample.com %spf[0]-ipt-warn: > spf[0] -a/24 - inconsistent with more specific spf[0] aexample.com %spf[0]-eval-warn: > spf[0] mx - unusable due to null MX for example.comdomain : example.comip : 127.0.0.1sender : example.comverdict : passreason : spf[0] +allowner : example.comcontact : noc@dns.icann.orgnum_spf : 1num_dnsm : 3num_dnsq : 4num_dnsv : 0num_checks : 4num_warn : 4num_error : 0duration : 1explanation:```## Width flagFinally, the `-w` flag can be used to control the width used when printinginformation of certain topics. Primarily meant so a markdown formatted reportcan be easily converted to pdf. Default value is 60, but you can make it aswide as necessary.<!-- @MODULEDOC -->## InstallationYou can install `spfcheck` as an escript:```bashmix escript.install hex spfcheck```After installation, `~/.mix/escripts/spfcheck` invokes the escript.Or use it in a project by adding `spfcheck` to the list of dependencies in `mix.exs`:```elixirdef deps do [ {:spfcheck, "~> 0.5.0"} ]end```