Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

No Format
   loadplugin            ReplaceTags  /path/to/plugin/ReplaceTags.pm
  replace_testsrules         bodyVIAGRA_testsOBFU,rawbody_tests,head_tests,full_tests,uri_testsCIALIS_OBFU # ... and so on
  replace_start_sign    <
  replace_end_sign      >

  # Example classes
  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\Ž\`\'\+]

  # This is only an example for a pair of rules
  body          VIAGRA_OBFU     /(?!viagra)<V>+<SP>*<I>+<SP>*<A>+<SP>*<G>+<SP>*<R>+<SP>*<A>+/i
  describe      VIAGRA_OBFU  Example testrule for ReplaceTags plugin, which would match a lot of different viagra-variationsobfuscated match "viagra"
  score         VIAGRA_OBFU     3

ReplaceTags.pm

No Format


=head1 NAME

ReplaceTags -body Create character-classes for SpamAssassin-rules

A character-class may contain   VIAGRA 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       /viagra/i
  describe      VIAGRA          match plain "viagra"
  score      ReplaceTags  /path/to/plugin/ReplaceTags.pm
  replace_tests VIAGRA          1.5

ReplaceTags.pm

No Format


=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 body_tests,rawbody_tests,head_tests,full_tests,uri_tests
  replace_start_sign    <
  replace_end_sign      >

  class         A  ReplaceTags  /path/to/plugin/ReplaceTags.pm
   [\@\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xe4\xe3\xe2\xe0\xe1\xe2\xe3\xe4\xe5\xe6]
  class         G replace_rules         VIAGRA_OBFU,CIALIS_OBFU,...
  replace_start_sign    <
  replace_end_sign      [gk]>

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

  body          VIAGRA_OBFU     /(?!viagra)<V>+<SP>*<I>+<SP>*<A>+<SP>*<G>+<SP>*<R>+<SP>*<A>+/i
  describe      VIAGRA_OBFU  Testrule   forobfuscated ReplaceTagsmatch Plugin"viagra"
  score         VIAGRA_OBFU     3

This  body 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->{'rulesVIAGRA          /viagra/i
  describe      VIAGRA          match plain "viagra"
  score         VIAGRA          1.5

This example displays how this plugin can be used to match obfuscated and "normal" phrases, which makes it easier
to increase the scores for rules matching only obfuscated patterns on a real world example.

=head1 CONFIGURATION

=over 4

=item replace_rules     list_of_tests

Specify a commaspererated list of symbolic test names of tests which should be parsed. The test will only be
parsed if it is a body, header, uri, full or raw test.

=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'};

  # Put the names of the rules, which get replaced in a hash, for easier usage
  my %Rules = ();
  for my $rule_name (@{$self->{'rules_to_replace'}}) {
    $Rules{$rule_name} = 1;
  }

  for my $rule_set (qw|body_tests rawbody_tests head_tests full_tests uri_tests|) {

    # 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}}) {

      # If rulename doesn't match, skip
      next unless ($Rules{$rule_name});

      # Increase it here, so that we can do some small debugging at the end
      $Rules{$rule_name}++;

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

      # Loop all available tags
      for my $tag_name (keys %{$self->{'tags_to_replace'}}) {

    # TODO check what that# 0 is for (I guess 0 are Check the GLOBAL tests and user-tests will
    #      have a number (uid?)...guess is wrong :( )
rule for replacements and replace them
       for myif ($rule_namecontent (keys %{$pms->{'permsgstatus'}->{'conf'}->{$rule_set}->{0}}=~ m|$start_tag$tag_name$end_tag|) {

      # skip __ rules
 my $replacement    next if ($rule_name =~ m|^__|= get_replacement_value($self,$tag_name);

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

if ($replacement) {
            for my $tagdbg("ReplaceTags: modifying rule $rule_name, (keys %{$self->{'tags_to_replace'}}) {

replacing $start_tag$tag_name$end_tag with $replacement");
           if ($rule_content =~ ms|$start_tag$tag_name$end_tag|) {

$replacement|g;
           my $replacement $pms->{'permsgstatus'}->{'conf'}->{$rule_set}->{0}->{$rule_name} = get_replacement_value($self,$tag_name);

$rule_content;
          }
          if ($replacement)else {
            dbg("ReplaceTags: modifying No replacement found for rule $rule_name, replacing ($start_tag$tag_name$end_tag with $replacement)");
            $rule_content =~ s|$start_tag$tag_name$end_tag|$replacement|g;}

        }

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

    } # for my $rule_name (keys   %{$pms->{'permsgstatus'}->{'conf'}->{$rule_set}->{0}->{$rule_name} = $rule_content;
          }
          else {})

  } # for my $test (qw|body_tests rawbody_tests head_tests full_tests  dbg("ReplaceTags: No replacement found for rule $rule_name, ($start_tag$tag_name$end_tag)");
          }

        }

      }

    }
uri_tests|)

  # See if we have rules, that we should parse and haven't been found
  for my $rule (keys %Rules) {
    dbg("ReplaceTags: Rule $rule not found.") if ($Rules{$rule} == 1);
  }

}

sub get_replacement_value {
  # Substitute replacement with the correct pattern
  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
  #   tagsclass
  #   replace_testsrules
  #   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_testsrules'
        {
          # The replace_testsrules configuration-pragma is used to specify the
contains a commasepareted
          # list of #all testsrules, which should beget parsed checkedby for replacement-tags.this module
          $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;


...