You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by gb...@apache.org on 2019/10/21 09:34:52 UTC

svn commit: r1868685 - in /spamassassin: branches/3.4/UPGRADE branches/3.4/lib/Mail/SpamAssassin/Conf.pm branches/3.4/lib/Mail/SpamAssassin/PerMsgStatus.pm trunk/UPGRADE trunk/lib/Mail/SpamAssassin/Conf.pm trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm

Author: gbechis
Date: Mon Oct 21 09:34:51 2019
New Revision: 1868685

URL: http://svn.apache.org/viewvc?rev=1868685&view=rev
Log:
Add a new subjprefix keyword.

This keyword will add a prefix in emails Subject if a rule is matched.
To enable this option "rewrite_header Subject" config option must be enabled
as well.

The check "if can(Mail::SpamAssassin::Conf::feature_subjprefix)"
should be used to silence warnings in previous SpamAssassin
versions.

This feature could not work out-of-the box if the glue
software that calls SpamAssassin (MimeDefang, Amavisd-new, ...)
uses the original email instead of the one produced by SA.
Some improvements to those softwares may be needed before enabling
this feature.

Modified:
    spamassassin/branches/3.4/UPGRADE
    spamassassin/branches/3.4/lib/Mail/SpamAssassin/Conf.pm
    spamassassin/branches/3.4/lib/Mail/SpamAssassin/PerMsgStatus.pm
    spamassassin/trunk/UPGRADE
    spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm

Modified: spamassassin/branches/3.4/UPGRADE
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.4/UPGRADE?rev=1868685&r1=1868684&r2=1868685&view=diff
==============================================================================
--- spamassassin/branches/3.4/UPGRADE (original)
+++ spamassassin/branches/3.4/UPGRADE Mon Oct 21 09:34:51 2019
@@ -1,6 +1,9 @@
 Note for Users Upgrading to SpamAssassin 3.4.3
 ----------------------------------------------
 
+- New subjprefix keyword added, this can be used to add a prefix to
+  email Subject if the original email matches a particular rule
+
 - New Util::is_fqdn_valid() function to validate hostname (DNS name) format
   (Bug 7736).  To check if a name contains valid TLD, it's still needed to
   additionally use RegistryBoundaries::is_domain_valid()

Modified: spamassassin/branches/3.4/lib/Mail/SpamAssassin/Conf.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.4/lib/Mail/SpamAssassin/Conf.pm?rev=1868685&r1=1868684&r2=1868685&view=diff
==============================================================================
--- spamassassin/branches/3.4/lib/Mail/SpamAssassin/Conf.pm (original)
+++ spamassassin/branches/3.4/lib/Mail/SpamAssassin/Conf.pm Mon Oct 21 09:34:51 2019
@@ -900,6 +900,25 @@ header.
     }
   });
 
