dmarc: add error handling and tests

This commit is contained in:
Matt Simerson 2015-01-21 10:09:37 -08:00
parent 376498f2e8
commit 8185d33fa5
2 changed files with 32 additions and 53 deletions

View File

@ -72,6 +72,7 @@ https://github.com/smtpd/qpsmtpd/wiki/DMARC-FAQ
use strict;
use warnings;
use English qw/-no_match_vars/;
use Qpsmtpd::Constants;
sub register {
@ -90,16 +91,16 @@ sub register {
}
else {
$self->{_dmarc} = Mail::DMARC::PurePerl->new();
$self->register_hook('data_post_headers', 'data_post_handler');
$self->register_hook('data_post_headers', 'check_dmarc');
};
}
sub data_post_handler {
sub check_dmarc {
my ($self, $transaction) = @_;
if ( $self->qp->connection->relay_client() ) {
$self->log(LOGINFO, "skip, relay client" );
return DECLINED; # disable reporting to ourself
return DECLINED; # don't report to ourself
};
my $dmarc = $self->{_dmarc};
@ -117,19 +118,15 @@ sub data_post_handler {
my @recipients = $transaction->recipients;
eval { $dmarc->envelope_to( lc $recipients[0]->host ); }; # optional
eval { $dmarc->envelope_from( $transaction->sender->host ); }; # may be <>
$dmarc->spf( $transaction->notes('dmarc_spf') );
eval { $dmarc->spf( $transaction->notes('dmarc_spf') ); };
my $dkim = $self->connection->notes('dkim_verifier');
if ( $dkim ) {
eval { $dmarc->dkim( $dkim ); }
};
if ( $dkim ) { eval { $dmarc->dkim( $dkim ); }; };
$dmarc->source_ip( $self->qp->connection->remote_ip );
eval { $dmarc->validate(); };
if ( $@ ) {
if ( $EVAL_ERROR ) {
$self->log(LOGERROR, $@ );
return DECLINED if $self->is_immune;
$self->log(LOGINFO, "TODO: handle this validation failure");
return DECLINED;
return $self->get_reject( $@, $@ );
return $self->get_reject( $@ );
};
#$self->log(LOGINFO, "result: " . Dumper( $dmarc ) );

View File

@ -1,64 +1,46 @@
#!perl -w
use strict;
use English qw/-no_match_vars/;
use POSIX qw(strftime);
use Qpsmtpd::Address;
use Qpsmtpd::Constants;
my $remote_ip = '66.128.51.165';
my $test_email = 'matt@tnpi.net';
sub register_tests {
my $self = shift;
# TODO: test against newer DMARC plugin that uses Mail::DMARC
eval 'use Mail::DMARC';
if ($EVAL_ERROR) {
warn 'unable to load Mail::DMARC';
return;
}
sub setup_test_headers {
$self->register_test('_check_dmarc');
}
sub _check_dmarc {
my $self = shift;
my $transaction = $self->qp->transaction;
my $address = Qpsmtpd::Address->new( "<$test_email>" );
my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE");
my $now = strftime "%a %b %e %H:%M:%S %Y", localtime time;
$self->qp->connection->remote_ip($remote_ip);
my $t = $self->qp->transaction;
$t->header(Mail::Header->new(Modify => 0, MailFrom => "COERCE"));
$t->sender(Qpsmtpd::Address->new( "<$test_email>" ));
$t->header->add('Date', strftime "%a %b %e %H:%M:%S %Y", localtime time);
$t->body_write( "test message body " );
$transaction->sender($address);
$transaction->header($header);
$transaction->header->add('From', "<$test_email>");
$transaction->header->add('Date', $now );
$transaction->body_write( "test message body " );
# no From header, reject as invalid message
my ($rc, $msg) = $self->check_dmarc($t);
cmp_ok($rc, '==', DENY, "no From header, $msg");
$self->qp->connection->relay_client(0);
}
sub test_fetch_dmarc_record {
my $self = shift;
foreach ( qw/ tnpi.net nictool.com / ) {
my @matches = $self->fetch_dmarc_record($_);
cmp_ok( scalar @matches, '==', 1, 'fetch_dmarc_record');
}
foreach ( qw/ example.com / ) {
my @matches = $self->fetch_dmarc_record($_);
cmp_ok( scalar @matches, '==', 0, 'fetch_dmarc_record');
}
}
sub test_get_organizational_domain {
my $self = shift;
$self->setup_test_headers();
my $transaction = $self->qp->transaction;
cmp_ok( $self->get_organizational_domain('test.www.tnpi.net'), 'eq', 'tnpi.net' );
cmp_ok( $self->get_organizational_domain('www.example.co.uk'), 'eq', 'example.co.uk' );
cmp_ok( $self->get_organizational_domain('plus.google.com'), 'eq', 'google.com' );
}
sub test_discover_policy {
my $self = shift;
$self->setup_test_headers();
ok( $self->discover_policy( 'tnpi.net' ), 'discover_policy' );
$t->header->add('From', "<$test_email>");
($rc, $msg) = $self->check_dmarc($t);
cmp_ok($rc, '==', DENY, "$msg");
cmp_ok($msg, 'eq', 'failed DMARC policy', 'check_dmarc, no SPF');
#warn $self->qp->connection->notes('authentication_results');
}