You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by fk...@apache.org on 2026/03/24 20:24:04 UTC

svn commit: r1932516 - in spamassassin/trunk: lib/Mail/SpamAssassin/Plugin rules

Author: fkento
Date: Tue Mar 24 20:24:04 2026
New Revision: 1932516

Log:
Handle DMARC permerror/temperror without falling back to Mail::DMARC::PurePerl

When an Authentication-Results header contains dmarc=permerror or
dmarc=temperror, these are terminal results that should not trigger a
fallback to Mail::DMARC::PurePerl for a redundant DNS lookup.

Also fixes the Mail::DMARC::PurePerl path where "too many policies"
was incorrectly setting dmarc_policy to "no policy available", which
caused check_dmarc_missing to fire instead of the more accurate
permerror result.

Adds new check_dmarc_permerror and check_dmarc_temperror eval rules
with corresponding DMARC_PERMERROR and DMARC_TEMPERROR rules.

Modified:
   spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DMARC.pm
   spamassassin/trunk/rules/25_dmarc.cf

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DMARC.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DMARC.pm	Tue Mar 24 20:10:31 2026	(r1932515)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DMARC.pm	Tue Mar 24 20:24:04 2026	(r1932516)
@@ -50,6 +50,16 @@ Mail::SpamAssassin::Plugin::DMARC - chec
     describe DMARC_MISSING Missing DMARC policy
     tflags DMARC_MISSING net
     score DMARC_MISSING 0.001
+
+    header DMARC_PERMERROR eval:check_dmarc_permerror()
+    describe DMARC_PERMERROR DMARC permanent error
+    tflags DMARC_PERMERROR net
+    score DMARC_PERMERROR 0.001
+
+    header DMARC_TEMPERROR eval:check_dmarc_temperror()
+    describe DMARC_TEMPERROR DMARC temporary error
+    tflags DMARC_TEMPERROR net
+    score DMARC_TEMPERROR 0.001
   endif
 
 =head1 DESCRIPTION
@@ -97,6 +107,8 @@ sub new {
   $self->register_eval_rule("check_dmarc_quarantine", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
   $self->register_eval_rule("check_dmarc_none", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
   $self->register_eval_rule("check_dmarc_missing", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
+  $self->register_eval_rule("check_dmarc_permerror", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
+  $self->register_eval_rule("check_dmarc_temperror", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
 
   return $self;
 }
@@ -217,6 +229,28 @@ sub check_dmarc_missing {
   return $self->_check_eval($pms, $result);
 }
 
+sub check_dmarc_permerror {
+  my ($self, $pms, $name) = @_;
+
+  my $result = sub {
+    defined $pms->{dmarc_result} &&
+      $pms->{dmarc_result} eq 'permerror';
+  };
+
+  return $self->_check_eval($pms, $result);
+}
+
+sub check_dmarc_temperror {
+  my ($self, $pms, $name) = @_;
+
+  my $result = sub {
+    defined $pms->{dmarc_result} &&
+      $pms->{dmarc_result} eq 'temperror';
+  };
+
+  return $self->_check_eval($pms, $result);
+}
+
 sub check_tick {
   my ($self, $opts) = @_;
 
@@ -368,8 +402,9 @@ sub _check_dmarc {
   } elsif (defined $pms->{dmarc_result}) {
     if (defined $result->reason->[0]{comment} &&
           $result->reason->[0]{comment} eq 'too many policies') {
-      dbg("result: no policy available (too many policies)");
-      $pms->{dmarc_policy} = 'no policy available';
+      dbg("result: permerror (too many policies)");
+      $pms->{dmarc_result} = 'permerror';
+      $pms->{dmarc_policy} = 'permerror';
     } elsif ($result->result eq 'pass') {
       dbg("result: pass");
       my $policy = eval { $result->published->p };
@@ -410,6 +445,12 @@ sub _check_dmarc_authres {
   if ($result eq 'none') {
     # dmarc=none means no DMARC record published, no policy to look up
     $policy = 'no policy available';
+  } elsif ($result eq 'permerror' || $result eq 'temperror') {
+    # error results are terminal, no point in falling back to local check
+    $pms->{dmarc_result} = $result;
+    $pms->{dmarc_policy} = $result;
+    dbg("using Authentication-Results: result=%s", $result);
+    return 1;
   } else {
     # Try to get policy from A-R header properties
     foreach my $parsed (@{$pms->{authres_parsed}{dmarc} || []}) {

Modified: spamassassin/trunk/rules/25_dmarc.cf
==============================================================================
--- spamassassin/trunk/rules/25_dmarc.cf	Tue Mar 24 20:10:31 2026	(r1932515)
+++ spamassassin/trunk/rules/25_dmarc.cf	Tue Mar 24 20:24:04 2026	(r1932516)
@@ -58,5 +58,17 @@ priority DMARC_MISSING 500
 tflags DMARC_MISSING net
 reuse DMARC_MISSING
 
+header DMARC_PERMERROR eval:check_dmarc_permerror()
+describe DMARC_PERMERROR DMARC permanent error
+priority DMARC_PERMERROR 500
+tflags DMARC_PERMERROR net
+reuse DMARC_PERMERROR
+
+header DMARC_TEMPERROR eval:check_dmarc_temperror()
+describe DMARC_TEMPERROR DMARC temporary error
+priority DMARC_TEMPERROR 500
+tflags DMARC_TEMPERROR net
+reuse DMARC_TEMPERROR
+
 endif