Spf.Context (Spfcheck v0.10.0) View Source

Functions to create, access and update an SPF evaluation context.

Many functions take and return an evaluation context whose purpose is to store information gathered during the evaluation. This includes a dns cache, an ip lookup table that maps prefixes to SPF terms that named them, a stack for recursive evaluations, as well as some statistics around DNS mechanisms seen and void DNS responses seen.

Link to this section Summary

Types

A {qualifier, nth, term} tuple, where nth is the nth SPF record where term was found.

t()

An SPF evaluation context.

An SPF evaluation result.

Functions

Updates context.ipt with one or more {prefix/0, iptval/0}-pairs.

Updates context with given error, reason and verdict.

Returns a previous SPF string given either its domain of nth-tracking number.

Updates context's message queue and, if available, calls the user supplied log function.

Returns true if new_domain constitues a loop for given context, false otherwise.

Returns a new Spf.Context.t/0 for given sender.

Pop the previous state of given context from its stack.

Push the current state of given context onto its stack and re-init the context.

Reinitializes current context for given domain of a redirect modifier.

Given a current context and a range, return the SPF term in that range.

Split an email address into a local and a domain part.

If test is true, logs the given msg with its facility and severity.

Adds delta to counter and returns updated context.

Link to this section Types

Specs

iptval() :: {Spf.Lexer.q(), non_neg_integer(), binary()}

A {qualifier, nth, term} tuple, where nth is the nth SPF record where term was found.

The context's ip lookup table stores these tuples thus tracking which term in which SPF record provided a qualifier for a prefix. Since an evaluation may involve multiple SPF records, each prefix actually stores a list of these tuples.

Once the sender's ip has a longest prefix match, the qualifier will tell how the mechanism at hand matches.

Specs

prefix() :: Pfx.prefix()

A Pfx.prefix/0.

Specs

t() :: %{
  ast: list(),
  atype: :a | :aaaa,
  contact: binary(),
  depth: non_neg_integer(),
  dns: map(),
  dns_timeout: non_neg_integer(),
  domain: binary(),
  duration: non_neg_integer(),
  error: nil | atom(),
  explain: nil | tuple(),
  explain_string: binary(),
  explanation: binary(),
  helo: binary(),
  ip: binary(),
  ipt: Iptrie.t(),
  local: binary(),
  log: nil | function(),
  map: map(),
  max_dnsm: non_neg_integer(),
  max_dnsv: non_neg_integer(),
  msg: list(),
  nameservers: nil | list(),
  nth: non_neg_integer(),
  num_checks: non_neg_integer(),
  num_dnsm: non_neg_integer(),
  num_dnsq: non_neg_integer(),
  num_dnsv: non_neg_integer(),
  num_error: non_neg_integer(),
  num_spf: non_neg_integer(),
  num_warn: non_neg_integer(),
  owner: binary(),
  reason: binary(),
  sender: binary(),
  spf: binary(),
  spf_rest: binary(),
  spf_tokens: list(),
  stack: list(),
  t0: non_neg_integer(),
  traces: map(),
  verbosity: non_neg_integer(),
  verdict: verdict()
}

An SPF evaluation context.

Field notes:

  • ast is a list of SPF terms to be evaluated as produced by Spf.Parser
  • atype is set according to the sender's IP address
  • contact is gleaned from the soa record for domain under evaluation
  • depth is the nested depth during recursion, used to print a tree of log messages
  • dns is the DNS cache, used to report on DNS information gathered during evaluation
  • duration is the time (in milliseconds) it took to evaluate the SPF policy
  • error set by either Spf.Parser or Spf.Eval and halts evaluation if set
  • explain is the token for the exp=-modifier, if any (not needed for actual evaluation)
  • explain_string is the explanation after all expansions (when available and applicable)
  • helo as set by the :helo option given to Spf.check/2
  • ip is the sender IP, as set by the :ip option given to Spf.check/2 (default 127.0.0.1)
  • ipt is an Iptrie.t/0 used to record addresses and/or prefixes authorized to send mails
  • local is the local part of the sender
  • log is the user callback log function as provided by the :log option to Spf.check/2
  • map is used to record nth => domain and domain => spf-string
  • max_dnsm is the max of dns-mechanisms allowed (default 10), if it took more => permerror
  • max_dnsv is the max of void dns-responses allowed (default 2), if it took more => permerror
  • msg the list of logged messages by the Spf modules
  • nameservers a list of nameservers to use or nil (uses system default)
  • nth is the nth SPF record being evaluated
  • num_checks counts how many checks were performed during evaluation
  • num_dnsm counts the number of dns-mechanisms seen during evaluation
  • num_dnsq counts the number of dns queries performed during evaluation
  • num_dnsv counts the number of void DNS responses seen during evaluation
  • num_error counts the number of errors seen during evaluation
  • num_spf counts the number of SPF records evaluated
  • num_warn counts the number of warnings seen during evaluation
  • owner shows the SOA zone for the original SPF domain being evaluated
  • reason shows the reason for the verdict, usually in the form of an SPF term
  • sender is the sender as given to Spf.check/2
  • spf is the SPF string of the domain being evaluated (if any)
  • spf_rest is the remainder of the SPF string (should always by "")
  • spf_tokens is the Spf.Lexer's result of lexing the SPF string (last seen)
  • stack is used to push/pop the evaluation state during recursive calls
  • t0 is the Unix Epoch time the evaluation started
  • traces is a map used to detect loops in an SPF policy
  • verbosity controls the level of logged messages to stderr
  • verdict is the final result of the SPF evaluation by Spf.check/2

