NAME
Mail::DMARC::Iterator - Iterativ DMARC validation for mails.
SYNOPSIS
use Mail::DMARC::Iterator;
use Net::DNS::Resolver;
my $resolver = Net::DNS::Resolver->new;
my $dmarc = Mail::DMARC::Iterator->new(
# data from SMTP dialog - used for SPF
ip => '10.11.12.13',
mailfrom => 'foo@example.com',
helo => 'mx.example.com',
# alternatively add predefined results from your own SPF validation
# spf_result => [ 'pass',undef, { 'envelope-from' => ... } ]
# or set to undef so that it tries to use Received-SPF header fields
# spf_result => undef,
# you can optionally use a global DNS cache
# dnscache => \%global_cache
);
open( my $fh,'<','mail.eml'); # open the file
my ($result,@todo) = $dmarc->next; # initial result
while (!defined $result && @todo) {
my $todo = shift(@todo);
if (!ref($todo)) {
# no reference - indicator that we need more mail data
if (read($fh, my $buf,8192)) {
($result,@todo) = $dmarc->next($buf);
} else {
# EOF
($result,@todo) = $dmarc->next('');
}
} else {
# Net::DNS Packet needed for lookups of
# SPF, DMARC and DKIM records
my $answer = $resolver->send($todo);
($result,@todo) = $dmarc->next(
$answer ||
[ $todo, $resolver->errorstring ]
);
}
}
print STDERR "%s from-domain=%s; reason=%s\n",
$result || 'no-result',
$dmarc->domain || 'unknown-domain',
$todo[0] || 'unknown-reason';
DESCRIPTION
This module can be used to validate mails against DMARC policies like specified in RFC 7208. The main difference to Mail::DMARC is that it does no blocking operations. Instead it implements a state machine where user input is fed into and instructions what the machine wants is returned. The state machine only wants the data from the mail and the result of specific DNS lookups. With each new data fed into the machine it will provide new information what it needs next, until it finally has enough input and returns the final result. Because of this design the DMARC policy validation can be easily integrated into event-driven programs or coupled with a specific DNS resolver.
Mail::DMARC::Iterator uses the similarly designed modules Mail::DKIM::Iterator and Mail::SPF::Iterator to provide the necessary functionality of validating against DKIM and SPF records.
Mail::DMARC::Iterator currently only validates a mail against the policy. It does not provide any kind of feedback to the owner of the domain, i.e. feedback based on the ruf
and rua
attributes of the DMARC policy is not implemented. One can still access the necessary information using the record
method.
The following methods are implemented
- $class->new(%args) -> $dmarc
-
This creates a new object. The following arguments can be given:
- mailfrom, ip, helo, myname
-
These arguments are given to the Mail::SPF::Iterator object where they will be used to compute the SPF policy.
- spf_received => \@spf_result | undef
-
In this record the final result of the SPF policy calculation can be given as described in Mail::SPF::Iterator. If the argument is given and set to undef it will try to find
Received-SPF
records inside the mail extract the SPF result from them. These records must have anenvelope-from
parameter which will be used for identity aligning as described in the DMARC specification. - dkim_result => DKIM_RESULT
-
If given this is the result of an externally done DKIM computation. It is expected to be in the same format as the result returned by Mail::DKIM::Iterator.
- dkim_sub => function
-
If given this is a function which computes the current DKIM result whenever it is needed (i.e. within calls of
next
). This is used of DKIM processing is done in parallel to DMARC processing so that the result can change. The function is expected to return the DKIM_RESULT in the same format as the result returned by Mail::DKIM::Iterator. - dnscache => \%hash
-
Optional cache for DNS lookups which can be shared between multiple instances of Mail::DMARC::Iterator. Before reporting DNS lookups as needed to the user of the object it will first try to resolve the lookups using the cache.
If neither
dkim_sub
nordkim_result
are given it will create an internal Mail::DKIM::Iterator object and feed the data into it as long as it is needed, i.e. as long as now final DMARC result is known. - $self->domain -> $domain
-
Returns the domain of the From record if already known.
- $self->record -> \%hash
-
Returns the DMARC policy record as hash, if already known.
- $self->next( [ mailtext | dnspkt | [ dnspkt,error] ]* ) -> (undef,@todo) | ($result,$reason,$action)
-
This is the central method to compute the result. If the final result is known it will return the
$result
including the$reason
for this result and any$action
which must be taken based on the policy.$result
will be pass, fail, ... as described below. In case of fail$action
will return the policy action in case offail
, i.e. reject, quarantine or none.If the final result is not known yet it will return a list of todo's, where each of these is either the scalar
'D'
or a DNS query in form of a Net::DNS::Packet object. In case of the scalar the state machine expects more data from the mail and in case of the DNS query it expectes the answer to this query.The results of these todo's are given as arguments to
next
, i.e. either data from the mail as string or a Net::DNS::Packet as the answer. In case the DNS lookup failed it should add an array reference[ dnspkt, error ]
consisting of the original DNS query packet and a string description of the error. - $self->authentication_results => @lines
-
Generates lines which can be used in the Authentiation-Results header. With builtin DKIM and SPF handling this will include the results from these too.
The final results of the DMARC calculation are a dualvar which is both a string and a number. The following values are defined:
- DMARC_PASS -> 1,'pass'
-
At least one of the identifier aligned DKIM or SPF reported pass.
- DMARC_INVALID_FROM -> -1,'invalid-from'
-
The mail contains no usable From, i.e. none or multiple or with invalid syntax.
- DMARC_NONE -> -2,'none'
-
No DMARC policy record was found.
- DMARC_PERMERROR -> -3,'perm-error'
-
A DMARC policy record was found but it is invalid.
- DMARC_TEMPERROR -> -4,'temp-error'
-
No SPF or DKIM pass and at least one temporary error (DNS lookup...) happened.
- DMARC_FAIL -> 0,'fail'
-
Everything else. The policy of reject|quarantine|none has to be applied in this case.
SEE ALSO
AUTHOR
Steffen Ullrich <sullr[at]cpan[dot]org>
COPYRIGHT
Steffen Ullrich, 2015..2019
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.