summaryrefslogtreecommitdiff
path: root/network/opendmarc/patches/z03_reportDestVerificationV2.patch
diff options
context:
space:
mode:
Diffstat (limited to 'network/opendmarc/patches/z03_reportDestVerificationV2.patch')
-rw-r--r--network/opendmarc/patches/z03_reportDestVerificationV2.patch487
1 files changed, 487 insertions, 0 deletions
diff --git a/network/opendmarc/patches/z03_reportDestVerificationV2.patch b/network/opendmarc/patches/z03_reportDestVerificationV2.patch
new file mode 100644
index 0000000000..6e428b76ce
--- /dev/null
+++ b/network/opendmarc/patches/z03_reportDestVerificationV2.patch
@@ -0,0 +1,487 @@
+diff --git b/reports/opendmarc-reports.in a/reports/opendmarc-reports.in
+index 43be1ff..fff9f8d 100755
+--- b/reports/opendmarc-reports.in
++++ a/reports/opendmarc-reports.in
+@@ -24,6 +24,8 @@ use POSIX;
+ use MIME::Base64;
+ use Net::SMTP;
+ use Time::Local;
++use Net::DNS;
++use Domain::PublicSuffix;
+
+ require DBD::@SQL_BACKEND@;
+
+@@ -39,7 +41,6 @@ my $showversion = 0;
+ my $interval;
+
+ my $gen;
+-my $uri;
+
+ my $buf;
+
+@@ -95,8 +96,6 @@ my $dkimdomain;
+ my $reason;
+ my $comment;
+
+-my $repdest;
+-
+ my $smtpstatus;
+ my $smtpfail;
+
+@@ -140,6 +139,18 @@ my $smtp;
+
+ my $answer;
+
++my $suffix;
++my $publicsuffixlist = "/etc/opendmarc/public_suffix_list.dat";
++if (-r $publicsuffixlist) {
++ $suffix = Domain::PublicSuffix->new(
++ { 'data_file' => $publicsuffixlist }
++ );
++}
++else
++{
++ $suffix = Domain::PublicSuffix->new();
++}
++
+ ###
+ ### NO user-serviceable parts beyond this point
+ ###
+@@ -172,6 +183,71 @@ sub usage
+ print STDERR "\t--version print version and exit\n";
+ }
+
++sub check_size_restriction
++{
++ my ($destination, $size) = @_;
++ my $report_maxbytes = $report_maxbytes_global;
++
++ # check for max report size
++ if ($destination =~ m/^(\S+)!(\d{1,15})([kmgt])?$/i)
++ {
++ $destination = $1;
++ $report_maxbytes = $2;
++ if ($3)
++ {
++ my $letter = lc($3);
++ if ($letter eq 'k')
++ {
++ $report_maxbytes = $report_maxbytes * 1024;
++ }
++ if ($letter eq 'm')
++ {
++ $report_maxbytes = $report_maxbytes * 1048576;
++ }
++ if ($letter eq 'g')
++ {
++ $report_maxbytes = $report_maxbytes * (2**30);
++ }
++ if ($letter eq 't')
++ {
++ $report_maxbytes = $report_maxbytes * (2**40);
++ }
++ }
++
++ if ($size > $report_maxbytes)
++ {
++ return 0;
++ }
++ }
++ return 1;
++}
++
++sub check_uri
++{
++ my $uri = URI->new($_[0]);
++ if (!defined($uri) ||
++ !defined($uri->scheme) ||
++ $uri->opaque eq "")
++ {
++ print STDERR "$progname: can't parse reporting URI for domain $domain\n";
++ return "";
++ }
++ # ensure a scheme is present
++ elsif (!defined($uri->scheme))
++ {
++ if ($verbose >= 2)
++ {
++ print STDERR "$progname: unknown URI scheme in '$repuri' for domain $domain\n";
++ }
++ return "";
++ }
++ elsif ($uri->scheme eq "mailto")
++ {
++ return $uri->opaque;
++ }
++ return "";
++}
++
+ # set locale
+ setlocale(LC_ALL, 'C');
+
+@@ -798,86 +874,181 @@ foreach (@$domainset)
+ print STDERR "$progname: keeping report file \"$repfile\"\n";
+ }
+
++ if (!open($zipin, $zipfile))
++ {
++ print STDERR "$progname: can't read zipped report for $domain: $!\n";
++ next;
++ }
++ my $encoded_report;
++ while (read($zipin, $buf, 60*57))
++ {
++ $encoded_report .= encode_base64($buf);
++ }
++ close($zipin);
++ my $reportsize = length($encoded_report);
++
++ my $repdest = "";
++ my $repdest_fallback = "";
++
+ # decode the URI
+ @repuris = split(',', $repuri);
+
+ for $repuri (@repuris)
+ {
+- $uri = URI->new($repuri);
+- if (!defined($uri) ||
+- !defined($uri->scheme) ||
+- $uri->opaque eq "")
++ my $raw_address = check_uri($repuri);
++ if ($raw_address eq "")
+ {
+- print STDERR "$progname: can't parse reporting URI for domain $domain\n";
+ next;
+ }
+-
+- $repdest = $uri->opaque;
+- my $report_maxbytes = $report_maxbytes_global;
+-
+- # check for max report size
+- if ($repdest =~ m/^(\S+)!(\d{1,15})([kmgt])?$/i)
++ else
+ {
+- $repdest = $1;
+- $report_maxbytes = $2;
+- if ($3)
++ my $domain_orgdom = $suffix->get_root_domain(lc($domain));
++ my $address = $raw_address;
++ $address =~ s/!\d{1,15}([kmgt])?$//i;
++ my $repdestdomain = $address;
++ $repdestdomain =~ s/.*@//;
++ my $repdest_orgdom = $suffix->get_root_domain(lc($repdestdomain));
++
++ if (defined($domain_orgdom) && defined($repdest_orgdom) && $domain_orgdom eq $repdest_orgdom)
++ {
++ if (check_size_restriction($raw_address, $reportsize))
++ {
++ $repdest .= $address . ", ";
++ }
++ else
++ {
++ $repdest_fallback .= $address . ", ";
++ }
++ }
++ else
+ {
+- my $letter = lc($3);
+- if ($letter eq 'k')
++ # validate external report destinations:
++ my $replaced = 0; # external address replaced
++ my $authorized = 0; # external address authorized
++ my $temprepuri;
++ my $res = Net::DNS::Resolver->new(udp_timeout => 15);
++ my $reply = $res->query("$domain._report._dmarc.$repdestdomain", "TXT");
++ if ($reply)
+ {
+- $report_maxbytes = $report_maxbytes * 1024;
++ foreach my $txt ($reply->answer)
++ {
++ next unless $txt->type eq "TXT";
++ my @parts = split(';', $txt->txtdata);
++ my $type = shift @parts;
++ next unless $type =~ m/^\s*v\s*=\s*DMARC1\s*/;
++ $authorized = 1;
++ # just for debugging:
++ if ($txt->txtdata ne "v=DMARC1")
++ {
++ print STDERR "$progname: DEBUG: $domain._report._dmarc.$repdestdomain: query answer: ", $txt->txtdata, "\n";
++ }
++ foreach my $parts (@parts)
++ {
++ if ($parts =~ m/^\s*rua\s*=/)
++ {
++ $replaced = 1;
++ $parts =~ s/^\s*rua\s*=\s*//;
++ foreach my $tempuri (split(',', $parts))
++ {
++ $raw_address = check_uri($tempuri);
++ if ($raw_address eq "")
++ {
++ next;
++ }
++ my $uridomain = lc($raw_address);
++ $uridomain =~ s/.*@//;
++ $uridomain =~ s/!\d{15}([kmgt])?$//;
++ if ($repdestdomain eq $uridomain)
++ {
++ $address =~ s/!\d([kmgt])?$//i;
++ if ($verbose)
++ {
++ print STDERR "$progname: adding new reporting URI for domain $domain: $address\n";
++ }
++ if (check_size_restriction($raw_address, $reportsize))
++ {
++ $repdest .= $address . ", ";
++ }
++ else
++ {
++ $repdest_fallback .= $address . ", ";
++ }
++ }
++ else
++ {
++ if ($verbose)
++ {
++ print STDERR "$progname: ignoring new reporting URI due to differing host parts: $repdestdomain != $uridomain!\n";
++ }
++ }
++ }
++ # there should be only one part with "rua=", so stop here
++ last;
++ }
++ }
++ # there should be only one TXT record starting with "v=DMARC1", so stop here
++ last;
++ }
+ }
+- if ($letter eq 'm')
++ else
+ {
+- $report_maxbytes = $report_maxbytes * 1048576;
++ switch ($res->errorstring)
++ {
++ case "NXDOMAIN" { } # definitely not authorized
++ case "SERVFAIL" { $authorized = 1; } # not a definite answer, so be kind
++ case "query timed out" { $authorized = 1; } # not a definite answer, so be kind
++ else { $authorized = 1; } # for now we authorize anything else
++ }
+ }
+- if ($letter eq 'g')
++
++ if ($authorized && !$replaced)
+ {
+- $report_maxbytes = $report_maxbytes * (2**30);
++
++ $repdest .= $address . ", ";
+ }
+- if ($letter eq 't')
++ elsif (!$authorized)
+ {
+- $report_maxbytes = $report_maxbytes * (2**40);
++ if ($verbose)
++ {
++ print STDERR "$progname: $domain is NOT authorized to send reports to $address, dropping address! (" . $res->errorstring . ")\n";
++ }
++ next;
+ }
+ }
+ }
++ }
++ $repdest =~ s/, $//;
++ $repdest_fallback =~ s/, $//;
+
+- # Test mode, just report what would have been done
+- if ($testmode)
++ # Test mode, just report what would have been done
++ if ($testmode)
++ {
++ if ($repdest ne "")
+ {
+ print STDERR "$progname: would email $domain report for " .
+- "$rowcount records to " . $uri->opaque . "\n";
++ "$rowcount records to $repdest\n";
+ }
+- # ensure a scheme is present
+- elsif (!defined($uri->scheme))
++ elsif ($repdest_fallback ne "")
+ {
+- if ($verbose >= 2)
+- {
+- print STDERR "$progname: unknown URI scheme in '$repuri' for domain $domain\n";
+- }
+- next;
++ print STDERR "$progname: would email an error report for " .
++ "$domain to $repdest_fallback\n";
+ }
+- # send/post report
+- elsif ($uri->scheme eq "mailto")
++ }
++ else
++ {
++ if ($repdest ne "")
+ {
+- my $datestr;
+- my $report_id;
+-
+- if (!open($zipin, $zipfile))
+- {
+- print STDERR "$progname: can't read zipped report for $domain: $!\n";
+- next;
+- }
++ # send out the report:
++ $boundary = hostfqdn() . "/" . time();
+
+- $boundary = "report_section";
+-
+- $report_id = $domain . "-" . $now . "@" . $repdom;
+- $datestr = strftime("%a, %e %b %Y %H:%M:%S %z (%Z)",
+- localtime);
++ my $report_id = $domain . "-" . $now . "@" . $repdom;
++ my $datestr = strftime("%a, %e %b %Y %H:%M:%S %z (%Z)", localtime);
+
+ $mailout = "To: $repdest\n";
+ $mailout .= "From: $repemail\n";
+- $mailout .= "Subject: Report Domain: " . $domain . " Submitter: " . $repdom . " Report-ID: " . $report_id . "\n";
++ $mailout .= "Subject: Report Domain: " . $domain . "\n";
++ $mailout .= " Submitter: " . $repdom . "\n";
++ $mailout .= " Report-ID: " . $report_id . "\n";
+ $mailout .= "X-Mailer: " . $progname . " v" . $version ."\n";
+ $mailout .= "Date: " . $datestr . "\n";
+ $mailout .= "Message-ID: <$report_id>\n";
+@@ -898,52 +1069,100 @@ foreach (@$domainset)
+ $mailout .= "Content-Disposition: attachment; filename=\"$zipfile\"\n";
+ $mailout .= "Content-Transfer-Encoding: base64\n";
+ $mailout .= "\n";
++ $mailout .= $encoded_report;
++ $mailout .= "\n";
++ $mailout .= "--$boundary--\n";
++ $smtpstatus = "sent";
++ $smtpfail = 0;
++ if (!$smtp->mail($repemail) ||
++ !$smtp->to(split(', ', $repdest), {SkipBad => 1 }) ||
++ !$smtp->data() ||
++ !$smtp->datasend($mailout) ||
++ !$smtp->dataend())
++ {
++ $smtpfail = 1;
++ $smtpstatus = "failed to send";
++ }
+
+- while (read($zipin, $buf, 60*57))
++ if ($verbose || $smtpfail)
+ {
+- $mailout .= encode_base64($buf);
++ # now perl voodoo:
++ $answer = ${${*$smtp}{'net_cmd_resp'}}[1] || $smtp->message() || 'unknown error';
++ chomp($answer);
++ print STDERR "$progname: $smtpstatus report for $domain to $repdest ($answer)\n";
+ }
+
++ $smtp->reset();
++ }
++ elsif ($repdest_fallback ne "")
++ {
++ # send error report to $repdest_fallback:
++ if ($verbose)
++ {
++ print STDERR "$progname: emailing an error report for $domain to $repdest_fallback\n";
++ }
++ $boundary = hostfqdn() . "/" . time();
++
++ my $report_id = $domain . "-" . $now . "@" . $repdom;
++ my $datestr = strftime("%a, %e %b %Y %H:%M:%S %z (%Z)", localtime);
++
++ $mailout = "To: $repdest_fallback\n";
++ $mailout .= "From: $repemail\n";
++ $mailout .= "Subject: Error Report Domain: " . $domain . " Submitter: " . $repdom . " Report-ID: " . $report_id . "\n";
++ $mailout .= "X-Mailer: " . $progname . " v" . $version ."\n";
++ $mailout .= "Date: " . $datestr . "\n";
++ $mailout .= "Message-ID: <$report_id>\n";
++ $mailout .= "Auto-Submitted: auto-generated\n";
++ $mailout .= "MIME-Version: 1.0\n";
++ $mailout .= "Content-Type: multipart/report;\n";
++ $mailout .= " report-type=delivery-status;\n";
++ $mailout .= " boundary=\"$boundary\"\n";
++ $mailout .= "\n";
++ $mailout .= "This is a MIME-encapsulated message.\n";
++ $mailout .= "\n";
++ $mailout .= "--$boundary\n";
++ $mailout .= "Content-Description: DMARC Notification\n";
++ $mailout .= "Content-Type: text/plain\n";
++ $mailout .= "\n";
++ $mailout .= "This is a DMARC error report from host " . hostfqdn() . ".\n";
++ $mailout .= "\n";
++ $mailout .= "I'm sorry to have to inform you that a DMARC aggregate report\n";
++ $mailout .= "could not be delivered to any of your URIs mentioned in your DMARC\n";
++ $mailout .= "DNS resource records because of size limitations.\n";
++ $mailout .= "\n";
++ $mailout .= "--$boundary\n";
++ $mailout .= "Content-Description: DMARC Error Report\n";
++ $mailout .= "Content-Type: text/plain\n";
++ $mailout .= "\n";
++ $mailout .= "Report-Date: " . strftime("%a, %b %e %Y %H:%M:%S %z (%Z)", localtime()) . "\n";
++ $mailout .= "Report-Domain: $domain\n";
++ $mailout .= "Report-ID: $report_id\n";
++ $mailout .= "Report-Size: $reportsize\n";
++ $mailout .= "Submitter: $repdom\n";
++ $mailout .= "Submitting-URI: $repdest_fallback\n";
+ $mailout .= "\n";
+ $mailout .= "--$boundary--\n";
+- my $reportsize = length($mailout);
+-
+- if ($reportsize > $report_maxbytes)
++ $smtpstatus = "sent";
++ $smtpfail = 0;
++ if (!$smtp->mail($repemail) ||
++ !$smtp->to(split(', ', $repdest_fallback), { SkipBad => 1 }) ||
++ !$smtp->data() ||
++ !$smtp->datasend($mailout) ||
++ !$smtp->dataend())
+ {
+- # XXX -- generate an error report here
+- print STDERR "$progname: report was too large ($reportsize bytes) per limitation of URI " . $uri->opaque . " for domain $domain\n";
++ $smtpfail = 1;
++ $smtpstatus = "failed to send";
+ }
+- else
+- {
+- $smtpstatus = "sent";
+- $smtpfail = 0;
+- if (!$smtp->mail($repemail) ||
+- !$smtp->to($repdest) ||
+- !$smtp->data() ||
+- !$smtp->datasend($mailout) ||
+- !$smtp->dataend())
+- {
+- $smtpfail = 1;
+- $smtpstatus = "failed to send";
+- }
+
+- if ($verbose || $smtpfail)
+- {
+- # now perl voodoo:
+- $answer = ${${*$smtp}{'net_cmd_resp'}}[1] || $smtp->message() || 'unknown error';
+- chomp($answer);
+- print STDERR "$progname: $smtpstatus report for $domain to $repdest ($answer)\n";
+- }
++ if ($verbose || $smtpfail)
++ {
++ # now perl voodoo:
++ $answer = ${${*$smtp}{'net_cmd_resp'}}[1] || $smtp->message() || 'unknown error';
++ chomp($answer);
++ print STDERR "$progname: $smtpstatus failure notice for report for $domain to $repdest ($answer)\n";
+ }
+
+ $smtp->reset();
+-
+- close($zipin);
+- }
+- else
+- {
+- print STDERR "$progname: unsupported reporting URI scheme " . $uri->scheme . " for domain $domain\n";
+- next;
+ }
+ }
+