From 28eb39cf2b231c34880a959fc7ccce1d0e339357 Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 31 Mar 2023 16:50:45 +0100 Subject: new role NoSpoof::DMARC --- lib/Sietima/Role/NoSpoof/DMARC.pm | 93 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 lib/Sietima/Role/NoSpoof/DMARC.pm (limited to 'lib/Sietima/Role/NoSpoof/DMARC.pm') diff --git a/lib/Sietima/Role/NoSpoof/DMARC.pm b/lib/Sietima/Role/NoSpoof/DMARC.pm new file mode 100644 index 0000000..de021da --- /dev/null +++ b/lib/Sietima/Role/NoSpoof/DMARC.pm @@ -0,0 +1,93 @@ +package Sietima::Role::NoSpoof::DMARC; +use Moo::Role; +use Sietima::Policy; +use Email::Address; +use Mail::DMARC::PurePerl; +use namespace::clean; + +# VERSION +# ABSTRACT: send out messages from subscribers' addresses only if DMARC allows it + +=head1 SYNOPSIS + + my $sietima = Sietima->with_traits('NoSpoof::DMARC')->new(\%args); + +=head1 DESCRIPTION + +A L<< C >> list with this role applied will replace the +C address with its own L<< +C|Sietima::Role::WithPostAddress >> (this is a +"sub-role" of L<< C|Sietima::Role::WithPostAddress +>>) I the originating address's DMARC policy requires it. + +This will make the list DMARC-compliant while minimising the changes +to the messages. + +The original C address will be preserved in the C +header, as required by RFC 5703. + +=head2 Some more details + +DMARC requires L<"identifier +alignment"|https://datatracker.ietf.org/doc/html/rfc7489#section-3.1>, +essentially the C (envelope) and the header C must +have the same domain (or at least belong to the same "organisational +domain", i.e. be both under a common non-top-level domain, roughly). + +Therefore, a mailing list that forwards a message sent from a +DMARC-enabled domain, I rewrite the C header, otherwise +the message will be discarded by recipient servers. If the originating +domain does not publish a DMARC policy (or publishes a C +policy), the mailing list can leave the C as is, but should add +a C header with the list's own address. + +This role does exactly that. + +=cut + +with 'Sietima::Role::WithPostAddress'; + +# mostly for testing +has dmarc_resolver => ( is => 'ro' ); + +around munge_mail => sub ($orig,$self,$incoming_mail) { + my $sender = $self->post_address->address; + my ($from) = Email::Address->parse($incoming_mail->header_str('From')); + my $from_domain = $from->host; + + my $dmarc = Mail::DMARC::PurePerl->new( + resolver => $self->dmarc_resolver, + ); + $dmarc->header_from($from_domain); + + if (my $policy = $dmarc->discover_policy) { + # sp applies to sub-domains, defaults to p; p applies to the + # domain itself, and is required + my $relevant_value = $dmarc->is_subdomain + ? ( $policy->sp // $policy->p ) + : $policy->p; + + if ($relevant_value ne 'none') { + $incoming_mail->header_str_set( + 'Original-From' => $from, + ); + + $from->address($sender); + + $incoming_mail->header_str_set( + From => $from, + ); + + return $self->$orig($incoming_mail); + } + } + + $incoming_mail->header_str_set( + Sender => $sender, + ); + + return $self->$orig($incoming_mail); + +}; + +1; -- cgit v1.2.3