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 2025/06/10 07:35:09 UTC

svn commit: r1926307 - in /spamassassin/trunk: lib/Mail/SpamAssassin/Plugin/HashBL.pm lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm t/data/spam/hashbl t/hashbl.t

Author: gbechis
Date: Tue Jun 10 07:35:09 2025
New Revision: 1926307

URL: http://svn.apache.org/viewvc?rev=1926307&view=rev
Log:
use replacetags from HashBL plugin, by enabling the new "replace" option in "check_hashbl_bodyre",
tags in the regexp will be replaced with the original char

Modified:
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm
    spamassassin/trunk/t/data/spam/hashbl
    spamassassin/trunk/t/hashbl.t

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm?rev=1926307&r1=1926306&r2=1926307&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HashBL.pm Tue Jun 10 07:35:09 2025
@@ -162,6 +162,10 @@ Additional supported OPTS:
 
   num      remove the chars from the match that are not numbers
 
+  replace  if a regexp contains replace tags, replace the match of the regexp with the first option
+           of the regexp, ex. +1 8O8.l23.4567 will be changed to +1 808.123.4567
+           For the subsitution to work, replace tags must contain only single chars.
+
 Optional subtest regexp to match DNS answer (default: '^127\.').
 
 =back
@@ -239,7 +243,7 @@ use strict;
 use warnings;
 use re 'taint';
 
-my $VERSION = 0.101;
+my $VERSION = 0.102;
 
 use Digest::MD5 qw(md5_hex);
 use Digest::SHA qw(sha1_hex sha256);
@@ -740,6 +744,23 @@ sub check_hashbl_bodyre {
     warn "HashBL: $rulename missing body regex\n";
     return 0;
   }
