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 an envelope-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 nor dkim_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 of fail, 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

Mail::DMARC

Mail::SPF::Iterator

Mail::DKIM::Iterator

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.