man Mail::SPF::Query () - query Sender Policy Framework for an IP,email,helo

NAME

Mail::SPF::Query - query Sender Policy Framework for an IP,email,helo

SYNOPSIS

  my $query = new Mail::SPF::Query (ip => "127.0.0.1", sender=>'foo@example.com', helo=>"somehost.example.com", trusted=>1, guess=>1);
  my ($result,           # pass | fail | softfail | neutral | none | error | unknown [mechanism]
      $smtp_comment,     # "please see http://spf.pobox.com/why.html?..."  when rejecting, return this string to the SMTP client
      $header_comment,   # prepend_header("Received-SPF" => "$result ($header_comment)")
      $spf_record,       # "v=spf1 ..." original SPF record for the domain
     ) = $query->result();

    if    ($result eq "pass") { "domain is (probably) not forged.  apply RHSBL and content filters" }
    elsif ($result eq "fail") { "domain is forged.  reject or save to spambox" }

  The default mechanism for trusted=>1 is "include:spf.trusted-forwarder.org".
  The default mechanisms for guess=>1 are "a/24 mx/24 ptr".

ABSTRACT

The SPF protocol relies on sender domains to describe their designated outbound mailers in DNS. Given an email address, Mail::SPF::Query determines the legitimacy of an SMTP client IP.

DESCRIPTION

There are two ways to use Mail::SPF::Query. Your choice depends on whether the domains your server is an MX for have secondary MXes which your server doesn't know about.

The first and more common style, calling ->result(), is suitable when all mail is received directly from the originator's MTA. If the domains you receive do not have secondary MX entries, this is appropriate. This style of use is outlined in the SYNOPSIS above. This is the common case.

The second style is more complex, but works when your server receives mail from secondary MXes. This performs checks as each recipient is handled. If the message is coming from a valid MX secondary for a recipient, then the SPF check is not performed, and a pass response is returned right away. To do this, call CWresult2() and CWmessage_result2() instead of CWresult().

If you do not know what a secondary MX is, you probably don't have one. Use the first style.

You can try out Mail::SPF::Query on the command line with the following command:

  perl -MMail::SPF::Query -le 'print for Mail::SPF::Query->new(helo=>shift, ipv4=>shift, sender=>shift)->result' myhost.mydomain.com 1.2.3.4 myname@myhost.mydomain.com

METHODS

  my $query = eval { new Mail::SPF::Query (ip    =>"127.0.0.1",
                                           sender=>'foo@example.com',
                                           helo  =>"host.example.com") };
  optional parameters:
     debug => 1, debuglog => sub { print STDERR "@_\n" },
     local => 'extra mechanisms',
     trusted => 1,                    # do trusted forwarder processing
     guess => 1,                      # do best_guess if no SPF record
     default_explanation => 'Please see http://spf.my.isp/spferror.html for details',
     max_lookup_count => 20,          # total number of SPF include/redirect queries
     sanitize => 0,                   # do not sanitize all returned strings
     myhostname => "foo.example.com", # prepended to header_comment
     fallback => {   "foo.com" => { record => "v=spf1 a mx -all", OPTION => VALUE },
                   "*.foo.com" => { record => "v=spf1 a mx -all", OPTION => VAULE }, },
     override => {   "bar.com" => { record => "v=spf1 a mx -all", OPTION => VALUE },
                   "*.bar.com" => { record => "v=spf1 a mx -all", OPTION => VAULE }, },
     callerid => {   "hotmail.com" => { check => 1 },
                   "*.hotmail.com" => { check => 1 },
                   "*."            => { check => 0 }, },
 },
  if ($@) { warn "bad input to Mail::SPF::Query: $@" }

