#
# MailScanner - SMTP E-Mail Virus Scanner
# Copyright (C) 2002 Julian Field
#
# $Id: GenericSpam.pm 3116 2005-07-11 20:16:13Z jkf $
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The author, Julian Field, can be contacted by email at
# Jules@JulianField.net
# or by paper mail at
# Julian Field
# Dept of Electronics & Computer Science
# University of Southampton
# Southampton
# SO17 1BJ
# United Kingdom
#
package MailScanner::GenericSpam;
use strict 'vars';
use strict 'refs';
no strict 'subs'; # Allow bare words for parameter %'s
#use English; # Needed for $PERL_VERSION to work in all versions of Perl
use IO;
use POSIX qw(:signal_h); # For Solaris 9 SIG bug workaround
# Don't do this any more as SpamAssassin prefers to do it itself
# use AnyDBM_File; # Doing this here keeps SpamAssassin quiet
use vars qw($VERSION);
### The package version, both in 1.23 style *and* usable by MakeMaker:
$VERSION = substr q$Revision: 3116 $, 10;
# Attributes are
#
#
my @GSsuccessqueue; # queue of failure history
my $GSsuccessqsum; # current sum of history queue
sub initialise {
# Initialise the class variables
@GSsuccessqueue = ();
$GSsuccessqsum = 0;
}
# Constructor.
sub new {
my $type = shift;
my $this = {};
bless $this, $type;
return $this;
}
# Do the Generic Spam checks on the passed in message
sub Checks {
my($message) = @_;
my(@WholeMessage, $scanner, $maxsize);
#print STDERR "Doing Generic Spam Checks\n";
# If they aren't using the generic spam scanner, bail out
$scanner = MailScanner::Config::Value('gsscanner', $message);
#return 0 unless $scanner;
# Bail out and fake a miss if too many consecutive GS checks failed
my $maxfailures = MailScanner::Config::Value('maxgstimeouts');
# If we get maxfailures consecutive timeouts, then disable the
# SpamAssassin RBL checks in an attempt to get it working again.
# If it continues to time out for another maxfailures consecutive
# attempts, then disable it completely.
if ($maxfailures>0 && $GSsuccessqsum>=$maxfailures) {
return (0,0,
sprintf(MailScanner::Config::LanguageValue($message,'gsdisabled'),
$maxfailures), 0);
}
$maxsize = MailScanner::Config::Value('maxgssize');
push(@WholeMessage, $global::MS->{mta}->OriginalMsgHeaders($message, "\n"));
#print STDERR "Headers are : " . join(', ', @WholeMessage) . "\n";
push(@WholeMessage, "\n");
$message->{store}->ReadBody(\@WholeMessage, $maxsize);
my($GenericSpamResult, $GenericSpamReport);
$GenericSpamResult = 0;
$GenericSpamReport = "";
($GenericSpamResult, $GenericSpamReport) =
GSForkAndTest($message, \@WholeMessage);
return ($GenericSpamResult, $GenericSpamReport);
}
# Run the generic spam scanner, and capture the 2 lines of output
sub GSForkAndTest {
my($Message, $Contents) = @_;
my($pipe, $gsscore, $gsreport, $queuelength);
my $PipeReturn = 0;
$queuelength = MailScanner::Config::Value('gstimeoutlen', $Message);
$pipe = new IO::Pipe
or MailScanner::Log::DieLog('Failed to create pipe, %s, try reducing ' .
'the maximum number of unscanned messages per batch', $!);
my $pid = fork();
die "Can't fork: $!" unless defined($pid);
if ($pid == 0) {
# In the child
$pipe->writer();
$pipe->autoflush();
my($gsscore, $gsreport);
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},
$Contents);
};
$gsscore = $gsscore + 0.0;
print $pipe "$gsscore\n";
print $pipe $gsreport . "\n";
$pipe->close();
$pipe = undef;
exit 0;
}
eval {
$pipe->reader();
local $SIG{ALRM} = sub { die "Command Timed Out" };
alarm MailScanner::Config::Value('gstimeout');
$gsscore = <$pipe>;
$gsreport = <$pipe>;
# Not sure if next 2 lines should be this way round...
waitpid $pid, 0;
$pipe->close();
$PipeReturn = $?;
alarm 0;
$pid = 0;
chomp $gsscore;
chomp $gsreport;
$gsscore = $gsscore + 0.0;
# We got a result so store a success
push @GSsuccessqueue, 0;
# Roll the queue along one
$GSsuccessqsum += (shift @GSsuccessqueue)?1:-1
if @GSsuccessqueue>$queuelength;
#print STDERR "Success: sum = $GSsuccessqsum\n";
$GSsuccessqsum = 0 if $GSsuccessqsum<0;
};
alarm 0;
# Workaround for bug in perl shipped with Solaris 9,
# it doesn't unblock the SIGALRM after handling it.
eval {
my $unblockset = POSIX::SigSet->new(SIGALRM);
sigprocmask(SIG_UNBLOCK, $unblockset)
or die "Could not unblock alarm: $!\n";
};
# Note to self: I only close the KID in the parent, not in the child.
# Catch failures other than the alarm
MailScanner::Log::DieLog("Generic Spam Scanner failed with real error: $@")
if $@ and $@ !~ /Command Timed Out/;
# In which case any failures must be the alarm
#if ($@ or $pid>0) {
if ($pid>0) {
my $maxfailures = MailScanner::Config::Value('maxgstimeouts');
# Increment the "consecutive" counter
#$safailures++;
if ($maxfailures>0) {
# We got a failure
push @GSsuccessqueue, 1;
$GSsuccessqsum++;
# Roll the queue along one
$GSsuccessqsum += (shift @GSsuccessqueue)?1:-1
if @GSsuccessqueue>$queuelength;
#print STDERR "Failure: sum = $GSsuccessqsum\n";
$GSsuccessqsum = 0 if $GSsuccessqsum<0;
if ($GSsuccessqsum>$maxfailures && @GSsuccessqueue>=$queuelength) {
MailScanner::Log::WarnLog("Generic Spam Scanner timed out and was" .
" killed, failure %d of %d", $GSsuccessqsum, $maxfailures);
}
} else {
MailScanner::Log::WarnLog("Generic Spam Scanner timed out and was killed");
}
# Make the report say GS was killed
$gsreport = MailScanner::Config::LanguageValue($Message, 'gstimedout');
# Kill the running child process
my($i);
kill 15, $pid; # Was -15
# Wait for up to 10 seconds for it to die
for ($i=0; $i<5; $i++) {
sleep 1;
waitpid($pid, &POSIX::WNOHANG);
($pid=0),last unless kill(0, $pid);
kill 15, $pid; # Was -15
}
# And if it didn't respond to 11 nice kills, we kill -9 it
if ($pid) {
kill 9, $pid; # Was -9
waitpid $pid, 0; # 2.53
}
}
#MailScanner::Log::WarnLog("8 PID is $pid");
# Generic Spam Scanner may play with the umask
umask 0077; # Safety net
# The return from the pipe is a measure of how spammy it was
MailScanner::Log::DebugLog("Generic Spam Scanner returned $PipeReturn");
# The Generic Spam Scanner returned something interesting
#print STDERR "Generic Spam Scanner points = $gsscore\n";
#print STDERR "Generic Spam Scanner report = $gsreport\n";
return ($gsscore, $gsreport);
}
1;
syntax highlighted by Code2HTML, v. 0.9.1