You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by fe...@apache.org on 2004/09/15 05:40:56 UTC

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

Author: felicity
Date: Tue Sep 14 20:40:55 2004
New Revision: 46076

Added:
   spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm
   spamassassin/trunk/rules/25_razor2.cf
Log:
doh, forgot to add the razor2 plugin and rule file...

Added: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm
==============================================================================
--- (empty file)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm	Tue Sep 14 20:40:55 2004
@@ -0,0 +1,317 @@
+=head1 NAME
+
+Mail::SpamAssassin::Plugin::Razor2 - perform Razor2 check of messages
+
+=head1 SYNOPSIS
+
+  loadplugin     Mail::SpamAssassin::Plugin::Razor2
+
+=cut
+
+# <@LICENSE>
+# Copyright 2004 Apache Software Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </...@LICENSE>
+
+package Mail::SpamAssassin::Plugin::Razor2;
+
+use Mail::SpamAssassin::Plugin;
+use strict;
+use bytes;
+
+use vars qw(@ISA);
+@ISA = qw(Mail::SpamAssassin::Plugin);
+
+sub new {
+  my $class = shift;
+  my $mailsaobject = shift;
+
+  $class = ref($class) || $class;
+  my $self = $class->SUPER::new($mailsaobject);
+  bless ($self, $class);
+
+  $self->{razor2_available} = 0;
+  $mailsaobject->{conf}->{razor2_timeout} = 10;
+
+  if ($mailsaobject->{local_tests_only}) {
+    dbg ("local tests only, ignoring Razor2", "razor", -1);
+  }
+  else {
+    if (eval { require Razor2::Client::Agent; }) {
+      dbg("Razor2 is available", "razor", -1);
+      $self->{razor2_available} = 1;
+    }
+    else {
+      dbg("Razor2 is not available", "razor", -1);
+    }
+  }
+
+  $self->register_eval_rule ("check_razor2");
+  $self->register_eval_rule ("check_razor2_range");
+
+  return $self;
+}
+
+sub parse_config {
+  my ($self, $opts) = @_;
+
+  my $conf = $opts->{conf};
+  my $key = $opts->{key};
+  my $value = $opts->{value};
+  my $line = $opts->{line};
+
+  # Backward compatibility ...  use_razor2 is implicit if the plugin is loaded
+  if ($key eq 'use_razor2') {
+    $self->inhibit_further_callbacks();
+    return 1;
+  }
+
+=item razor_timeout n		(default: 10)
+
+How many seconds you wait for razor to complete before you go on without
+the results
+
+=cut
+
+  if ($key eq 'razor_timeout') {
+    Mail::SpamAssassin::Conf::Parser::set_numeric_value($conf, $key, $value, $line);
+    $self->inhibit_further_callbacks();
+    return 1;
+  }
+
+=item razor_config filename
+
+Define the filename used to store Razor's configuration settings.
+Currently this is left to Razor to decide.
+
+=cut
+
+  if ($key eq 'razor_config') {
+    Mail::SpamAssassin::Conf::Parser::set_string_value($conf, $key, $value, $line);
+    $self->inhibit_further_callbacks();
+    return 1;
+  }
+
+  return 0;
+}
+
+sub razor2_lookup {
+  my ($self, $permsgstatus, $fulltext) = @_;
+  my $timeout=$self->{main}->{conf}->{razor_timeout};
+
+  # Set the score for the ranged checks
+  $self->{razor2_cf_score} = 0;
+  return $self->{razor2_result} if ( defined $self->{razor2_result} );
+  $self->{razor2_result} = 0;
+
+  # this test covers all aspects of availability
+  if (!$self->{razor2_available}) { return 0; }
+  
+  # razor also debugs to stdout. argh. fix it to stderr...
+  if ($Mail::SpamAssassin::DEBUG->{enabled}) {
+    open (OLDOUT, ">&STDOUT");
+    open (STDOUT, ">&STDERR");
+  }
+
+  $permsgstatus->enter_helper_run_mode();
+
+    eval {
+      local ($^W) = 0;    # argh, warnings in Razor
+
+      local $SIG{ALRM} = sub { die "alarm\n" };
+      alarm $timeout;
+
+      # everything's in the module!
+      my $rc = Razor2::Client::Agent->new('razor-check');
+
+      if ($rc) {
+        my %opt = (
+            debug      => ($Mail::SpamAssassin::DEBUG->{enabled} and
+                 $Mail::SpamAssassin::DEBUG->{razor} < -2), 
+	    foreground => 1,
+            config     => $self->{main}->{conf}->{razor_config}
+        );
+        $rc->{opt} = \%opt;
+        $rc->do_conf() or die $rc->errstr;
+
+	my $tmptext = $$fulltext;
+	my @msg = (\$tmptext);
+
+        my $objects = $rc->prepare_objects( \@msg )
+          or die "error in prepare_objects";
+        $rc->get_server_info() or die $rc->errprefix("spamassassin");
+
+	# let's reset the alarm since get_server_info() calls
+	# nextserver() which calls discover() which very likely will
+	# reset the alarm for us ... how polite.  :(  
+	alarm $timeout;
+
+        my $sigs = $rc->compute_sigs($objects)
+          or die "error in compute_sigs";
+
+        # 
+        # if mail isn't whitelisted, check it out
+        #   
+        if ( ! $rc->local_check( $objects->[0] ) ) {
+          if (!$rc->connect()) {
+            # provide a better error message when servers are unavailable,
+            # than "Bad file descriptor Died".
+            die "could not connect to any servers\n";
+          }
+          $rc->check($objects) or die $rc->errprefix("spamassassin");
+          $rc->disconnect() or die $rc->errprefix("spamassassin");
+
+	  # if we got here, we're done doing remote stuff, abort the alert
+	  alarm 0;
+
+          # figure out if we have a log file we need to close...
+          if (ref($rc->{logref}) && exists $rc->{logref}->{fd}) {
+            # the fd can be stdout or stderr, so we need to find out if it is
+	    # so we don't close them by accident.  Note: we can't just
+	    # undef the fd here (like the IO::Handle manpage says we can)
+	    # because it won't actually close, unfortunately. :(
+            my $untie = 1;
+            foreach my $log ( *STDOUT{IO}, *STDERR{IO} ) {
+              if ($log == $rc->{logref}->{fd}) {
+                $untie = 0;
+                last;
+              }
+            }
+            close $rc->{logref}->{fd} if ($untie);
+          }
+
+	  dbg("Using results from Razor v".$Razor2::Client::Version::VERSION, "razor", -1);
+
+	  # so $objects->[0] is the first (only) message, and ->{spam} is a general yes/no
+          $self->{razor2_result} = $objects->[0]->{spam} || 0;
+
+	  # great for debugging, but leave this off!
+	  #use Data::Dumper;
+	  #print Dumper($objects),"\n";
+
+	  # ->{p} is for each part of the message
+	  # so go through each part, taking the highest cf we find
+	  # of any part that isn't contested (ct).  This helps avoid false
+	  # positives.  equals logic_method 4.
+	  #
+	  # razor-agents < 2.14 have a different object format, so we now support both.
+	  # $objects->[0]->{resp} vs $objects->[0]->{p}->[part #]->{resp}
+	  my $part = 0;
+	  my $arrayref = $objects->[0]->{p} || $objects;
+	  if ( defined $arrayref ) {
+	    foreach my $cf ( @{$arrayref} ) {
+	      if ( exists $cf->{resp} ) {
+	        for (my $response=0;$response<@{$cf->{resp}};$response++) {
+	          my $tmp = $cf->{resp}->[$response];
+	      	  my $tmpcf = $tmp->{cf} || 0; # Part confidence
+	      	  my $tmpct = $tmp->{ct} || 0; # Part contested?
+		  my $engine = $cf->{sent}->[$response]->{e};
+	          dbg("Found Razor2 part: part=$part engine=$engine ct=$tmpct cf=$tmpcf", "razor", -1);
+	          $self->{razor2_cf_score} = $tmpcf if ( !$tmpct && $tmpcf > $self->{razor2_cf_score} );
+	        }
+	      }
+	      else {
+		my $text = "part=$part noresponse";
+		$text .= " skipme=1" if ( $cf->{skipme} );
+	        dbg("Found Razor2 part: $text", "razor", -1);
+	      }
+	      $part++;
+	    }
+	  }
+	  else {
+	    # If we have some new $objects format that isn't close to
+	    # the current razor-agents 2.x version, we won't FP but we
+	    # should alert in debug.
+	    dbg("It looks like the internal Razor object has changed format!  Tell spamassassin-devel!",
+	    	"razor", -1);
+	  }
+        }
+      }
+      else {
+        warn "undefined Razor2::Client::Agent\n";
+      }
+  
+      alarm 0;
+    };
+
+    alarm 0;    # just in case
+  
+    if ($@) {
+      if ( $@ =~ /alarm/ ) {
+          dbg("razor2 check timed out after $timeout secs.");
+        } elsif ($@ =~ /(?:could not connect|network is unreachable)/) {
+          # make this a dbg(); SpamAssassin will still continue,
+          # but without Razor checking.  otherwise there may be
+          # DSNs and errors in syslog etc., yuck
+          dbg("razor2 check could not connect to any servers");
+        } else {
+          warn("razor2 check skipped: $! $@");
+        }
+      }
+
+  # work around serious brain damage in Razor2 (constant seed)
+  srand;
+
+  $permsgstatus->leave_helper_run_mode();
+
+  # razor also debugs to stdout. argh. fix it to stderr...
+  if ($Mail::SpamAssassin::DEBUG->{enabled}) {
+    open (STDOUT, ">&OLDOUT");
+    close OLDOUT;
+  }
+
+  dbg("Razor2 results: spam? ".$self->{razor2_result}."  highest cf score: ".$self->{razor2_cf_score},
+      "razor", -1);
+
+  if ($self->{razor2_result} > 0) {
+      return 1;
+  }
+  return 0;
+}
+
+sub check_razor2 {
+  my ($self, $permsgstatus) = @_;
+
+  return 0 unless ($self->{razor2_available});
+  return $self->{razor2_result} if (defined $self->{razor2_result});
+
+  my $full = $permsgstatus->{msg}->get_pristine();
+  return $self->razor2_lookup ($permsgstatus, \$full);
+}
+
+# Check the cf value of a given message and return if it's within the
+# given range
+sub check_razor2_range {
+  my ($self, $permsgstatus, $body, $min, $max) = @_;
+
+  # If Razor2 isn't available, or the general test is disabled, don't
+  # continue.
+  return 0 unless $self->{razor2_available};
+  return 0 unless $self->{main}->{conf}->{scores}->{'RAZOR2_CHECK'};
+
+  # If Razor2 hasn't been checked yet, go ahead and run it.
+  if (!defined $self->{razor2_result}) {
+    $self->check_razor2($permsgstatus);
+  }
+
+  if ($self->{razor2_cf_score} >= $min && $self->{razor2_cf_score} <= $max) {
+    $permsgstatus->test_log(sprintf ("cf: %3d", $self->{razor2_cf_score}));
+    return 1;
+  }
+  return 0;
+}
+
+sub dbg { Mail::SpamAssassin::dbg (@_); }
+
+1;

