#!/usr/bin/perl # SPAMCHECK version 2, by RavenBlack # No warranties; "do not use this script" (that should # cover me for any sort of legal thingies.) # Also, copyright, bla bla, credit me in any derivative # works, bla bla bla. # To make this work, put it somewhere accessible, make it # executable (chmod 755), and put in your .qmail and/or # .qmail-default something akin to the following: # |/usr/bin/spamcheck.pl -p./spamphrases.txt -l./spamlog \ # -f./friends.txt -b./spambounce.txt -o./spamdubious.txt \ # -i"I am real" # The first part being pipe-symbol followed by the location # of the script. The second part (-p) being the location of your # file of spam-phrases to filter (./ will be your home dir) # and the -l[filename] parameter is the file you want to log # blocked spam to. You can omit this if you want no log. # -f[filename] indicates a file containing a list of substrings # that you would like to always allow mail from (appearing in # either the 'from' or the 'reply to' line). # -b[filename] is the file to be sent to the address of an # uncertain source, which should direct them to send a message # with "I am real" in the subject line - if this is omitted, # a default message will be sent. # -i[text] may be used to replace the phrase "I am real" with # another phrase. # -o[filename] is the file which will store the addresses to # which the spambounce message has already been sent, to prevent # the same address receiving it more than three times (to prevent # infinite loops with auto-responders). # I expect it's also possible to use for procmail, but I # don't do procmail. my @phrases; my @friends; my $file; my $origfile; our $logfile='spamlog'; my $friendsfile='.spamallow'; my $phrasesfile='.spamphrases'; my $bouncefile='.spambounce'; my $dubiousfile='.spamdubious'; my $allowphrase='I am real'; my $bouncereturnpath=''; sub DoLog { my ($txt)=@_; if (open(HANDLE,">>$logfile")) { print HANDLE localtime().": $txt\n"; close(HANDLE); } } foreach $file (@ARGV) { if ($file=~/-l(.*)/) { $logfile=$1; } elsif ($file=~/-f(.*)/) { $friendsfile=$1; #if (open(HANDLE,$1)) { # push(@friends,); # close(HANDLE); #} } elsif ($file=~/-b(.*)/) { $bouncefile=$1; } elsif ($file=~/-p(.*)/) { $phrasesfile=$1; } elsif ($file=~/-i(.*)/) { $allowphrase=$1; } elsif ($file=~/-o(.*)/) { $dubiousfile=$1; } elsif ($file=~/-r(.*)/) { $bouncereturnpath=$1; } } { my $oldsplit=$/; undef $/; $origfile=; $/=$oldsplit; } $file=lc($origfile); my $from=''; my $reply=''; my $return=''; if ($file=~/from:\s*(.*?)\s*[\r\n]/si) { $from=$1; } if ($file=~/[\r\n]reply-to:\s*(.*?)\s*[\r\n]/si) { $reply=$1; } if ($file=~/return-path:\s*(.*?)\s*[\r\n]/si) { $return=$1; } my $fromandreply=''; if (length($from)) {$fromandreply=$from;} if (length($reply)) {$fromandreply.=' / '.$reply;} if (length($return)) {$fromandreply.=' / '.$return;} else {$return=$from;} if (open(HANDLE,$friendsfile)) { push (@friends,); close(HANDLE); @friends=grep {$_=~s/\s*$//s; $_=lc($_); length($_)} @friends; if (grep {index($fromandreply,$_)!=-1;} @friends) { # allow from a listed friend if (index($fromandreply,'mailer-daemon')!=-1) { if ($file=~/User-Agent: spamcheck/si) { if ($file=~/I tried to deliver a bounce message to this address, but the bounce bounced!\s+(\S+)/si or $file=~/The following addresses had permanent fatal errors -----\s+(\S+)/si or $file=~/I'm afraid I wasn't able to deliver your message to the following addresses.\s+This is a permanent error; I've given up. Sorry it didn't work out.\s+(\S+)/si or $file=~/The Postfix program\s+(\S+)/si or $file=~/Undeliverable address: (\S+)/si or $file=~/These recipients of your message have been processed by the mail server:\s+(\S+)/si) { &DoLog("Failed bounce to $1."); exit 99; } } } exit 0; } } if ($file=~/subject:(.*?)[\r\n]/si) { if ($1=~/$allowphrase/i) { if (open(HANDLE,">>$friendsfile")) { if ($from=~/<(.*)>/) {$from=$1;} print HANDLE "$from\n"; exit 0; } } } if (open(HANDLE,$phrasesfile)) { push (@phrases,); close(HANDLE); @phrases=grep {$_=~s/\s*$//s; $_=lc($_); length($_)} @phrases; my $startforeign=chr(128); my $endforeign=chr(255); if (($file=~/[$startforeign-$endforeign]{3}/ and ($find[0]='foreign-charset')) or @find=(grep {index($file,$_)!=-1;} @phrases)) { #spam phrase found if (open(HANDLE,$dubiousfile)) { my $count=0; my $countlines=0; while () { if ($_ eq $return."\n") {$count++;} $countlines++; } close(HANDLE); $file=~/[\r\n]to:\s*(.*?)\s*[\r\n]/i; my $to=$1; my $reason; if ($to=~/<(.*)>/) {$to=$1;} if (($count>2 and ($reason='bounce-loop')) or (length($to)>0 and index($return,$to)!=-1 and ($reason='to-from')) or (index($return,'postmaster')!=-1 and ($reason='bounce-postmaster')) or (index($return,'mailer-daemon')!=-1 and ($reason='bounce-mailer-d'))) { &DoLog("Silent-blocked a message from $fromandreply. ($find[0] / $reason)"); exit 99; #silently drop the message } } my $openmethod='>>'; if ($countlines>25) {$openmethod='>';} #clear dubious file if it's long if (open(HANDLE,$openmethod.$dubiousfile)) { print HANDLE $return."\n"; close(HANDLE); } my $bouncetext; if (open(HANDLE,$bouncefile)) { my $olddiv=$/; undef $/; $bouncetext=; $/=$olddiv; close(HANDLE); } else { $bouncetext=<\n"; print MAIL "From: MAILER-DAEMON\@$host"; #newline from hostname print MAIL "To: $return\n"; print MAIL "Subject: Spam filter\n"; print MAIL "Reply-To: $bouncereturnpath\n"; print MAIL "User-Agent: spamcheck.pl\n"; print MAIL "\n"; print MAIL $bouncetext." \n"; print MAIL "--- Below this line is a copy of the message.\n\n"; print MAIL $origfile."\n"; close(MAIL); exit 99; } else { print STDERR $bouncetext; exit 100; } } } exit 0;