Set CWtrusted=>1 to turned on automatic trusted_forwarder processing. The mechanism CWinclude:spf.trusted-forwarder.org is used just before a CW-all or CW?all. The precise circumstances are somewhat more complicated, but it does get the case of CWv=spf1 -all right i.e. spf.trusted-forwarder.org is not checked. Set CWguess=>1 to turned on automatic best_guess processing. This will use the best_guess SPF record when one cannot be found in the DNS. Note that this can only return CWpass or CWneutral. The CWtrusted and CWlocal flags also operate when the best_guess is being used. Set CWlocal=>'include:local.domain' to include some extra processing just before a CW-all or CW?all. The local processing happens just before the trusted processing. Set CWdefault_explanation to a string to be used if the SPF record does not provide a specific explanation. The default value will direct the user to a page at spf.pobox.com with Please see http://spf.pobox.com/why.html?sender=%{S}&ip=%{I}&receiver=%{xR}. Note that the string has macro substitution performed. Set CWsanitize to 0 to get all the returned strings unsanitized. Alternatively, pass a function reference and this function will be used to sanitize the returned values. The function must take a single string argument and return a single string which contains the sanitized result. Set CWdebug=>1 to watch the queries happen. Set CWfallback to define pretend SPF records for domains that don't publish them yet. Wildcards are supported. Set CWoverride to define SPF records for domains that do publish but which you want to override anyway. Wildcards are supported. Set CWcallerid to look for Microsoft Caller-ID for Email records if an SPF record is not found. Wildcards are supported. You will need Expat, XML::Parser, and LMAP::CID2SPF installed for this to work; if you do not have these libraries installed, the lookup will not occur. By default, this library will only look for those records in hotmail.com and microsoft.com domains. If you want to always look for Caller-ID records, set

  ->new(..., callerid => { "*." => { check => 1 } })
If you never want to do Caller-ID,
  ->new(..., callerid => { "*." => { check => 0 } })
NOTE: domain name arguments to fallback, override, and callerid need to be in all lowercase.

$query->result()

  my ($result, $smtp_comment, $header_comment, $spf_record, $detail) = $query->result();

CW$result will be one of CWpass, CWfail, CWsoftfail, CWneutral, CWnone, CWerror or CWunknown [...]. CWpass means the client IP is a designated mailer for the sender. The mail should be accepted subject to local policy regarding the sender. CWfail means the client IP is not a designated mailer, and the sender wants you to reject the transaction for fear of forgery. CWsoftfail means the client IP is not a designated mailer, but the sender prefers that you accept the transaction because it isn't absolutely sure all its users are mailing through approved servers. The CWsoftfail status is often used during initial deployment of SPF records by a domain. CWneutral means that the sender makes no assertion about the status of the client IP. CWnone means that there is no SPF record for this domain. CWunknown [...] means the domain has a configuration error in the published data or defines a mechanism which this library does not understand. If the data contained an unrecognized mechanism, it will be presented following unknown. You should test for unknown using a regexp CW/^unknown/ rather than CWeq "unknown". CWerror means the DNS lookup encountered a temporary error during processing. Results are cached internally for a default of 120 seconds. You can call CW->result() repeatedly; subsequent lookups won't hit your DNS. The smtp_comment should be displayed to the SMTP client. The header_comment goes into a Received-SPF header, like so: CWReceived-SPF: $result ($header_comment) The spf_record shows the original SPF record fetched for the query. If there is no SPF record, it is blank. Otherwise, it will start with v=spf1 and contain the SPF mechanisms and such that describe the domain. Note that the strings returned by this method (and most of the other methods) are (at least partially) under the control of the sender's domain. This means that, if the sender is an attacker, the contents can be assumed to be hostile. The various methods that return these strings make sure that (by default) the strings returned contain only characters in the range 32 - 126. This behavior can be changed by setting CWsanitize to 0 to turn off sanitization entirely. You can also set CWsanitize to a function reference to perform custom sanitization. In particular, assume that the CWsmtp_comment might contain a newline character. The CWdetail element is a hash of all the foregoing elements, plus extra data returned by the SPF result. Why the weird duplication? In the beginning, CWresult() returned only one value, the CW$result. Then CW$smtp_comment and CW$header_comment came along. Then CW$spf_record. Past a certain number of positional results, it makes more sense to have a hash. But we didn't want to break backwards compatibility, so we just declared that the fifth result would be a hash and future return value would go in there. The keys of the hash are:

  result
  smtp_comment
  header_comment
  header_pairs
  spf_record
  modifiers
  vouches
$query->result();
  my ($result, $smtp_comment, $header_comment, $spf_record) = $query->result2('recipient@domain', 'recipient2@domain');