Added: spamassassin/trunk/rules/25_razor2.cf
==============================================================================
--- (empty file)
+++ spamassassin/trunk/rules/25_razor2.cf	Tue Sep 14 20:40:55 2004
@@ -0,0 +1,35 @@
+ifplugin Mail::SpamAssassin::Plugin::SPF
+
+full RAZOR2_CHECK	eval:check_razor2()
+describe RAZOR2_CHECK	Listed in Razor2 (http://razor.sf.net/)
+tflags RAZOR2_CHECK	net
+
+# cf (confidence level) is how likely the message is spam.  RAZOR2_CHECK
+# returns true if cf>=min_cf (as defined by user/config).  These return
+# true depending on what cf value the message has.  The algorithm goes:
+# check the message via razor, then go through each mime part and check
+# how razor scored it.  If the part is contested (ie: it's been reported
+# as both ham and spam) it's ignored.  SA takes the highest non-contested
+# part cf score and returns it for the range rules.  ie: This is essentially
+# Razor 2's logic_method 4.
+#
+# Note: Disabling RAZOR2_CHECK (score RAZOR2_CHECK 0) will also disable
+# these checks.
+#
+# Note: The scores are set to 0 on these tests right now until they get
+# better integrated with SA overall.
+#
+full	RAZOR2_CF_RANGE_51_100	eval:check_razor2_range('51','100')
+tflags  RAZOR2_CF_RANGE_51_100	net
+describe RAZOR2_CF_RANGE_51_100	Razor2 gives confidence level above 50%
+
+lang de describe RAZOR2_CHECK Gelistet im "Razor2"-System (http://razor.sf.net/)
+lang de describe RAZOR2_CF_RANGE_51_100 Razor2 Spam-Bewertung liegt zwischen 51 und 100
+lang fr describe RAZOR2_CHECK		Message list� par Razor2, voir http://razor.sourceforge.net
+lang fr describe RAZOR2_CF_RANGE_51_100	Razor2 donne un indice de confiance entre 51 et 100
+lang nl describe RAZOR2_CHECK                    Gevonden in Razor2 (http://razor.sf.net/)
+lang nl describe RAZOR2_CF_RANGE_51_100          Razor2 geeft een zekerheid tussen 51 en 100
+lang pl describe RAZOR2_CF_RANGE_51_100	Razor2 stwierdzi� pewno�� pomi�dzy 51 i 100
+lang pl describe RAZOR2_CHECK		Na li�cie Razor2 (http://razor.sf.net/)
+
+endif