dspam for MailScanner

Todd T. Fries todd at fries.net
Sun Jul 19 22:50:42 IST 2009


I've been using this for a few years now, and keep forgetting to
contribute it back.

This is my own work, I couldn't be more pleased if MailScanner took it
and made the equivalent or better functionality in the default
distribution.

If I can polish it or whatever, please let me know, if it saves you
work.

To explain how this works, I added the ability for the generic spam scanner
to pass back headers to include in the messages, and fork a copy of dspamc
with cmdline args (hardcoded currently to my preferences)..

So with this, one need only setup dspam, and twiddle the following knobs in
MailScanner.conf:

 Use SpamAssassin = no
 Use Custom Spam Scanner = yes
 Custom Spam Scanner Timeout = 1030
 Max Custom Spam Scanner Timeouts = 100
 Custom Spam Scanner Timeout History = 20
 Spam Score Number Format = %5.5f

Thanks,

--- lib/MailScanner/CustomFunctions/GenericSpamScanner.pm.orig	Thu Dec  7 13:12:22 2006
+++ lib/MailScanner/CustomFunctions/GenericSpamScanner.pm	Thu Dec  7 13:32:50 2006
@@ -35,66 +35,80 @@ use IPC::Open2;
 use FileHandle;
 
 sub GenericSpamScanner {
-  my($ip, $from, $to, $message) = @_;
+  my($Message, $message) = @_;
+  my($ip, $from, $to) = ($Message->{clientip},
+	$Message->{from},
+	$Message->{to});
 
-  print STDERR "Generic Spam Scanner\n";
-  print STDERR "====================\n";
-  print STDERR "\n";
-  print STDERR "IP = \"$ip\"\n";
-  print STDERR "From = \"$from\"\n";
-  print STDERR "To = \"" . join(", ", @$to) . "\"\n";
+  #print STDERR "Generic Spam Scanner\n";
+  #print STDERR "====================\n";
+  #print STDERR "\n";
+  #print STDERR "IP = \"$ip\"\n";
+  #print STDERR "From = \"$from\"\n";
+  #print STDERR "To = \"" . join(", ", @$to) . "\"\n";
   #print STDERR "Message = \"" . join(", ", @$message) . "\"\n";
 
  # To call a remote program you might want to do this:
- my($fhread, $fhwrite, $pid, $score, $report);
- die "Can't fork: $!" unless defined($pid = open2($fhread, $fhwrite,
-                                     '/usr/local/bin/yourprogramhere'));
- $fhwrite->print("$ip\n");
- $fhwrite->print("$from\n");
+ my($fhread, $fhwrite, $pid, @result, $report, $users);
  foreach my $address (@$to) {
-  $fhwrite->print("$address\n");
+  $users .= "$address ";
  }
+ my $cmd = sprintf "/usr/local/bin/dspamc --client --stdout --deliver=innocent,spam --mode=tum --user %s",$users;
+ #print STDERR "cmd: $cmd\n";
+ die "Can't fork: $!" unless defined($pid = open2($fhread, $fhwrite, $cmd));
  $fhwrite->print(@$message);
  $fhwrite->flush();
  $fhwrite->close();
- $score = <$fhread>;
- chomp $score;
- print STDERR "Read \"$score\" from your program\n\n";
+ my $state = 0;
+ my ($score,$report);
+ my @headers = ();
+ foreach my $line (<$fhread>) {
+	next if ($state > 0);
+ 	chomp $line;
+	if ($line =~ m/^$/) {
+		$state++;
+		next;
+	}
+	if ($line =~ m/^X-DSPAM/) {
+		$line =~ m/^([^:]*): (.*)$/;
+		my ($header,$val)=($1,$2);
+		next if ($header eq "X-DSPAM-Processed");
+		push @headers,"$header:$val";
+		#printf STDERR "Storing: $header \/ $val, now %s headers\n", $#headers;
+		$global::MS->{mta}->AddHeader($Message,
+				"${header}:", $val);
+		#@$message = ($line,@$message);
+		if ($header eq "X-DSPAM-Result") {
+			$result=$val;
+		}
+		if ($header eq "X-DSPAM-Confidence") {
+			# Confidence is a percentage, so
+			# make it 'spam' for 1.0 - 7.0, and
+			# make it 'ham' for 0.0 - 0.999999
+			if ($result eq "Spam") {
+				$score= 6.0*$val+1.0;
+			} else {
+				$score = 1.0-$val;
+			}
+			#print STDERR "Score! $score\n";
+		}
+		if ($header eq "X-DSPAM-Improbability") {
+			$report=$val;
+			#print STDERR "Report! $report\n";
+		}
+	}
+	next;
+ }
+	
+ #print STDERR "Read \"$score\" from your program\n";
  $score = $score+0.0;
 
- $report = <$fhread>;
- chomp $report; 
- print STDERR "Read \"$report\" from your program\n\n";
+ #print STDERR "Read \"$report\" from your program\n";
+ #printf STDERR "Read %d headers from your program\n",$#headers;
 
- return ($score, $report);
+ return ($score, $report, @headers);
 
  # return (0.0, 'No report');
 }
 
 1;