+
+  my $conf = $pms->{conf};
+  dbg("using regexp $re");
+  my $orig_re = $re;
+  my $replaced_regexp = 0;
+
+  # replace regexp matches only if requested
+  if(exists $conf->{plugins_loaded}{'Mail::SpamAssassin::Plugin::ReplaceTags'}) {
+    if(exists($conf->{replace_rules}->{$rulename})) {
+      $re = Mail::SpamAssassin::Plugin::ReplaceTags->replace_regexp($re, $conf);
+      if(defined $re and ($orig_re ne $re)) {
+        dbg("regexp $orig_re replaced with $re");
+        $replaced_regexp = 1;
+      }
+    }
+  }
+
   my ($rec, $err) = compile_regexp($re, 0);
   if (!$rec) {
     warn "HashBL: $rulename invalid body regex: $@\n";
@@ -769,6 +790,12 @@ sub check_hashbl_bodyre {
       while ($body =~ /$re/gs) {
         next if !defined $1;
         my $match = $opts->{case} ? $1 : lc($1);
+        # Check if ReplaceTags plugin is enabled
+        if(exists $conf->{plugins_loaded}{'Mail::SpamAssassin::Plugin::ReplaceTags'}) {
+          if($replaced_regexp and $opts->{replace}) {
+            $match = Mail::SpamAssassin::Plugin::ReplaceTags->replace_result($orig_re, $match, $conf);
+          }
+        }
         if($opts->{num}) {
           $match =~ tr/0-9//cd;
         }
@@ -781,6 +808,12 @@ sub check_hashbl_bodyre {
     while ($$bodyref =~ /$re/gs) {
       next if !defined $1;
       my $match = $opts->{case} ? $1 : lc($1);
+      # Check if ReplaceTags plugin is enabled
+      if(exists $conf->{plugins_loaded}{'Mail::SpamAssassin::Plugin::ReplaceTags'}) {
+        if($replaced_regexp and $opts->{replace}) {
+          $match = Mail::SpamAssassin::Plugin::ReplaceTags->replace_result($orig_re, $match, $conf);
+        }
+      }
       if($opts->{num}) {
         $match =~ tr/0-9//cd;
       }
@@ -1112,6 +1145,7 @@ sub _finish_query {
 # Version features
 sub has_hashbl_bodyre { 1 }
 sub has_hashbl_bodyre_num { 1 }
+sub has_hashbl_bodyre_replace { 1 }
 sub has_hashbl_emails { 1 }
 sub has_hashbl_uris { 1 }
 sub has_hashbl_ignore { 1 }

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm?rev=1926307&r1=1926306&r2=1926307&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ReplaceTags.pm Tue Jun 10 07:35:09 2025
@@ -83,15 +83,132 @@ sub finish_parsing_start {
   return 1;
 }
 
+sub replace_regexp {
+  my ($self, $re, $conf) = @_;
+
+  my $passes = 0;
+  my $doagain;
+  my $start = $conf->{replace_start};
+  my $end = $conf->{replace_end};
+
+  do {
+    my $pre_name;
+    my $post_name;
+    my $inter_name;
+    $doagain = 0;
+
+    # get modifier tags
+    if ($re =~ s/${start}pre (.+?)${end}//) {
+      $pre_name = $1;
+    }
+    if ($re =~ s/${start}post (.+?)${end}//) {
+      $post_name = $1;
+    }
+    if ($re =~ s/${start}inter (.+?)${end}//) {
+      $inter_name = $1;
+    }
+
+    # this will produce an array of tags to be replaced
+    # for two adjacent tags, an element of "" will be between the two
+    my @re = split(/(<[^<>]+>)/, $re);
+
+    if ($pre_name) {
+      my $pre = $conf->{replace_pre}->{$pre_name};
+      if ($pre) {
+        s{($start.+?$end)}{$pre$1}  for @re;
+      }
+    }
+    if ($post_name) {
+      my $post = $conf->{replace_post}->{$post_name};
+      if ($post) {
+        s{($start.+?$end)}{$1$post}g  for @re;
+      }
+    }
+    if ($inter_name) {
+      my $inter = $conf->{replace_inter}->{$inter_name};
+      if ($inter) {
+        s{^$}{$inter}  for @re;
+      }
+    }
+    for (my $i = 0; $i < @re; $i++) {
+      if ($re[$i] =~ m|$start(.+?)$end|g) {
+        my $tag_name = $1;
+        # if the tag exists, replace it with the corresponding phrase
+        if ($tag_name) {
+          my $replacement = $conf->{replace_tag}->{$tag_name};
+          if ($replacement) {
+            $re[$i] =~ s|$start$tag_name$end|$replacement|g;
+            $doagain = 1 if !$doagain && $replacement =~ /<[^>]+>/;
+          }
+        }
+      }
+    }
+
+    $re = join('', @re);
+
+    $passes++;
+  } while $doagain && $passes <= 5;
+  return $re;
+}
+
+sub replace_result {
+  my ($self, $re, $match, $conf) = @_;
+
+  my $pre_name;
+  my $post_name;
+  my $inter_name;
+  my $start = $conf->{replace_start};
+  my $end = $conf->{replace_end};
+
+  my @re = split(/(<[^<>]+>)/, $re);
+
+  if ($pre_name) {
+    my $pre = $conf->{replace_pre}->{$pre_name};
+    if ($pre) {
+      s{($start.+?$end)}{$pre$1}  for @re;
+    }
+  }
+  if ($post_name) {
+    my $post = $conf->{replace_post}->{$post_name};
+    if ($post) {
+      s{($start.+?$end)}{$1$post}g  for @re;
+    }
+  }
+  if ($inter_name) {
+    my $inter = $conf->{replace_inter}->{$inter_name};
+    if ($inter) {
+      s{^$}{$inter}  for @re;
+    }
+  }
+  for (my $i = 0; $i < @re; $i++) {
+    if ($re[$i] =~ m|$start(.+?)$end|g) {
+      my $tag_name = $1;
+      # if the tag exists, replace it with the corresponding phrase
+      if ($tag_name) {
+        my $replacement = $conf->{replace_tag}->{$tag_name};
+        if (defined $replacement) {
+          if($replacement =~ /^\(\?\:(.{1})/) {
+            my $subst = $1;
+            my ($rec, $err) = compile_regexp($replacement, 0);
+            if(!$rec) {
+              dbg("Invalid regexp $replacement");
+              return;
+            }
+            $match =~ s/$rec/$subst/g;
+          }
+        }
+      }
+    }
+  }
+  return $match;
+}
+
 sub finish_parsing_end {
   my ($self, $opts) = @_;
 
   dbg("replacetags: replacing tags");
 
   my $conf = $opts->{conf};
-  my $start = $conf->{replace_start};
-  my $end = $conf->{replace_end};
-
   foreach my $rule (keys %{$conf->{replace_rules}}) {
     # process rules only once, mark to replace_rules_done,
     # do NOT delete $conf->{replace_rules}, it's used by BodyRuleExtractor
@@ -99,76 +216,19 @@ sub finish_parsing_end {
     $self->{replace_rules_done}->{$rule} = 1;
 
     if (!exists $conf->{test_qrs}->{$rule}) {
-      dbg("replacetags: replace requested for non-existing or incompatible rule: $rule\n");
+      # HashBL rules will substitute "replace_tag" on a different code path
+      dbg("replacetags: replace requested for non-existing or incompatible/HashBL rule: $rule\n");
       next;
     }
 
     my $re = qr_to_string($conf->{test_qrs}->{$rule});
     next unless defined $re;
-    my $origre = $re;
 
-    my $passes = 0;
-    my $doagain;
-
-    do {
-      my $pre_name;
-      my $post_name;
-      my $inter_name;
-      $doagain = 0;
-
-      # get modifier tags
-      if ($re =~ s/${start}pre (.+?)${end}//) {
-        $pre_name = $1;
-      }
-      if ($re =~ s/${start}post (.+?)${end}//) {
-        $post_name = $1;
-      }
-      if ($re =~ s/${start}inter (.+?)${end}//) {
-        $inter_name = $1;
-      }
-
-      # this will produce an array of tags to be replaced
-      # for two adjacent tags, an element of "" will be between the two
-      my @re = split(/(<[^<>]+>)/, $re);
-
-      if ($pre_name) {
-        my $pre = $conf->{replace_pre}->{$pre_name};
-        if ($pre) {
-          s{($start.+?$end)}{$pre$1}  for @re;
-        }
-      }
-      if ($post_name) {
-        my $post = $conf->{replace_post}->{$post_name};
-        if ($post) {
-          s{($start.+?$end)}{$1$post}g  for @re;
-        }
-      }
-      if ($inter_name) {
-        my $inter = $conf->{replace_inter}->{$inter_name};
-        if ($inter) {
-          s{^$}{$inter}  for @re;
-        }
-      }
-      for (my $i = 0; $i < @re; $i++) {
-        if ($re[$i] =~ m|$start(.+?)$end|g) {
-          my $tag_name = $1;
-          # if the tag exists, replace it with the corresponding phrase
-          if ($tag_name) {
-            my $replacement = $conf->{replace_tag}->{$tag_name};
-            if ($replacement) {
-              $re[$i] =~ s|$start$tag_name$end|$replacement|g;
-              $doagain = 1 if !$doagain && $replacement =~ /<[^>]+>/;
-            }
-          }
-        }
-      }
-
-      $re = join('', @re);
+    my $origre = $re;
 
-      $passes++;
-    } while $doagain && $passes <= 5;
+    $re = $self->replace_regexp($re, $conf);
 
-    if ($re ne $origre) {
+    if (defined $re and ($re ne $origre)) {
       # do the actual replacement
       my ($rec, $err) = compile_regexp($re, 0);
       if (!$rec) {

Modified: spamassassin/trunk/t/data/spam/hashbl
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/spam/hashbl?rev=1926307&r1=1926306&r2=1926307&view=diff
==============================================================================
--- spamassassin/trunk/t/data/spam/hashbl (original)
+++ spamassassin/trunk/t/data/spam/hashbl Tue Jun 10 07:35:09 2025
@@ -41,6 +41,8 @@ email host/domain userpart@host.domain.c
 
 telephone: +1(123)123-4567
 
+telephone: +1(l23)l23-4S67
+
 --ETDFsshmzrOmOVdZ
 Content-Type: application/octet-stream
 Content-Disposition: attachment; filename="macro.xlsm"

Modified: spamassassin/trunk/t/hashbl.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/hashbl.t?rev=1926307&r1=1926306&r2=1926307&view=diff
==============================================================================
--- spamassassin/trunk/t/hashbl.t (original)
+++ spamassassin/trunk/t/hashbl.t Tue Jun 10 07:35:09 2025
@@ -9,7 +9,7 @@ plan skip_all => "Can't use Net::DNS Saf
 
 # run many times to catch some random natured failures
 my $iterations = 5;
-plan tests => 13 * $iterations;
+plan tests => 14 * $iterations;
 
 # ---------------------------------------------------------------------------
 
@@ -18,6 +18,7 @@ plan tests => 13 * $iterations;
  q{ 1.0 X_HASHBL_OSENDR } => '',
  q{ 1.0 X_HASHBL_BTC } => '',
  q{ 1.0 X_HASHBL_NUM } => '',
+ q{ 1.0 X_HASHBL_ONUM } => '',
  q{ 1.0 X_HASHBL_URI } => '',
  q{ 1.0 X_HASHBL_TAG } => '',
  q{ 1.0 META_HASHBL_EMAIL } => '',
@@ -83,6 +84,11 @@ sub check_queries {
   return !%invalid;
 }
 
+tstpre(q{
+loadplugin Mail::SpamAssassin::Plugin::HashBL
+loadplugin Mail::SpamAssassin::Plugin::ReplaceTags
+});
+
 tstlocalrules(q{
   rbl_timeout 30
 
@@ -102,6 +108,20 @@ tstlocalrules(q{
   body     X_HASHBL_NUM eval:check_hashbl_bodyre('hashbltest9.spamassassin.org', 'raw/max=10/shuffle/num', '\b(?:\+)?(?:\s)?((?:[0-9]{1,2})?(?:\s)?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6})\b', '127.0.0.2')
   tflags   X_HASHBL_NUM net
 
+  replace_tag N0      (?:0|O)
+  replace_tag N1      (?:1|l)
+  replace_tag N2      (?:2)
+  replace_tag N3      (?:3)
+  replace_tag N4      (?:4)
+  replace_tag N5      (?:5|S)
+  replace_tag N6      (?:6)
+  replace_tag N7      (?:7)
+  replace_tag N8      (?:8)
+  replace_tag N9      (?:9)
+  replace_rules X_HASHBL_ONUM
+  body     X_HASHBL_ONUM eval:check_hashbl_bodyre('hashbltest9.spamassassin.org', 'raw/max=10/shuffle/num/case/replace', '(?<!\d)((?:<N1>[^a-zA-Z0-9]*)?(?:<N0>|<N1>|<N2>|<N3>|<N4>|<N5>|<N6>|<N7>|<N8>|<N9>){3}[^a-zA-Z0-9]+(?:<N0>|<N1>|<N2>|<N3>|<N4>|<N5>|<N6>|<N7>|<N8>|<N9>){3}[^a-zA-Z0-9]+(?:<N0>|<N1>|<N2>|<N3>|<N4>|<N5>|<N6>|<N7>|<N8>|<N9>){4,6})(?!\d)', '127.0.0.2')
+  tflags   X_HASHBL_ONUM net
+
   # Not supposed to hit, @valid_queries just checks that sha256 is calculated correctly
   body     X_HASHBL_SHA256 eval:check_hashbl_bodyre('hashbltest3.spamassassin.org', 'sha256/max=10/shuffle', '\b([13][a-km-zA-HJ-NP-Z1-9]{25,34})\b')
   tflags   X_HASHBL_SHA256 net