You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

The ReplaceTags Plugin

This SpamAssassin plugin module allows you to create various character-classes and use them in your rules. The basic advantage is that you don't have to write huge rules, which are harder to debug, but can use more readable shortcuts. Anotherone is that it gets pretty easy to extend rules by only modifying the class. It is also pretty customizeable for individual start/end tags and which tests are parsed.

Code

Add the following to your local.cf file:

   loadplugin            ReplaceTags  /path/to/plugin/ReplaceTags.pm
  replace_tests         body_tests,rawbody_tests,head_tests,full_tests,uri_tests
  replace_start_sign    <
  replace_end_sign      >

  class         A       [\@\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xe4\xe3\xe2\xe0\xe1\xe2\xe3\xe4\xe5\xe6]
  class         G       [gk]
  class         I       [il\|\!1y\?\xcc\xcd\xce\xcf\xec\xed\xee\xef]
  class         R       [r3]
  class         V       [v\\\/wu]
  class         SP      [\s\d\_\-\*\$\%\(\)\,\.\:\;\?\!\}\{\[\]\|\/\?\^\#\~\xa1\Ž\`\'\+]

  body          VIAGRA  /<V>+<SP>*<I>+<SP>*<A>+<SP>*<G>+<SP>*<R>+<SP>*<A>+/i
  describe      VIAGRA  Example testrule for ReplaceTags plugin, which would match a lot of different viagra-variations
  score         VIAGRA  3

ReplaceTags.pm

=head1 NAME

ReplaceTags - Create character-classes for SpamAssassin-rules

A character-class may contain of any character which is valid in a regular expression.
The grouped characters are easy to maintain and the rules are more readable.

=head1 SYNOPSIS

  loadplugin            ReplaceTags  /path/to/plugin/ReplaceTags.pm
  replace_tests         body_tests,rawbody_tests,head_tests,full_tests,uri_tests
  replace_start_sign    <
  replace_end_sign      >

  class         A       [\@\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xe4\xe3\xe2\xe0\xe1\xe2\xe3\xe4\xe5\xe6]
  class         G       [gk]
  class         I       [il\|\!1y\?\xcc\xcd\xce\xcf\xec\xed\xee\xef]
  class         R       [r3]
  class         V       [v\\\/wu]
  class         SP      [\s\d\_\-\*\$\%\(\)\,\.\:\;\?\!\}\{\[\]\|\/\?\^\#\~\xa1\Ž\`\'\+]

  body          VIAGRA  /<V>+<SP>*<I>+<SP>*<A>+<SP>*<G>+<SP>*<R>+<SP>*<A>+/i
  describe      VIAGRA  Testrule for ReplaceTags Plugin
  score         VIAGRA  3

This VIAGRA rule would match many different variations of "viagra" (like "v*ì*@*g*3*Ä" for instance, which
is a real-world example).

=head1 CONFIGURATION

=over 4

=item replace_tests     list_of_tests

Specify a commaspererated list of names of tests which should be parsed. Valid values are C<body_tests>,
C<rawbody_tests>, C<head_tests>, C<full_tests> and C<uri_tests>.

=item replace_start_sign sign

=item replace_end_sign   sign

Character(s) which indicate the start and end of a class inside a rule. If the class is not enclosed by this
signs it won't be found nor replaced. If you encounter problems run spamassassin from the commandline with
the -D flag and check the output. The default values are < (for replace_start_sign) and > (for replace_end_sign).

=item class classname characters

Define a character class. Valid characters are the same as in any usual regular expression. It is not a good idea to
put quantifiers inside the character class, better use them inside your rule is shown above in the example.

=cut

package ReplaceTags;

use strict;
use Mail::SpamAssassin;
use Mail::SpamAssassin::Plugin;
use Switch;

our @ISA = qw|Mail::SpamAssassin::Plugin|;

sub new {
  my ($class, $mailsa) = @_;
      $class = ref($class) || $class;

  my $self = $class->SUPER::new($mailsa);

  bless ($self, $class);

  return $self;
}

sub check_start {
  # This is the point where the rulesets get replaced with our stuff
  my ($self,$pms) = @_;

  my $start_tag   = $self->{'replace_start_sign'} ;

  my $end_tag     = $self->{'replace_end_sign'};

  for my $rule_set (@{$self->{'rules_to_replace'}}) {

    # TODO check what that 0 is for (I guess 0 are the GLOBAL tests and user-tests will
    #      have a number (uid?)...guess is wrong :( )
    for my $rule_name (keys %{$pms->{'permsgstatus'}->{'conf'}->{$rule_set}->{0}}) {
      # skip __ rules
      next if ($rule_name =~ m|^__|);

      my $rule_content = $pms->{'permsgstatus'}->{'conf'}->{$rule_set}->{0}->{$rule_name};

      for my $tag_name (keys %{$self->{'tags_to_replace'}}) {

        if ($rule_content =~ m|$start_tag$tag_name$end_tag|) {

          my $replacement = get_replacement_value($self,$tag_name);

          if ($replacement) {
            dbg("ReplaceTags: modifying rule $rule_name, replacing $start_tag$tag_name$end_tag with $replacement");
            $rule_content =~ s|$start_tag$tag_name$end_tag|$replacement|g;
            $pms->{'permsgstatus'}->{'conf'}->{$rule_set}->{0}->{$rule_name} = $rule_content;
          }
          else {
            dbg("ReplaceTags: No replacement found for rule $rule_name, ($start_tag$tag_name$end_tag)");
          }

        }

      }

    }

  }

}

sub get_replacement_value {
  my ($self,$tag_name) = @_;

  my $replacement = $self->{'tags_to_replace'}->{$tag_name};

  if ($replacement) {
    dbg("ReplaceTags: Replacement found $replacement");
  }
  else {
    dbg("ReplaceTags: No Replacement for $tag_name");
  }
  return ($replacement);
}

sub parse_config {
  # Used configuration pragmas are
  #   tags
  #   replace_tests
  #   replace_start_sign
  #   replace_end_sign

  my ($self, $opts) = @_;

  switch ($opts->{'key'}) {
    case 'class'
        {
          if ($opts->{'value'} =~ m|^(\S+)\s+(.*?)\s*$|) {
            my $tag_name        = $1;
            my $tag_replacement = $2;

            dbg("ReplaceTags: parse_config got $tag_name -> $tag_replacement");

            $self->{'tags_to_replace'}->{$tag_name} = $tag_replacement;
            #$opts->{'conf'}->{'tags_to_replace'}->{$TagName} = $TagReplacement;
          }
        }
   case 'replace_tests'
        {
          # The replace_tests configuration-pragma is used to specify the
          # tests which should be checked for replacement-tags.
          $opts->{'value'} =~ s|\s*||g;

          @{$self->{'rules_to_replace'}} = split(/\,/,$opts->{'value'});
        }

   case 'replace_start_sign'
        {
          # The replace_start_sign indicates the start of a replacement-tag. If this
          # setting is omitted a < is used as default.
          if ($opts->{'value'}) {
            dbg("ReplaceTags: setting start sign to '".$opts->{'value'}."'");

            $self->{'replace_start_sign'} = $opts->{'value'};
          }
          else {
            dbg("ReplaceTags: no start sign specified. Fall back to default '<'");

            $self->{'replace_start_sign'} = '<';
          }
        }

   case 'replace_end_sign'
        {
          # The replace_end_sign indicates the end of a replacement-tag. If this
          # setting is omitted a > is used as default.
          if ($opts->{'value'}) {
            dbg("ReplaceTags: setting end sign to '".$opts->{'value'}."'");

            $self->{'replace_end_sign'} = $opts->{'value'};
          }
          else {
            dbg("ReplaceTags: no end sign specified. Fall back to default '>'");

            $self->{'replace_end_sign'} = '>';
          }
        }
  }
}

sub dbg { Mail::SpamAssassin::dbg (@_); }

1;


How To Use It

See the pod or the example above.

Example Classes

class   A       [gra\@\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xe4\xe3\xe2\xe0\xe1\xe2\xe3\xe4\xe5\xe60o]
class   B       [b8]
class   C       [kc\xc7\xe7@]
class   D       [d\xd0]
class   E       [e3\xc8\xc9\xca\xcb\xe8\xe9\xea\xeb\xa4]
class   F       [f]
class   G       [gk]
class   H       [h]
class   I       [il\|\!1y\?\xcc\xcd\xce\xcf\xec\xed\xee\xef]
class   J       [j]
class   K       [k]
class   L       [il\|\!1\xa3]
class   M       [m]
class   N       [n\xd1\xf1]
class   O       [go0\xd2\xd3\xd4\xd5\xd6\xd8\xf0\xf2\xf3\xf4\xf5\xf6\xf8]
class   P       [p\xfek]
class   Q       [q]
class   R       [r]
class   S       [s\xa6\xa7]
class   T       [t]
class   U       [uv\xb5\xd9\xda\xdb\xdc\xfc\xfb\xfa\xf9\xfd]
class   V       [v\\\/]
class   W       [wv]
class   X       [x\xd7]
class   Y       [y\xff\xfd\xa5j]
class   Z       [zs]
class   PIC     (jpe*g|gif|png)
class   SP      [\s\d\_\-\*\$\%\(\)\,\.\:\;\?\!\}\{\[\]\|\/\?\^\#\~\xa1\<B4>\`\'\+]
class   CUR     [\$\xa5\xa3\xa4\xa2]
  • No labels