CWresult2 does everything that CWresult does, but it first checks to see if the sending system is a recognized MX secondary for the recipient(s). If so, then it returns CWpass and does not perform the SPF query. Note that the sending system may be a MX secondary for some (but not all) of the recipients for a multi-recipient message, which is why result2 takes an argument list. See also CWmessage_result2(). CW$result will be one of CWpass, CWfail, CWneutral [...], or CWunknown. See CWresult() above for meanings. If you have MX secondaries and if you are unable to explicitly whitelist those secondaries before SPF tests occur, you can use this method in place of CWresult(), calling it as many times as there are recipients, or just providing all the recipients at one time. The smtp_comment can be displayed to the SMTP client. For example:
  my $query = new Mail::SPF::Query (ip => "127.0.0.1",
                                    sender=>'foo@example.com',
                                    helo=>"somehost.example.com");
  ...
  my ($result, $smtp_comment, $header_comment);
  ($result, $smtp_comment, $header_comment) = $query->result2('recip1@mydomain.com');
  # return suitable error code based on $result eq 'fail' or not
  ($result, $smtp_comment, $header_comment) = $query->result2('recip2@mydom.org');
  # return suitable error code based on $result eq 'fail' or not
  ($result, $smtp_comment, $header_comment) = $query->message_result2();
  # return suitable error if $result eq 'fail'
  # prefix message with "Received-SPF: $result ($header_comment)"
This feature is relatively new to the module. You can get support on the mailing list spf-devel@listbox.com. The methods CWresult2() and CWmessage_result2() use 2 because they work for secondary MXes. CWresult2() takes care to minimize the number of DNS operations so that there is little performance penalty from using it in place of CWresult(). In particular, if no arguments are supplied, then it just calls CWresult() and returns the method response.
  my ($result, $smtp_comment, $header_comment, $spf_record) = $query->message_result2();
CWmessage_result2() returns an overall status for the message after zero or more calls to CWresult2(). It will always be the last status returned by CWresult2(), or the status returned by CWresult() if CWresult2() was never called. CW$result will be one of CWpass, CWfail, CWneutral [...], or CWerror. See CWresult() above for meanings.
      my ($result, $smtp_comment, $header_comment) = $query->best_guess();
When a domain does not publish SPF records, this library can produce an educated guess anyway. It pretends the domain defined A, MX, and PTR mechanisms, plus a few others. The default set of directives is
  "a/24 mx/24 ptr"
That default set will return either pass or neutral. If you want to experiment with a different default, you can pass it as an argument: CW$query->best_guess("a mx ptr") Note that this method is deprecated. You should set CWguess=>1 on the CWnew method instead.
      my ($result, $smtp_comment, $header_comment) = $query->best_guess();
It is possible that the message is coming through a known-good relay like acm.org or pobox.com. During the transitional period, many legitimate services may appear to forge a sender address: for example, a news website may have a send me this article in email link. The trusted-forwarder.org domain is a whitelist of known-good hosts that either forward mail or perform legitimate envelope sender forgery.
  "include:spf.trusted-forwarder.org"
This will return either pass or neutral. Note that this method is deprecated. You should set CWtrusted=>1 on the CWnew method instead. This applies the sanitization rules for the particular query object. These rules are controlled by the CWsanitize parameter to the Mail::SPF::Query new method. This ensures that all the characters in the returned string are printable. All whitespace is converted into spaces, and all other non-printable characters are converted into question marks. This is probably over aggressive for many applications. This function is used by default when the CWsanitize option is passed to the new method of Mail::SPF::Query. Note that this function is not a class method. Subclasses may override this with their own debug logger. I recommend Log::Dispatch. Alternatively, pass the CWnew() constructor a CWdebuglog = sub { ... }> callback, and we'll pass debugging lines to that.
EXPORT
None by default.

WARNINGS

Mail::Query::SPF should only be used at the point where messages are received from the Internet. The underlying assumption is that the sender of the email is sending the message directly to you or one of your secondaries. If your MTA does not have an exhaustive list of secondaries, then the CWresult2() and CWmessage_result2() methods should be used. These methods take care to permit mail from secondaries.

AUTHORS

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

Philip Gladstone

ACKNOWLEDGEMENTS

Joe Christy joe@eshu.net 2004-04-20 for the CW$DNS_RESOLVER_TIMEOUT patch.

SEE ALSO

http://spf.pobox.com/