Other notes:

  • max_dnsm and max_dnsv are only checked after evaluating the entire policy
    • this allows to debug most of the SPF policy under consideration
  • Spf.Parser may set an syntax error, in which case the SPF record results in a permerror
    • the ast is produced by the parser by processing all spf_tokens
    • whitespace tokens are used to report on repeated whitespace in an SPF string
    • whitespace tokens donot end up in the AST
    • v=spf1-modifier is checked and if not present, results in an error
    • by processing all tokens, any error set reflects the last error seen
  • Spf.Eval may set an evaluation error, which may result in an overall permerror
  • a void DNS response is either a NXDOMAIN or ZERO ANSWERS

Specs

token() :: Spf.Lexer.token()

A Spf.Lexer.token/0.

Specs

verdict() ::
  :fail | :neutral | :none | :pass | :permerror | :softfail | :temperror

An SPF evaluation result.

Link to this section Functions

Link to this function

addip(context, ips, dual, value)

View Source

Specs

addip(t(), list(), list(), iptval()) :: t()
addip(t(), binary(), list(), iptval()) :: t()

Updates context.ipt with one or more {prefix/0, iptval/0}-pairs.

When given a list op ip's, they all will be be updated with given iptval/0 which records the SPF record and term (including the qualifier) that attributed the ip or ip's.

The dual parameter contains the dual-cidr lengths to apply to the given ip addresses.

Link to this function

error(context, facility, error, reason, verdict)

View Source

Specs

error(t(), atom(), atom(), binary(), atom()) :: t()

Updates context with given error, reason and verdict.

When verdict is nil, context.verdict is not updated. This allows for setting error conditions whose impact is to be evaluated at a later stage.

Specs

get_spf(t(), integer() | binary()) :: binary()

Returns a previous SPF string given either its domain of nth-tracking number.

Used for reporting rather than evalutation an SPF record.

Link to this function

log(context, facility, severity, msg)

View Source

Specs

log(t(), atom(), atom(), binary()) :: t()

Updates context's message queue and, if available, calls the user supplied log function.

The log/4 is called with:

  • context the current context/state of the evalution
  • facility an atom denoting which part of the program emitted the event
  • severity an atom describing the severity
  • msg a binary with event details
Link to this function

loop?(context, new_domain)

View Source

Specs

loop?(t(), binary()) :: boolean()

Returns true if new_domain constitues a loop for given context, false otherwise.

Loops may occur when two SPF records (eventually) include or redirect to each other and is considered a permanent error.

Specs

new(binary(), Keyword.t()) :: t()

Returns a new Spf.Context.t/0 for given sender.

Options include:

  • :dns, filepath or binary with zonedata (defaults to nil)
  • :helo, sender's helo string to use (defaults to sender)
  • :ip, sender ip to use (defaults to 127.0.0.1)
  • :log, user supplied log function (defaults to nil)
  • :verbosity, log level 0..5 to use (defaults to 4)
  • :nameserver, IPv4 or IPv6 address of a nameserver to use instead of the default

The initial domain is derived from given sender. The default for ip is likely to traverse all SPF mechanisms during evaluation, gathering as much information as possible. Set :ip to a real IPv4 or IPv6 address to check an SPF policy for that specific address.

The context is used for the entire SPF evaluation, including during any recursive calls. When evaluating an include mechanism, the current state (a few selected context properties) is pushed onto an internal stack and a new domain is set. After evaluating the include mechanism, the state if popped and the results are processed according to the include-mechanism's qualifier.

When evaluating a redirect modifier, the current state is altered for the new domain specified by the modifier.

Specify more than one recursive nameserver by repeating the :nameserver option in the Keyword list. They will be tried in the order listed. Mainly useful when the local default recursive nameserver is having problems, or when an external nameserver is to be used for checking an SPF policy instead of an internal nameserver. As an example, use in opts [nameserver: "2001:4860:4860::8888", nameserver: "2001:4860:4860::8844"] to use the IPv6 dns.google servers.

Specs

pop(t()) :: t()

Pop the previous state of given context from its stack.

Before evaluating an include mechanism, the current SPF's record state is pushed onto the stack. This function restores that state from the stack.

Specs

push(t(), binary()) :: t()

Push the current state of given context onto its stack and re-init the context.

The details of the current SPF record are pushed onto a stack and the context is re-initialized for retrieving, parsing and evaluate a new included record.

Link to this function

redirect(context, domain)

View Source

Specs

redirect(t(), binary()) :: t()

Reinitializes current context for given domain of a redirect modifier.

When a redirect modifier is encountered it basically replaces the current SPF record and the context is modified accordingly.

Specs

spf_term(t(), Range.t()) :: binary()

Given a current context and a range, return the SPF term in that range.

Retrieves a slice of the current SPF record being evaluated. Used for logging events.

Specs

split(binary()) :: {binary(), binary()}

Split an email address into a local and a domain part.

The local part is left to the left-most @, if there is no local part it defaults to "postmaster". Note that splitting an empty string yields {"postmaster", ""}.

Link to this function

test(context, facility, severity, test, msg)

View Source

Specs

test(t(), atom(), atom(), boolean(), binary()) :: t()

If test is true, logs the given msg with its facility and severity.

A convencience function to quickly check some test and, if true, log it as well in one go.

Link to this function

tick(context, counter, delta \\ 1)

View Source

Specs

tick(t(), atom(), integer()) :: t()

Adds delta to counter and returns updated context.

Valid counters include:

  • :num_spf, the number of SPF records seen
  • :num_dnsm the number of DNS mechanisms seen
  • :num_dnsq the number of DNS queries performed
  • :num_dnsv the number of void DNS queries seen
  • :num_checks the number of checks performed
  • :num_warn the number of warnings seen
  • :num_error the number of errors see (may not be fatal)
  • :depth the current recursion depth