-
-__DATA__
-
-#------------------------------------------------------------
-#
-# C source code of a skeleton yourprogramhere program
-#
-#------------------------------------------------------------
-
-#include <stdio.h>
-#include <stdlib.h>
-
-char buffer[256];
-
-int main(void) {
-  char *result;
-
-  result = fgets(buffer, 256, stdin);
-  while(result!=NULL) {
-    result = fgets(buffer, 256, stdin);
-  }
-
-  printf("55\n");
-  printf("This is a report\n");
-}
-
--- lib/MailScanner/GenericSpam.pm.orig	Thu Dec  7 13:12:22 2006
+++ lib/MailScanner/GenericSpam.pm	Thu Dec  7 13:33:07 2006
@@ -101,19 +101,19 @@ sub Checks {
   push(@WholeMessage, "\n");
   $message->{store}->ReadBody(\@WholeMessage, $maxsize);
 
-  my($GenericSpamResult, $GenericSpamReport);
+  my($GenericSpamResult, $GenericSpamReport, @GenericSpamHeaders);
   $GenericSpamResult = 0;
   $GenericSpamReport = "";
-  ($GenericSpamResult, $GenericSpamReport) =
+  ($GenericSpamResult, $GenericSpamReport, @GenericSpamHeaders) =
     GSForkAndTest($message, \@WholeMessage);
-  return ($GenericSpamResult, $GenericSpamReport);
+  return ($GenericSpamResult, $GenericSpamReport, @GenericSpamHeaders);
 }
 
 # Run the generic spam scanner, and capture the 2 lines of output
 sub GSForkAndTest {
   my($Message, $Contents) = @_;
 
-  my($pipe, $gsscore, $gsreport, $queuelength);
+  my($pipe, $gsscore, $gsreport, @gsheaders, $queuelength);
   my $PipeReturn = 0;
 
   $queuelength = MailScanner::Config::Value('gstimeoutlen', $Message);
@@ -129,23 +129,25 @@ sub GSForkAndTest {
     # In the child
     $pipe->writer();
     $pipe->autoflush();
-    my($gsscore, $gsreport);
+    my($gsscore, $gsreport, @gsheaders);
     eval {
       #print STDERR "ClientIP = " . $Message->{clientip} . "\n";
       #print STDERR "From = " . $Message->{from} . "\n";
       #print STDERR "To = " . join(', ', @{$Message->{to}}) . "\n";
       #print STDERR "This is in the caller\n";
 
-      ($gsscore, $gsreport) = MailScanner::CustomConfig::GenericSpamScanner(
-       $Message->{clientip},
-       $Message->{from},
-       $Message->{to},
+      ($gsscore, $gsreport, @gsheaders) = MailScanner::CustomConfig::GenericSpamScanner(
+       $Message,
        $Contents);
     };
     
     $gsscore = $gsscore + 0.0;
     print $pipe "$gsscore\n";
     print $pipe $gsreport . "\n";
+    foreach my $header (@gsheaders) {
+        #print STDERR " to pipe .. <$header> \n";
+	print $pipe $header . "\n";
+    }
     $pipe->close();
     $pipe = undef;
     exit 0;
@@ -157,6 +159,10 @@ sub GSForkAndTest {
     alarm MailScanner::Config::Value('gstimeout');
     $gsscore = <$pipe>;
     $gsreport = <$pipe>;
+    foreach my $h (<$pipe>) {
+	chomp $h;
+	push @gsheaders,$h;
+    }
 
     # Not sure if next 2 lines should be this way round...
     waitpid $pid, 0;
@@ -246,7 +252,7 @@ sub GSForkAndTest {
   #print STDERR "Generic Spam Scanner points = $gsscore\n";
   #print STDERR "Generic Spam Scanner report = $gsreport\n";
 
-  return ($gsscore, $gsreport);
+  return ($gsscore, $gsreport, @gsheaders);
 }
 
 1;
--- lib/MailScanner/Message.pm.orig	Fri May  8 05:20:04 2009
+++ lib/MailScanner/Message.pm	Wed May 13 19:22:44 2009
@@ -719,12 +719,19 @@ sub IsSpam {
   # rblspamheader is useful start to spamreport if RBLsaysspam.
 
   # Do the Custom Spam Checker
-  my($gsscore, $gsreport);
+  my($gsscore, $gsreport, @gsheaders);
   #print STDERR "In Message.pm about to look at gsscanner\n";
   if ($usegsscanner) {
     #print STDERR "In Message.pm about to run gsscanner\n";
-    ($gsscore, $gsreport) = MailScanner::GenericSpam::Checks($this);
+    ($gsscore, $gsreport, @gsheaders) = MailScanner::GenericSpam::Checks($this);
     #print STDERR "In Message.pm we got $gsscore, $gsreport\n";
+    foreach my $line (@gsheaders) {
+      my ($header,$val)=split(/:/,$line);
+      #print STDERR "In Message.pm we got gsheader $line <${header}|${val}>\n";
+      $global::MS->{mta}->AddHeader($this,
+                                    "$header:",
+                                    $val);
+    }
     $this->{gshits} = $gsscore;
     $this->{gsreport} = $gsreport;
     $this->{sascore} += $gsscore; # Add the score
-- 
Todd Fries .. todd at fries.net

 _____________________________________________
|                                             \  1.636.410.0632 (voice)
| Free Daemon Consulting, LLC                 \  1.405.227.9094 (voice)
| http://FreeDaemonConsulting.com             \  1.866.792.3418 (FAX)
| "..in support of free software solutions."  \          250797 (FWD)
|                                             \
 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
                                                 
              37E7 D3EB 74D0 8D66 A68D  B866 0326 204E 3F42 004A
                        http://todd.fries.net/pgp.txt



More information about the MailScanner mailing list