+=item subjprefix
+
+Add a prefix in emails Subject if a rule is matched.
+To enable this option "rewrite_header Subject" config
+option must be enabled as well.
+
+The check C<if can(Mail::SpamAssassin::Conf::feature_subjprefix)>
+should be used to silence warnings in previous
+SpamAssassin versions.
+
+=cut
+
+  push (@cmds, {
+    command => 'subjprefix',
+    setting => 'subjprefix',
+    is_frequent => 1,
+    type => $CONF_TYPE_HASH_KEY_VALUE,
+  });
+
 =item add_header { spam | ham | all } header_name string
 
 Customized headers can be added to the specified type of messages (spam,
@@ -4303,6 +4322,8 @@ optional, and the default is shown below
  _LANGUAGES_       possible languages of mail
  _PREVIEW_         content preview
  _REPORT_          terse report of tests hit (for header reports)
+ _SUBJPREFIX_      subject prefix based on rules, to be prepended to Subject
+                   header by SpamAssassin caller
  _SUMMARY_         summary of tests hit for standard report (for body reports)
  _CONTACTADDRESS_  contents of the 'report_contact' setting
  _HEADER(NAME)_    includes the value of a message header.  value is the same
@@ -4440,6 +4461,7 @@ sub new {
   $self->{descriptions} = { };
   #tie %{$self->{descriptions}}, 'Mail::SpamAssassin::Util::TieOneStringHash'
   #  or warn "tie failed";
+  $self->{subjprefix} = { };
 
   # after parsing, tests are refiled into these hashes for each test type.
   # this allows e.g. a full-text test to be rewritten as a body test in
@@ -5019,6 +5041,7 @@ sub new_netset {
 sub finish {
   my ($self) = @_;
   untie %{$self->{descriptions}};
+  untie %{$self->{subjprefix}};
   %{$self} = ();
 }
 
@@ -5042,6 +5065,7 @@ sub feature_dns_query_restriction { 1 }
 sub feature_registryboundaries { 1 } # replaces deprecated registrarboundaries
 sub feature_compile_regexp { 1 } # Util::compile_regexp
 sub feature_meta_rules_matching { 1 } # meta rules_matching() expression
+sub feature_subjprefix { 1 } # add subject prefixes rule option
 sub has_tflags_nosubject { 1 } # tflags nosubject
 sub perl_min_version_5010000 { return $] >= 5.010000 }  # perl version check ("perl_version" not neatly backwards-compatible)
 

Modified: spamassassin/branches/3.4/lib/Mail/SpamAssassin/PerMsgStatus.pm
URL: http://svn.apache.org/viewvc/spamassassin/branches/3.4/lib/Mail/SpamAssassin/PerMsgStatus.pm?rev=1868685&r1=1868684&r2=1868685&view=diff
==============================================================================
--- spamassassin/branches/3.4/lib/Mail/SpamAssassin/PerMsgStatus.pm (original)
+++ spamassassin/branches/3.4/lib/Mail/SpamAssassin/PerMsgStatus.pm Mon Oct 21 09:34:51 2019
@@ -213,6 +213,11 @@ BEGIN {
       "\n" . ($pms->{tag_data}->{REPORT} || "");
     },
 
+    SUBJPREFIX => sub {
+      my $pms = shift;
+      ($pms->{tag_data}->{SUBJPREFIX} || "");
+    },
+
     HEADER => sub {
       my $pms = shift;
       my $hdr = shift;
@@ -269,6 +274,7 @@ sub new {
     'master_deadline'   => $msg->{master_deadline},  # dflt inherited from msg
     'deadline_exceeded' => 0,  # time limit exceeded, skipping further tests
     'uri_detail_list'   => { },
+    'subjprefix'        => "",
   };
 
   dbg("check: pms new, time limit in %.3f s",
@@ -293,7 +299,7 @@ sub new {
   # known valid tags that might not get their entry in pms->{tag_data}
   # in some circumstances
   my $tag_data_ref = $self->{tag_data};
-  foreach (qw(SUMMARY REPORT RBL)) { $tag_data_ref->{$_} = '' }
+  foreach (qw(SUMMARY REPORT SUBJPREFIX RBL)) { $tag_data_ref->{$_} = '' }
   foreach (qw(AWL AWLMEAN AWLCOUNT AWLPRESCORE
               DCCB DCCR DCCREP PYZOR DKIMIDENTITY DKIMDOMAIN
               BAYESTC BAYESTCLEARNED BAYESTCSPAMMY BAYESTCHAMMY
@@ -1042,6 +1048,8 @@ sub _get_added_headers {
 sub rewrite_report_safe {
   my ($self) = @_;
 
+  my $tag;
+
   # This is the original message.  We do not want to make any modifications so
   # we may recover it if necessary.  It will be put into the new message as a
   # message/rfc822 MIME part.
@@ -1079,8 +1087,15 @@ sub rewrite_report_safe {
   # possibilities right now, it's easier not to...
 
   if (defined $self->{conf}->{rewrite_header}->{Subject}) {
+    # Add a prefix to the subject if needed
+    if((defined $self->{subjprefix}) and ($self->{subjprefix} ne "")) {
+      $tag = $self->_replace_tags($self->{subjprefix});
+      $tag =~ s/\n/ /gs;
+      $subject = $tag . $subject;
+    }
+    # Add a **SPAM** prefix
     $subject = "\n" if !defined $subject;
-    my $tag = $self->_replace_tags($self->{conf}->{rewrite_header}->{Subject});
+    $tag = $self->_replace_tags($self->{conf}->{rewrite_header}->{Subject});
     $tag =~ s/\n/ /gs; # strip tag's newlines
     $subject =~ s/^(?:\Q${tag}\E )?/${tag} /g; # For some reason the tag may already be there!?
   }
@@ -1208,6 +1223,9 @@ EOM
 sub rewrite_no_report_safe {
   my ($self) = @_;
 
+  my $ntag;
+  my $pref_subject = 0;
+
   # put the pristine headers into an array
   # skip the X-Spam- headers, but allow the X-Spam-Prev headers to remain.
   # since there may be a missing header/body
@@ -1259,11 +1277,55 @@ sub rewrite_no_report_safe {
 	# The tag should be a comment for this header ...
 	$tag = "($tag)" if ($hdr =~ /^(?:From|To)$/);
 
-        local $1;
+        if((defined $self->{subjprefix}) and (defined $self->{conf}->{rewrite_header}->{Subject})) {
+	  if($self->{subjprefix} ne "") {
+            $ntag = $self->_replace_tags($self->{subjprefix});
+            $ntag =~ s/\n/ /gs;
+	    $ntag =~ s/\s+$//;
+
+            local $1;
+	    if(defined $ntag) {
+              s/^([^:]+:)[ \t]*(?:\Q${ntag}\E )?/$1 ${ntag} /i;
+	    }
+	  }
+        }
         s/^([^:]+:)[ \t]*(?:\Q${tag}\E )?/$1 ${tag} /i;
       }
 
       $addition = 'headers_spam';
+  } else {
+      # special-case: Subject lines.  ensure one exists, if we're
+      # supposed to mark it up.
+      my $created_subject = 0;
+      my $subject = $self->{msg}->get_pristine_header('Subject');
+      if (!defined($subject)
+            && exists $self->{conf}->{rewrite_header}->{'Subject'})
+      {
+        push(@pristine_headers, "Subject: \n");
+        $created_subject = 1;
+      }
+
+      # Deal with header rewriting
+      foreach (@pristine_headers) {
+        # if we're not going to do a rewrite, skip this header!
+        next if (!/^(Subject):/i);
+	my $hdr = ucfirst(lc($1));
+	next if (!defined $self->{conf}->{rewrite_header}->{$hdr});
+
+        if((defined $self->{subjprefix}) and (defined $self->{conf}->{rewrite_header}->{Subject})) {
+	  if($self->{subjprefix} ne "") {
+            $ntag = $self->_replace_tags($self->{subjprefix});
+            $ntag =~ s/\n/ /gs;
+	    $ntag =~ s/\s+$//;
+
+            local $1;
+	    if(defined $ntag) {
+              s/^([^:]+:)[ \t]*(?:\Q${ntag}\E )?/$1 ${ntag} /i;
+	    }
+	  }
+        }
+      }
+
   }
 
   # Break the pristine header set into two blocks; $new_hdrs_pre is the stuff
@@ -2666,6 +2728,9 @@ sub _handle_hit {
               $self->_wrap_desc($desc,
                   3+length($rule)+length($score)+length($area), " " x 28),
               ($self->{test_log_msgs}->{LONG} || ''));
+    if((defined $self->{subjprefix}) and ($self->{subjprefix} ne "")) {
+      $self->{tag_data}->{SUBJPREFIX} = $self->{subjprefix};
+    }
 }
 
 sub _wrap_desc {
@@ -2796,6 +2861,16 @@ sub got_hit {
   #$rule_descr = $rule  if !defined $rule_descr || $rule_descr eq '';
   $rule_descr = "No description available." if !defined $rule_descr || $rule_descr eq '';
 
+  if(defined $self->{conf}->{rewrite_header}->{Subject}) {
+    my $rule_subjprefix = $conf_ref->{subjprefix}->{$rule};
+    if (defined $rule_subjprefix) {
+      dbg("subjprefix: setting Subject prefix to $rule_subjprefix");
+      if($self->{subjprefix} !~ /\Q$rule_subjprefix\E/) {
+        $self->{subjprefix} .= $rule_subjprefix . " ";  # save dynamic subject prefix.
+      }
+    }
+  }
+
   $self->_handle_hit($rule,
             $score,
             $area,

Modified: spamassassin/trunk/UPGRADE
URL: http://svn.apache.org/viewvc/spamassassin/trunk/UPGRADE?rev=1868685&r1=1868684&r2=1868685&view=diff
==============================================================================
--- spamassassin/trunk/UPGRADE (original)
+++ spamassassin/trunk/UPGRADE Mon Oct 21 09:34:51 2019
@@ -2,6 +2,9 @@
 Note for Users Upgrading to SpamAssassin 4.0.0
 ----------------------------------------------
 
+- New subjprefix keyword added, this can be used to add a prefix to
+  email Subject if the original email matches a particular rule
+
 - All log output (stderr, file, syslog) is now escaped properly,
   \r \n \t \\, and control chars, DEL, UTF-8 sequences as \x{XX}.
   Whitespace is not normalized anymore like in versions <4.0.

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm?rev=1868685&r1=1868684&r2=1868685&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm Mon Oct 21 09:34:51 2019
@@ -899,6 +899,25 @@ header.
     }
   });
 
+=item subjprefix
+
+Add a prefix in emails Subject if a rule is matched.
+To enable this option "rewrite_header Subject" config
+option must be enabled as well.
+
+The check C<if can(Mail::SpamAssassin::Conf::feature_subjprefix)>
+should be used to silence warnings in previous
+SpamAssassin versions.
+
+=cut
+
+  push (@cmds, {
+    command => 'subjprefix',
+    setting => 'subjprefix',
+    is_frequent => 1,
+    type => $CONF_TYPE_HASH_KEY_VALUE,
+  });
+
 =item add_header { spam | ham | all } header_name string
 
 Customized headers can be added to the specified type of messages (spam,
@@ -4552,6 +4571,8 @@ optional, and the default is shown below
  _LANGUAGES_       possible languages of mail
  _PREVIEW_         content preview
  _REPORT_          terse report of tests hit (for header reports)
+ _SUBJPREFIX_      subject prefix based on rules, to be prepended to Subject
+                   header by SpamAssassin caller
  _SUMMARY_         summary of tests hit for standard report (for body reports)
  _CONTACTADDRESS_  contents of the 'report_contact' setting
  _HEADER(NAME)_    includes the value of a message header.  value is the same
@@ -4689,6 +4710,7 @@ sub new {
   $self->{descriptions} = { };
   #tie %{$self->{descriptions}}, 'Mail::SpamAssassin::Util::TieOneStringHash'
   #  or warn "tie failed";
+  $self->{subjprefix} = { };
 
   # after parsing, tests are refiled into these hashes for each test type.
   # this allows e.g. a full-text test to be rewritten as a body test in
@@ -5264,6 +5286,7 @@ sub new_netset {
 sub finish {
   my ($self) = @_;
   untie %{$self->{descriptions}};
+  untie %{$self->{subjprefix}};
   %{$self} = ();
 }
 
@@ -5289,6 +5312,7 @@ sub feature_geodb { 1 } # if needed for
 sub feature_dns_block_rule { 1 } # supports 'dns_block_rule' config option
 sub feature_compile_regexp { 1 } # Util::compile_regexp
 sub feature_meta_rules_matching { 1 } # meta rules_matching() expression
+sub feature_subjprefix { 1 } # add subject prefixes rule option
 sub feature_get_host { 1 } # $pms->get() :host :domain :ip :revip # was implemented together with AskDNS::has_tag_header # Bug 7734
 sub has_tflags_nosubject { 1 } # tflags nosubject
 sub perl_min_version_5010000 { return $] >= 5.010000 }  # perl version check ("perl_version" not neatly backwards-compatible)

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm?rev=1868685&r1=1868684&r2=1868685&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm Mon Oct 21 09:34:51 2019
@@ -216,6 +216,11 @@ BEGIN {
       "\n" . ($pms->{tag_data}->{REPORT} || "");
     },
 
+    SUBJPREFIX => sub {
+      my $pms = shift;
+      ($pms->{tag_data}->{SUBJPREFIX} || "");
+    },
+
     HEADER => sub {
       my $pms = shift;
       my $hdr = shift;
@@ -275,6 +280,7 @@ sub new {
     'deadline_exceeded' => 0,  # time limit exceeded, skipping further tests
     'tmpfiles'          => { },
     'uri_detail_list'   => { },
+    'subjprefix'        => "",
   };
 
   dbg("check: pms new, time limit in %.3f s",
@@ -299,7 +305,7 @@ sub new {
   # known valid tags that might not get their entry in pms->{tag_data}
   # in some circumstances
   my $tag_data_ref = $self->{tag_data};
-  foreach (qw(SUMMARY REPORT RBL)) { $tag_data_ref->{$_} = '' }
+  foreach (qw(SUMMARY REPORT SUBJPREFIX RBL)) { $tag_data_ref->{$_} = '' }
   foreach (qw(AWL AWLMEAN AWLCOUNT AWLPRESCORE
               DCCB DCCR DCCREP PYZOR DKIMIDENTITY DKIMDOMAIN
               BAYESTC BAYESTCLEARNED BAYESTCSPAMMY BAYESTCHAMMY
@@ -1057,6 +1063,8 @@ sub _get_added_headers {
 sub rewrite_report_safe {
   my ($self) = @_;
 
+  my $tag;
+
   # This is the original message.  We do not want to make any modifications so
   # we may recover it if necessary.  It will be put into the new message as a
   # message/rfc822 MIME part.
@@ -1096,8 +1104,15 @@ sub rewrite_report_safe {
   # possibilities right now, it's easier not to...
 
   if (defined $self->{conf}->{rewrite_header}->{Subject}) {
+    # Add a prefix to the subject if needed
+    if((defined $self->{subjprefix}) and ($self->{subjprefix} ne "")) {
+      $tag = $self->_replace_tags($self->{subjprefix});
+      $tag =~ s/\n/ /gs;
+      $subject = $tag . $subject;
+    }
+    # Add a **SPAM** prefix
     $subject = "\n" if !defined $subject;
-    my $tag = $self->_replace_tags($self->{conf}->{rewrite_header}->{Subject});
+    $tag = $self->_replace_tags($self->{conf}->{rewrite_header}->{Subject});
     $tag =~ s/\n/ /gs; # strip tag's newlines
     $subject =~ s/^(?:\Q${tag}\E )?/${tag} /g; # For some reason the tag may already be there!?
   }
@@ -1225,6 +1240,9 @@ EOM
 sub rewrite_no_report_safe {
   my ($self) = @_;
 
+  my $ntag;
+  my $pref_subject = 0;
+
   # put the pristine headers into an array
   # skip the X-Spam- headers, but allow the X-Spam-Prev headers to remain.
   # since there may be a missing header/body
@@ -1276,11 +1294,55 @@ sub rewrite_no_report_safe {
 	# The tag should be a comment for this header ...
 	$tag = "($tag)" if ($hdr =~ /^(?:From|To)$/);
 
-        local $1;
+        if((defined $self->{subjprefix}) and (defined $self->{conf}->{rewrite_header}->{Subject})) {
+	  if($self->{subjprefix} ne "") {
+            $ntag = $self->_replace_tags($self->{subjprefix});
+            $ntag =~ s/\n/ /gs;
+	    $ntag =~ s/\s+$//;
+
+            local $1;
+	    if(defined $ntag) {
+              s/^([^:]+:)[ \t]*(?:\Q${ntag}\E )?/$1 ${ntag} /i;
+	    }
+	  }
+        }
         s/^([^:]+:)[ \t]*(?:\Q${tag}\E )?/$1 ${tag} /i;
       }
 
       $addition = 'headers_spam';
+  } else {
+      # special-case: Subject lines.  ensure one exists, if we're
+      # supposed to mark it up.
+      my $created_subject = 0;
+      my $subject = $self->{msg}->get_pristine_header('Subject');
+      if (!defined($subject)
+            && exists $self->{conf}->{rewrite_header}->{'Subject'})
+      {
+        push(@pristine_headers, "Subject: \n");
+        $created_subject = 1;
+      }
+
+      # Deal with header rewriting
+      foreach (@pristine_headers) {
+        # if we're not going to do a rewrite, skip this header!
+        next if (!/^(Subject):/i);
+	my $hdr = ucfirst(lc($1));
+	next if (!defined $self->{conf}->{rewrite_header}->{$hdr});
+
+        if((defined $self->{subjprefix}) and (defined $self->{conf}->{rewrite_header}->{Subject})) {
+	  if($self->{subjprefix} ne "") {
+            $ntag = $self->_replace_tags($self->{subjprefix});
+            $ntag =~ s/\n/ /gs;
+	    $ntag =~ s/\s+$//;
+
+            local $1;
+	    if(defined $ntag) {
+              s/^([^:]+:)[ \t]*(?:\Q${ntag}\E )?/$1 ${ntag} /i;
+	    }
+	  }
+        }
+      }
+
   }
 
   # Break the pristine header set into two blocks; $new_hdrs_pre is the stuff
@@ -2800,6 +2862,9 @@ sub _handle_hit {
               $self->_wrap_desc($desc,
                   3+length($rule)+length($score)+length($area), " " x 28),
               ($self->{test_log_msgs}->{LONG} || ''));
+    if((defined $self->{subjprefix}) and ($self->{subjprefix} ne "")) {
+      $self->{tag_data}->{SUBJPREFIX} = $self->{subjprefix};
+    }
 }
 
 sub _wrap_desc {
@@ -2930,6 +2995,16 @@ sub got_hit {
   #$rule_descr = $rule  if !defined $rule_descr || $rule_descr eq '';
   $rule_descr = "No description available." if !defined $rule_descr || $rule_descr eq '';
 
+  if(defined $self->{conf}->{rewrite_header}->{Subject}) {
+    my $rule_subjprefix = $conf_ref->{subjprefix}->{$rule};
+    if (defined $rule_subjprefix) {
+      dbg("subjprefix: setting Subject prefix to $rule_subjprefix");
+      if($self->{subjprefix} !~ /\Q$rule_subjprefix\E/) {
+        $self->{subjprefix} .= $rule_subjprefix . " ";  # save dynamic subject prefix.
+      }
+    }
+  }
+
   $self->_handle_hit($rule,
             $score,
             $area,