The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Mail::SPF::Query - query Sender Permitted From for an IP,email

SYNOPSIS

  my $query = new Mail::SPF::Query (ip => "127.0.0.1", sender=>'foo@example.com');
  my ($result, $comment) = $query->result();

  if    ($result eq "pass") { ... } # mail is not spam
  elsif ($result eq "deny") { ... } # mail may be spam
  else                      { ... } # sender domain has not implemented SPF

ABSTRACT

The SPF protocol relies on sender domains to publish a DNS whitelist of their designated outbound mailers. Given an envelope sender, Mail::SPF::Query determines the legitimacy of an SMTP client IP.

Mail::SPF::Query->new()

  my $query = eval { new Mail::SPF::Query (ip => "127.0.0.1", sender=>'foo@example.com') };

                    optional parameters:   fallbacks => ["spf.mailzone.com", ...],
                                           debug => 1,

  if ($@) { warn "bad input to Mail::SPF::Query: $@" }

$query->result()

  my ($result, $comment) = $query->result();

$result will be one of pass, fail, softfail, unknown, or error.

pass means the client IP is a designated mailer for the sender's domain.

error means the client IP is not.

unknown means the domain does not publish SPF data.

error means the DNS lookup encountered an error during processing.

$query->debuglog()

  Subclasses may override this with their own debug logger.  I recommend Log::Dispatch.

Algorithm

input: SEARCH_STACK = ([domain_name, is_fallback], ...)

returns: one of PASS | SOFTFAIL | FAIL | DEFER_PASS | DEFER_SOFTFAIL | DEFER_FAIL | UNKNOWN | ERROR , TEXT

data: LOOKUP_RESULT = PASS | FAIL | DEFER_PASS | DEFER_FAIL | UNKNOWN | ERROR , TEXT SPFQUERY_RESULT = PASS | FAIL | DEFER_PASS | DEFER_FAIL | UNKNOWN | ERROR , TEXT

pop a DOMAIN off the top of the stack and run

  LOOKUP_RESULT, LOOKUP_TEXT = LOOKUP(DOMAIN, SEARCH_STACK).

as a side effect, LOOKUP may push new domains onto the top of the SEARCH_STACK on the basis of SPFinclude replies.

They will be pushed with the attribute includehardenfail=1, because SOFTDENY makes everything more complicated. It should be relevant for the top-level search but not in any included domains.

If LOOKUP returns a PASS, a FAIL, or a SOFT_FAIL, short-circuit the query by returning LOOKUP_RESULT, LOOKUP_TEXT immediately. That result will propagate all the way back up the recursion stack.

If LOOKUP found any includes, it will return DEFER_FAIL or DEFER_SOFTFAIL instead of FAIL or SOFTFAIL. So try the includes also before returning the current value.

If the search stack is empty, return the LOOKUP_RESULT, LOOKUP_TEXT.

To exhaust the search stack, we will recurse:

  SPFQUERY_RESULT, SPFQUERY_TEXT = SPFQUERY(SEARCH_STACK)

return the severer of LOOKUP_RESULT vs SPFQUERY_RESULT, together with the appropriate TEXT. Severity is defined according to the following table:

     PASS        
     FAIL        
     SOFTFAIL    
     ERROR       
     DEFER_PASS
     DEFER_FAIL
     DEFER_SOFTFAIL
     UNKNOWN    

SEARCH ALGORITHM: lookup

global IP global DOMAINS_QUERIED

lookup(DOMAIN, SEARCH_STACK):

Pop a domain off the top of the stack.

Have we queried this domain already? If so, return nothing.

Perform a TXT query. If the result contains

  CNAME: push the CNAME's target onto the SEARCH_STACK and return nothing.
  TXT SPF=allow: return PASS.
  TXT SPFinclude=domain.com: push all matching domain.com onto the SEARCH_STACK in reverse order of their [:priority].
  TXT SPF=fail: return FAIL if there were no includes; if there were includes, return DEFER_FAIL.
  TXT SPF=softfail: return SOFTFAIL if there were no includes; if there were includes, return DEFER_SOFTFAIL.

If the query failed or returned unknown, if the domain IS NOT FALLBACK,

  push the fallback versions of the current domain onto the
  top of the search stack:

    SEARCH_STACK = SEARCH_STACK map { "domain_name.$_" } FALLBACK_LIST

Then return unknown.

EXPORT

None by default.

AUTHOR

Meng Weng Wong, <mengwong+spf@pobox.com>

SEE ALSO

http://spf.pobox.com/