#!/usr/bin/perl # $Id: dspam.cgi,v 1.23 2006/05/13 01:13:01 jonz Exp $ # DSPAM # COPYRIGHT (C) 2002-2006 JONATHAN A. ZDZIARSKI # # 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; version 2 # of the License. # # 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. use strict; use Time::Local; use vars qw { %CONFIG %DATA %FORM $MAILBOX $CURRENT_USER $USER $TMPFILE}; use vars qw { $CURRENT_STORE }; require "ctime.pl"; # Read configuration parameters common to all CGI scripts require "configure.pl"; if($CONFIG{"DATE_FORMAT"}) { use POSIX qw(strftime); } # # The current CGI script # $CONFIG{'ME'} = "dspam.cgi"; $| = 1; # # Determine which extensions are available # if ($CONFIG{'AUTODETECT'} == 1 || $CONFIG{'AUTODETECT'} eq "") { $CONFIG{'PREFERENCES_EXTENSION'} = 0; $CONFIG{'LARGE_SCALE'} = 0; $CONFIG{'DOMAIN_SCALE'} = 0; do { my $x = `$CONFIG{'DSPAM'} --version`; if ($x =~ /--enable-preferences-extension/) { $CONFIG{'PREFERENCES_EXTENSION'} = 1; } if ($x =~ /--enable-large-scale/) { $CONFIG{'LARGE_SCALE'} = 1; } if ($x =~ /--enable-domain-scale/) { $CONFIG{'DOMAIN_SCALE'} = 1; } }; } # # Determine admin status # $CONFIG{'ADMIN'} = 0; if ($ENV{'REMOTE_USER'} ne "") { open(FILE, "<./admins"); while() { chomp; if ($_ eq $ENV{'REMOTE_USER'}) { $CONFIG{'ADMIN'} = 1; } } close(FILE); } # # Configure Filesystem # %FORM = &ReadParse; $CURRENT_USER = $ENV{'REMOTE_USER'}; if ($FORM{'user'} ne "" && $CONFIG{'ADMIN'} == 1) { $CURRENT_USER = $FORM{'user'}; } else { $FORM{'user'} = $CURRENT_USER ; } $CONFIG{'DSPAM_ARGS'} =~ s/%CURRENT_USER%/$CURRENT_USER/g; # Current Store do { my(%PREF) = GetPrefs($CURRENT_USER); $CURRENT_STORE = $PREF{"localStore"}; if ($CURRENT_STORE eq "") { $CURRENT_STORE = $CURRENT_USER; } }; $USER = GetPath($CURRENT_STORE); $MAILBOX = $USER . ".mbox"; $TMPFILE = $USER . ".tmp"; if ($CURRENT_USER eq "") { &error("System Error. I was unable to determine your identity."); } if ($FORM{'template'} eq "" || $FORM{'template'} !~ /^([A-Z0-9]*)$/i) { $FORM{'template'} = "performance"; } my($MYURL); $MYURL = "$CONFIG{'ME'}?user=$FORM{'user'}&template=$FORM{'template'}"; # # Set up initial display variables # &CheckQuarantine; $DATA{'REMOTE_USER'} = $CURRENT_USER; # # Process Commands # # Performance if ($FORM{'template'} eq "performance") { if ($FORM{'command'} eq "resetStats") { &ResetStats; redirect($MYURL); } elsif ($FORM{'command'} eq "tweak") { &Tweak; redirect($MYURL); } else { &DisplayIndex; } # Quarantine } elsif ($FORM{'template'} eq "quarantine") { if ($FORM{'command'} eq "viewMessage") { &Quarantine_ViewMessage; } else { $MYURL .= "&sortby=$FORM{'sortby'}" if ($FORM{'sortby'} ne ""); if ($FORM{'command'} eq "processQuarantine") { &ProcessQuarantine; redirect($MYURL); } elsif ($FORM{'command'} eq "processFalsePositive") { &ProcessFalsePositive; redirect($MYURL); } else { &DisplayQuarantine; } } # Alerts } elsif ($FORM{'template'} eq "alerts") { if ($FORM{'command'} eq "addAlert") { &AddAlert; redirect($MYURL); } elsif ($FORM{'command'} eq "deleteAlert") { &DeleteAlert; redirect($MYURL); } else { &DisplayAlerts; } # Preferences } elsif ($FORM{'template'} eq "preferences") { &DisplayPreferences; # Analysis } elsif ($FORM{'template'} eq "analysis") { &DisplayAnalysis; # History } elsif ($FORM{'template'} eq "history") { &DisplayHistory; } elsif ($FORM{'template'} eq "fragment") { &DisplayFragment; } else { &error("Invalid Command $FORM{'COMMAND'}"); } # # History Functions # sub DisplayFragment { $FORM{'signatureID'} =~ s/\///g; $DATA{'FROM'} = $FORM{'from'}; $DATA{'SUBJECT'} = $FORM{'subject'}; $DATA{'INFO'} = $FORM{'info'}; $DATA{'TIME'} = $FORM{'time'}; open(FILE, "<$USER.frag/$FORM{'signatureID'}.frag") || &error($!); while() { s//>\;/g; $DATA{'MESSAGE'} .= $_; } close(FILE); &output(%DATA); } sub DisplayHistory { my($all_lines , $begin, $history_pages, $rec, $history_page); unless ($history_page = $FORM{'history_page'}) { $history_page = 1;} my(@buffer, @history, $line, %rec); my($rowclass) = "rowEven"; if ($CONFIG{'HISTORY_PER_PAGE'} == 0) { $CONFIG{'HISTORY_PER_PAGE'} = 50; } if ($FORM{'command'} eq "retrainChecked") { foreach my $i (0 .. $#{ $FORM{retrain_checked} }) { my ($retrain, $signature) = split(/:/, $FORM{retrain_checked}[$i]); if ($retrain eq "innocent") { $FORM{'signatureID'} = quotemeta($signature); &ProcessFalsePositive(); undef $FORM{'signatureID'}; } elsif ($retrain eq "innocent" or $retrain eq "spam") { system("$CONFIG{'DSPAM'} --source=error --class=" . quotemeta($retrain) . " --signature=" . quotemeta($signature) . " --user " . quotemeta("$CURRENT_USER")); } } redirect("$MYURL&history_page=$history_page"); } else { if ($FORM{'retrain'} ne "") { if ($FORM{'retrain'} eq "innocent") { &ProcessFalsePositive(); } else { system("$CONFIG{'DSPAM'} --source=error --class=" . quotemeta($FORM{'retrain'}) . " --signature=" . quotemeta($FORM{'signatureID'}) . " --user " . quotemeta("$CURRENT_USER")); } redirect("$MYURL&history_page=$history_page"); } } my($LOG) = "$USER.log"; if (! -e $LOG) { &error("No historical data is available"); } # Preseed retraining information and delivery errors open(LOG, "< $LOG") or die "Can't open log file $LOG"; while() { my($time, $class, $from, $signature, $subject, $info, $messageid) = split(/\t/, $_); next if ($signature eq ""); if ($class eq "M" || $class eq "F" || $class eq "E") { if ($class eq "E") { $rec{$signature}->{'info'} = $info; } elsif ($class eq "F" || $class eq "M") { $rec{$signature}->{'class'} = $class; $rec{$signature}->{'count'}++; $rec{$signature}->{'info'} = $info if ($rec{$signature}->{'info'} eq ""); } # filter out resents if there are any. Since it's the same # message we only allow retraining on the 1st occurence of it. } elsif ($messageid == '' || $rec{$signature}->{'messageid'} != $messageid || $CONFIG{'HISTORY_DUPLICATES'} ne "no") { $rec{$signature}->{'time'} = $time; $rec{$signature}->{'class'} = $class; $rec{$signature}->{'from'} = $from; $rec{$signature}->{'signature'} = $signature; $rec{$signature}->{'subject'} = $subject; $rec{$signature}->{'info'} = $info; $rec{$signature}->{'messageid'} = $messageid; unshift(@buffer, $rec{$signature}); } } close(LOG); if($CONFIG{'HISTORY_SIZE'} < ($#buffer+1)) { $history_pages = int($CONFIG{'HISTORY_SIZE'} / $CONFIG{'HISTORY_PER_PAGE'}); $history_pages += 1 if($CONFIG{'HISTORY_SIZE'} % $CONFIG{'HISTORY_PER_PAGE'}); } else { $history_pages = int( ($#buffer+1) / $CONFIG{'HISTORY_PER_PAGE'}); $history_pages += 1 if(($#buffer+1) % $CONFIG{'HISTORY_PER_PAGE'}); } $begin = int(($history_page - 1) * $CONFIG{'HISTORY_PER_PAGE'}) ; # Now lets just keep the information that we really need. @buffer = splice(@buffer, $begin,$CONFIG{'HISTORY_PER_PAGE'}); my $retrain_checked_msg_no = 0; while ($rec = pop@buffer) { my($time, $class, $from, $signature, $subject, $info, $messageid); $time = $rec->{'time'}; $class = $rec->{'class'}; $from = $rec->{'from'}; $signature = $rec->{'signature'}; $subject = $rec->{'subject'}; $info = $rec->{'info'}; $messageid = $rec->{'messageid'}; next if ($signature eq ""); next if ($rec{$signature}->{'displayed'} ne ""); next if ($class eq "E"); $rec{$signature}->{'displayed'} = 1; # Resends of retrained messages will need the original from/subject line if ($messageid ne "") { $from = $rec{$messageid}->{'from'} if ($from eq ""); $subject = $rec{$messageid}->{'subject'} if ($subject eq ""); $rec{$messageid}->{'from'} = $from if ($rec{$messageid}->{'from'} eq ""); $rec{$messageid}->{'subject'} = $subject if ($rec{$messageid}->{'subject'} eq ""); } $from = "" if ($from eq ""); $subject = "" if ($subject eq ""); my $ctime; if($CONFIG{"DATE_FORMAT"}) { $ctime = strftime($CONFIG{"DATE_FORMAT"}, localtime($time)); } else { $ctime = ctime($time); my(@t) = split(/\:/, (split(/\s+/, $ctime))[3]); my($x) = (split(/\s+/, $ctime))[0]; my($m) = "a"; if ($t[0]>12) { $t[0] -= 12; $m = "p"; } if ($t[0] == 0) { $t[0] = 12; } $ctime = "$x $t[0]:$t[1]$m"; } # Set the appropriate type and label for this message my($cl, $cllabel); $class = $rec{$signature}->{'class'} if ($rec{$signature}->{'class'} ne ""); if ($class eq "S") { $cl = "spam"; $cllabel="SPAM"; } elsif ($class eq "I") { $cl = "innocent"; $cllabel="Good"; } elsif ($class eq "F") { if ($rec{$signature}->{'count'} % 2 != 0) { $cl = "false"; $cllabel="Miss"; } else { $cl = "innocent"; $cllabel="Good"; } } elsif ($class eq "M") { if ($rec{$signature}->{'count'} % 2 != 0) { $cl = "missed"; $cllabel="Miss"; } else { $cl = "spam"; $cllabel="SPAM"; } } elsif ($class eq "N") { $cl = "inoculation"; $cllabel="Spam"; } elsif ($class eq "C") { $cl = "blacklisted"; $cllabel="RBL"; } elsif ($class eq "W") { $cl = "whitelisted"; $cllabel="Whitelist"; } if ($messageid ne "") { if ($rec{$messageid}->{'resend'} ne "") { $cl = "relay"; $cllabel = "Resend"; } $rec{$messageid}->{'resend'} = $signature; } $info = $rec{$signature}->{'info'} if ($rec{$signature}->{'info'} ne ""); $from =~ s//>/g; $subject =~ s//>/g; $from = substr($from, 0, $CONFIG{'MAX_COL_LEN'}) . "..." if (length($from)>$CONFIG{'MAX_COL_LEN'}); $subject = substr($subject, 0, $CONFIG{'MAX_COL_LEN'}) . "..." if (length($subject)>$CONFIG{'MAX_COL_LEN'}); my($rclass); $rclass = "spam" if ($class eq "I" || $class eq "W" || $class eq "F"); $rclass = "innocent" if ($class eq "S" || $class eq "M"); my($retrain); if ($rec{$signature}->{'class'} =~ /^(M|F)$/ && $rec{$signature}->{'count'} % 2 != 0) { $retrain = "Retrained"; } if ($retrain eq "") { $retrain = qq!As ! . ucfirst($rclass) . ""; } else { $retrain .= qq! (Undo)!; } my($path) = "$USER.frag/$signature.frag"; if (-e $path) { my(%pairs); $pairs{'template'} = "fragment"; $pairs{'signatureID'} = $signature; my($sub) = $subject; $sub =~ s/#//g; $sub =~ s/(['])/\\$1/g; $pairs{'subject'} = $sub; $pairs{'from'} = $from; $pairs{'info'} = $info; $pairs{'time'} = $ctime; $pairs{'user'} = $FORM{'user'}; my($url) = &SafeVars(%pairs); $from = qq!$from!; } my($entry) = <<_END; $cllabel $retrain $ctime $from $subject $info _END $retrain_checked_msg_no++; push(@history, $entry); if ($rowclass eq "rowEven") { $rowclass = "rowOdd"; } else { $rowclass = "rowEven"; } } my $entry = <<_END; _END push(@history, $entry); while($line = pop(@history)) { $DATA{'HISTORY'} .= $line; } if ($CONFIG{'HISTORY_PER_PAGE'} > 0) { $DATA{'HISTORY'} .= "
["; if (($history_pages > 1) && ($history_page > 1)) { my $i = $history_page-1; $DATA{'HISTORY'} .= " < "; } for(my $i = 1; $i <= $history_pages; $i++) { if ($i == $history_page) { $DATA{'HISTORY'} .= " $i "; } else { $DATA{'HISTORY'} .= " $i "; } } if (($history_pages > 1) && ($history_page < $history_pages)) { my $i = $history_page+1; $DATA{'HISTORY'} .= " > "; } $DATA{'HISTORY'} .= "]

"; } &output(%DATA); } # # Analysis Functions # sub DisplayAnalysis { my($LOG) = "$USER.log"; my %Stats=( daily => {}, weekly => {}, ); my ($min, $hour, $mday, $mon, $year) = (localtime(time))[1,2,3,4,5]; my ($daystart) = timelocal(0, 0, 0, $mday, $mon, $year); my ($periodstart) = $daystart - (3600*24*13); # 2 Weeks ago my ($dailystart) = time - (3600*23); if (! -e $LOG) { &error("No historical data is available."); } open(LOG, "<$LOG") || &error("Unable to open logfile: $!"); while() { my($t_log, $c_log) = split(/\t/); # Only Parse Log Data in our Time Period if ($t_log>=$periodstart) { my($tmin, $thour, $tday, $tmon) = (localtime($t_log))[1,2,3,4]; $tmon++; foreach my $period (qw( weekly daily )) { my $idx; if ($period eq "weekly") { $idx="$tmon/$tday"; } else { ($t_log>=$dailystart) || next; $idx=To12Hour($thour); } if (!exists $Stats{$period}->{$idx}) { $Stats{$period}->{$idx}={ nonspam => 0, spam => 0, title => $idx, idx => $t_log, }; } my $hr=$Stats{$period}->{$idx}; if ($c_log eq "S") { $hr->{spam}++; } if ($c_log eq "I" || $c_log eq "W") { $hr->{nonspam}++; } if ($c_log eq "F") { $hr->{spam}--; ($hr->{spam}<0) && ($hr->{spam}=0); $hr->{nonspam}++; } if ($c_log eq "M") { $hr->{nonspam}--; ($hr->{nonspam}<0) && ($hr->{nonspam}=0); $hr->{spam}++; } } } } close(LOG); foreach my $period (qw( daily weekly )) { my $uc_period=uc($period); my $hk="DATA_$uc_period"; my %lst=(); foreach my $hr (sort {$a->{idx}<=>$b->{idx}} (values %{$Stats{$period}})) { foreach my $type (qw( spam nonspam title )) { (exists $lst{$type}) || ($lst{$type}=[]); push(@{$lst{$type}},$hr->{$type}); my $totk=""; if ($type eq "spam") { $totk="S"; } elsif ($type eq "nonspam") { $totk="I"; } ($totk eq "") && next; my $sk="T${totk}_$uc_period"; (exists $DATA{$sk}) || ($DATA{$sk}=0); $DATA{$sk}+=$hr->{$type}; } } $DATA{$hk}=join("_", join(",",@{$lst{spam}}), join(",",@{$lst{nonspam}}), join(",",@{$lst{title}}), ); } &output(%DATA); } # # Preferences Functions # sub DisplayPreferences { my(%PREFS); my($FILE) = "$USER.prefs"; my $username = $CURRENT_USER; if ($FORM{'submit'} ne "") { if ($FORM{'enableBNR'} ne "on") { $FORM{'enableBNR'} = "off"; } if ($FORM{'optIn'} ne "on") { $FORM{'optIn'} = "off"; } if ($FORM{'optOut'} ne "on") { $FORM{'optOut'} = "off"; } if ($FORM{'showFactors'} ne "on") { $FORM{'showFactors'} = "off"; } if ($FORM{'enableWhitelist'} ne "on") { $FORM{'enableWhitelist'} = "off"; } if ($CONFIG{'PREFERENCES_EXTENSION'} == 1) { if ($FORM{'spamSubject'} eq "") { $FORM{'spamSubject'} = "''"; } else { $FORM{'spamSubject'} = quotemeta($FORM{'spamSubject'}); } system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " trainingMode " . quotemeta($FORM{'trainingMode'}) . " > /dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " spamAction " . quotemeta($FORM{'spamAction'}) . " > /dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " signatureLocation " . quotemeta($FORM{'signatureLocation'}) . " > /dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " spamSubject " . $FORM{'spamSubject'} . " > /dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " statisticalSedation " . quotemeta($FORM{'statisticalSedation'}) . " > /dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " enableBNR " . quotemeta($FORM{'enableBNR'}) . "> /dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " optOut " . quotemeta($FORM{'optOut'}) . ">/dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " optIn " . quotemeta($FORM{'optIn'}) . ">/dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " showFactors " . quotemeta($FORM{'showFactors'}) . "> /dev/null"); system("$CONFIG{'DSPAM_BIN'}/dspam_admin ch pref ".quotemeta($CURRENT_USER). " enableWhitelist " . quotemeta($FORM{'enableWhitelist'}) . "> /dev/null"); } else { open(FILE, ">$FILE") || do { &error("Unable to write preferences: $!"); }; print FILE <<_END; trainingMode=$FORM{'trainingMode'} spamAction=$FORM{'spamAction'} spamSubject=$FORM{'spamSubject'} statisticalSedation=$FORM{'statisticalSedation'} enableBNR=$FORM{'enableBNR'} optIn=$FORM{'optIn'} optOut=$FORM{'optOut'} showFactors=$FORM{'showFactors'} enableWhitelist=$FORM{'enableWhitelist'} signatureLocation=$FORM{'signatureLocation'} _END close(FILE); } redirect("$CONFIG{'ME'}?user=$FORM{'user'}&template=preferences"); } %PREFS = GetPrefs(); $DATA{"SEDATION_$PREFS{'statisticalSedation'}"} = "CHECKED"; $DATA{"S_".$PREFS{'trainingMode'}} = "CHECKED"; $DATA{"S_ACTION_".uc($PREFS{'spamAction'})} = "CHECKED"; $DATA{"S_LOC_".uc($PREFS{'signatureLocation'})} = "CHECKED"; $DATA{"SPAM_SUBJECT"} = $PREFS{'spamSubject'}; if ($PREFS{'optIn'} eq "on") { $DATA{'C_OPTIN'} = "CHECKED"; } if ($PREFS{'optOut'} eq "on") { $DATA{'C_OPTOUT'} = "CHECKED"; } if ($PREFS{"enableBNR"} eq "on") { $DATA{"C_BNR"} = "CHECKED"; } if ($PREFS{"showFactors"} eq "on") { $DATA{"C_FACTORS"} = "CHECKED"; } if ($PREFS{"enableWhitelist"} eq "on") { $DATA{"C_WHITELIST"} = "CHECKED"; } if ($CONFIG{'OPTMODE'} eq "OUT") { $DATA{"OPTION"} = "Disable DSPAM filtering
"; } elsif ($CONFIG{'OPTMODE'} eq "IN") { $DATA{"OPTION"} = "Enable DSPAM filtering
"; } else { $DATA{"OPTION"} = ""; } &output(%DATA); } # # Quarantine Functions # sub ProcessQuarantine { if ($FORM{'manyNotSpam'} ne "") { &Quarantine_ManyNotSpam; } else { &Quarantine_DeleteSpam; } &CheckQuarantine; return; } sub ProcessFalsePositive { my(@buffer, %head, $found); if ($FORM{'signatureID'} eq "") { &error("No Message ID Specified"); } open(FILE, "<$MAILBOX"); while() { chomp; push(@buffer, $_); } close(FILE); while($#buffer>=0) { my($buff, $mode, @temp); $mode = 0; @temp = (); while(($buff !~ /^From /) && ($#buffer>=0)) { $buff = $buffer[0]; if ($buff =~ /^From /) { if ($mode == 0) { $mode = 1; } else { next; } } $buff = shift(@buffer); if ($buff !~ /^From /) { push(@temp, $buff); } next; } foreach(@temp) { last if ($_ eq ""); my($key, $val) = split(/\: ?/, $_, 2); $head{$key} = $val; } if ($head{'X-DSPAM-Signature'} eq $FORM{'signatureID'}) { $found = 1; open(PIPE, "|$CONFIG{'DSPAM'} $CONFIG{'DSPAM_ARGS'} >$TMPFILE 2>&1") || &error($!); foreach(@temp) { print PIPE "$_\n"; } close(PIPE); } } # Couldn't find the message, so just retrain on signature if (!$found) { system("$CONFIG{'DSPAM'} --source=error --class=innocent --signature=" . quotemeta($FORM{'signatureID'}) . " --user " . quotemeta("$CURRENT_USER")); } if ($?) { my(@log); open(LOG, "<$TMPFILE"); @log = ; close(LOG); unlink("$TMPFILE"); &error("
".join('', @log)."
"); } unlink("$TMPFILE"); $FORM{$FORM{'signatureID'}} = "on"; &Quarantine_DeleteSpam(); return; } sub Quarantine_ManyNotSpam { my(@buffer, @errors); open(FILE, "<$MAILBOX"); while() { chomp; push(@buffer, $_); } close(FILE); open(FILE, ">$MAILBOX") || &error($!); open(RETRAIN, ">>$USER.retrain.log"); while($#buffer>=0) { my($buff, $mode, @temp, %head, $delivered); $mode = 0; while(($buff !~ /^From /) && ($#buffer>=0)) { $buff = $buffer[0]; if ($buff =~ /^From /) { if ($mode == 0) { $mode = 1; $buff = shift(@buffer); push(@temp, $buff); $buff = ""; next; } else { next; } } $buff = shift(@buffer); push(@temp, $buff); next; } foreach(@temp) { last if ($_ eq ""); my($key, $val) = split(/\: ?/, $_, 2); $head{$key} = $val; } $delivered = 0; if ($FORM{$head{'X-DSPAM-Signature'}} ne "") { my($err); $err = &Deliver(@temp); if ($err eq "") { $delivered = 1; } else { push(@errors, $err); } } if (!$delivered) { foreach(@temp) { print FILE "$_\n"; } } else { print RETRAIN time . "\t$head{'X-DSPAM-Signature'}\tinnocent\n"; } } close(RETRAIN); close(FILE); if (@errors > 0) { &error(join("
", @errors)); } return; } sub Deliver { my(@temp) = @_; open(PIPE, "|$CONFIG{'DSPAM'} $CONFIG{'DSPAM_ARGS'}") || return $!; foreach(@temp) { print PIPE "$_\n" || return $!; } close(PIPE) || return $!; return ""; } sub Quarantine_ViewMessage { my(@buffer); if ($FORM{'signatureID'} eq "") { &error("No Message ID Specified"); } $DATA{'MESSAGE_ID'} = $FORM{'signatureID'}; open(FILE, "<$MAILBOX"); while() { chomp; push(@buffer, $_); } close(FILE); while($#buffer>=0) { my($buff, $mode, @temp, %head); $mode = 0; @temp = (); while(($buff !~ /^From /) && ($#buffer>=0)) { $buff = $buffer[0]; if ($buff =~ /^From /) { if ($mode == 0) { $mode = 1; } else { next; } } $buff = shift(@buffer); if ($buff !~ /^From /) { push(@temp, $buff); } next; } foreach(@temp) { last if ($_ eq ""); my($key, $val) = split(/\: ?/, $_, 2); $head{$key} = $val; } if ($head{'X-DSPAM-Signature'} eq $FORM{'signatureID'}) { foreach(@temp) { s//\>\;/g; $DATA{'MESSAGE'} .= "$_\n"; } } } $FORM{'template'} = "viewmessage"; &output(%DATA); } sub Quarantine_DeleteSpam { my(@buffer); if ($FORM{'deleteAll'} ne "") { my($sz); my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat("$USER.mbox"); open(FILE, "<$USER.mbox.size"); $sz = ; close(FILE); chomp($sz); if ($sz == $size) { open(FILE, ">$MAILBOX"); close(FILE); unlink("$USER.mbox.size"); unlink("$USER.mboxwarn"); } else { return &DisplayQuarantine; } $FORM{'template'} = "performance"; &CheckQuarantine; return &DisplayIndex; } open(FILE, "<$MAILBOX"); while() { chomp; push(@buffer, $_); } close(FILE); open(FILE, ">$MAILBOX"); while($#buffer>=0) { my($buff, $mode, @temp, %head); $mode = 0; while(($buff !~ /^From /) && ($#buffer>=0)) { $buff = $buffer[0]; if ($buff =~ /^From /) { if ($mode == 0) { $mode = 1; $buff = shift(@buffer); push(@temp, $buff); $buff = ""; next; } else { next; } } $buff = shift(@buffer); push(@temp, $buff); next; } foreach(@temp) { last if ($_ eq ""); my($key, $val) = split(/\: ?/, $_, 2); $head{$key} = $val; } if ($FORM{$head{'X-DSPAM-Signature'}} eq "") { foreach(@temp) { print FILE "$_\n"; } } } close(FILE); return; } sub by_rating { $a->{'rating'} <=> $b->{'rating'} } sub by_subject { lc($a->{'Subject'}) cmp lc($b->{'Subject'}) } sub by_from { lc($a->{'From'}) cmp lc($b->{'From'}) } sub DisplayQuarantine { my(@alerts); my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat("$USER.mbox"); open(FILE, ">$USER.mbox.size"); print(FILE "$size"); close(FILE); open(FILE, ">$MAILBOX.stamp"); close(FILE); chmod 0660, "$MAILBOX.stamp"; open(FILE, "<$USER.alerts"); while() { chomp; push(@alerts, $_); } close(FILE); my($next, @buffer, $rowclass, $mode, $markclass, $marklabel, @headings); $rowclass="rowEven"; open(FILE, "<$MAILBOX"); while() { chomp; if ($_ ne "") { if ($mode eq "") { if ($_ =~ /^From /) { $mode = 1; } else { next; } } push(@buffer, $_); next; } next if ($mode eq ""); my($new, $start, $alert); $alert = 0; $new = {}; foreach(@buffer) { my($al); foreach $al (@alerts) { if (/$al/i) { $alert = 1; } } if ($_ =~ /^From /) { my(@a) = split(/ /, $_); my($x) = 2; for (0..$#a) { if (($a[$_] =~ /\@|>/) && ($_>$x)) { $x = $_ + 1; } } for(1..$x) { shift(@a); } $start = join(" ", @a); } else { my($key, $val) = split(/\: ?/, $_, 2); $new->{$key} = $val; } } if ($rowclass eq "rowEven") { $rowclass = "rowOdd"; } else { $rowclass = "rowEven"; } $new->{'alert'} = $alert; if ($alert) { $rowclass="rowAlert"; } $new->{'Sub2'} = $new->{'X-DSPAM-Signature'}; if (length($new->{'Subject'})>$CONFIG{'MAX_COL_LEN'}) { $new->{'Subject'} = substr($new->{'Subject'}, 0, $CONFIG{'MAX_COL_LEN'}) . "..."; } if (length($new->{'From'})>$CONFIG{'MAX_COL_LEN'}) { $new->{'From'} = substr($new->{'From'}, 0, $CONFIG{'MAX_COL_LEN'}) . "..."; } if ($new->{'Subject'} eq "") { $new->{'Subject'} = ""; } # $new->{'rating'} = $new->{'X-DSPAM-Probability'} * $new->{'X-DSPAM-Confidence'}; $new->{'rating'} = $new->{'X-DSPAM-Confidence'}; foreach(keys(%$new)) { next if ($_ eq "X-DSPAM-Signature"); $new->{$_} =~ s/{$_} =~ s/>/\>\;/g; } push(@headings, $new); @buffer = (); $mode = ""; next; } my($sortBy) = $FORM{'sortby'}; if ($sortBy eq "") { $sortBy = $CONFIG{'SORT_DEFAULT'}; } if ($sortBy eq "Rating") { @headings = sort by_rating @headings; } if ($sortBy eq "Subject") { @headings = sort by_subject @headings; } if ($sortBy eq "From") { @headings = sort by_from @headings; } if ($sortBy eq "Date") { @headings = reverse @headings; } $DATA{'SORTBY'} = $sortBy; $DATA{'SORT_SELECTOR'} .= "Sort by: "; if ($sortBy eq "Rating") { $DATA{'SORT_SELECTOR'} .= "Rating"; } else { $DATA{'SORT_SELECTOR'} .= "Rating"; } $DATA{'SORT_SELECTOR'} .= " | "; if ($sortBy eq "Date") { $DATA{'SORT_SELECTOR'} .= "Date"; } else { $DATA{'SORT_SELECTOR'} .= "Date"; } $DATA{'SORT_SELECTOR'} .= " | "; if ($sortBy eq "Subject") { $DATA{'SORT_SELECTOR'} .= "Subject"; } else { $DATA{'SORT_SELECTOR'} .= "Subject"; } $DATA{'SORT_SELECTOR'} .= " | "; if ($sortBy eq "From") { $DATA{'SORT_SELECTOR'} .= "From"; } else { $DATA{'SORT_SELECTOR'} .= "From"; } $DATA{'SORT_SELECTOR'} .= ""; my($row, $rowclass); $rowclass = "rowEven"; for $row (@headings) { my($rating, $url, $markclass, $outclass); $rating = sprintf("%3.0f%%", $row->{'rating'} * 100.0); if ($row->{'rating'} > 0.8) { $markclass = "high"; } else { if ($row->{'rating'} < 0.7) { $markclass = "low"; } else { $markclass = "medium"; } } my(%PAIRS); $PAIRS{'signatureID'} = $row->{'X-DSPAM-Signature'}; $PAIRS{'command'} = "viewMessage"; $PAIRS{'user'} = $CURRENT_USER; $PAIRS{'template'} = "quarantine"; $url = &SafeVars(%PAIRS); if ($row->{'alert'}) { $outclass = "rowAlert"; } else { $outclass = $rowclass; } my(@ptfields) = split(/\s+/, $row->{'X-DSPAM-Processed'}); my(@times) = split(/\:/, $ptfields[3]); my($ptime); if($CONFIG{"DATE_FORMAT"}) { my($month); $month->{'Jan'}=0; $month->{'Feb'}=1; $month->{'Mar'}=2; $month->{'Apr'}=3; $month->{'May'}=4; $month->{'Jun'}=5; $month->{'Jul'}=6; $month->{'Aug'}=7; $month->{'Sep'}=8; $month->{'Oct'}=9; $month->{'Nov'}=10; $month->{'Dec'}=11; $ptime = strftime($CONFIG{"DATE_FORMAT"}, $times[2],$times[1],$times[0],$ptfields[2],$month->{$ptfields[1]},$ptfields[4]-1900); } else { my($mer) = "a"; if ($times[0] > 12) { $times[0] -= 12; $mer = "p"; } if ($times[0] == 0) { $times[0] = "12"; } $ptime = "$ptfields[1] $ptfields[2] $times[0]:$times[1]$mer"; } $DATA{'QUARANTINE'} .= <<_END; $rating $ptime $row->{'From'} $row->{'Subject'} _END if ($rowclass eq "rowEven") { $rowclass = "rowOdd"; } else { $rowclass = "rowEven"; } } &output(%DATA); } # # Performance Functions # sub ResetStats { my($ts, $ti, $tm, $fp, $sc, $ic); my($group); open(FILE, "<$USER.stats"); chomp($ts = ); chomp($group = ); close(FILE); ($ts, $ti, $tm, $fp, $sc, $ic) = split(/\,/, $ts); if ($group ne "") { my($GROUP) = GetPath($group) . ".stats"; my($gts, $gti, $gtm, $gfp, $gsc, $gic); open(FILE, "<$GROUP"); chomp($gts = ); close(FILE); ($gts, $gti, $gtm, $gfp, $gsc, $gic) = split(/\,/, $gts); $ts -= $gts; $ti -= $gti; $tm -= $gtm; $fp -= $gfp; $sc -= $gsc; $ic -= $gic; } open(FILE, ">$USER.rstats"); print FILE "$ts" . "," . "$ti" . "," . "$tm" . "," . "$fp" . "," . "$sc" . "," . "$ic\n"; close(FILE); } sub Tweak { my($ts, $ti, $tm, $fp, $sc, $ic); open(FILE, "<$USER.rstats"); chomp($ts = ); close(FILE); ($ts, $ti, $tm, $fp, $sc, $ic) = split(/\,/, $ts); $tm++; open(FILE, ">$USER.rstats"); print FILE "$ts,$ti,$tm,$fp,$sc,$ic\n"; close(FILE); } sub DisplayIndex { my($spam, $innocent, $ratio, $fp, $misses); my($rts, $rti, $rtm, $rfp, $sc, $ic, $overall, $fpratio, $monthly, $real_missed, $real_caught, $real_fp, $real_innocent); my($time) = ctime(time); my($group); open(FILE, "<$USER.stats"); chomp($spam = ); chomp($group = ); close(FILE); ($spam, $innocent, $misses, $fp, $sc, $ic) = split(/\,/, $spam); if ($group ne "") { my($GROUP) = GetPath($group) . ".stats"; my($gspam, $ginnocent, $gmisses, $gfp, $gsc, $gic); open(FILE, "<$GROUP"); chomp($gspam = ); close(FILE); ($gspam, $ginnocent, $gfp, $gmisses, $gsc, $gic) = split(/\,/, $gspam); $spam -= $gspam; $innocent -= $ginnocent; $misses -= $gmisses; $fp -= $gfp; $sc -= $gsc; $ic -= $gic; } if ($spam+$innocent>0) { $ratio = sprintf("%2.3f", (($spam+$misses)/($spam+$misses+$fp+$innocent)*100)); } else { $ratio = 0; } if (open(FILE, "<$USER.rstats")) { my($rstats); chomp($rstats = ); ($rts, $rti, $rtm, $rfp) = split(/\,/, $rstats); close(FILE); $real_missed = $misses-$rtm; $real_caught = $spam-$rts; $real_fp = $fp-$rfp; if ($real_fp < 0) { $real_fp = 0; } $real_innocent = $innocent-$rti; if (($spam-$rts > 0) && ($spam-$rts + $misses-$rtm != 0) && ($real_caught+$real_missed>0) && ($real_fp+$real_innocent>0)) { $monthly = sprintf("%2.3f", (100.0-(($real_missed)/($real_caught+$real_missed))*100.0)); $overall = sprintf("%2.3f", (100-((($real_missed+$real_fp) / ($real_fp+$real_innocent+$real_caught+$real_missed))*100))); } else { if ($real_caught == 0 && $real_missed > 0) { $monthly = 0; $overall = 0; } else { $monthly = 100; $overall = 100; } } if ($real_fp+$real_innocent>0) { $fpratio = sprintf("%2.3f", ($real_fp/($real_fp+$real_innocent)*100)); } else { $fpratio = 0; } } else { $rts = $spam+$misses; $rti = $innocent; $rtm = $misses; $rfp = $fp; open(FILE, ">$USER.rstats"); print FILE "$rts,$rti,$rtm,$rfp\n"; close(FILE); $monthly = "N/A"; $fpratio = "N/A"; $overall = "N/A"; } $DATA{'TIME'} = $time; $DATA{'TOTAL_SPAM_SCANNED'} = $spam; $DATA{'TOTAL_SPAM_LEARNED'} = $misses; $DATA{'TOTAL_NONSPAM_SCANNED'} = $innocent; $DATA{'TOTAL_NONSPAM_LEARNED'} = $fp; $DATA{'SPAM_RATIO'} = $ratio; $DATA{'SPAM_ACCURACY'} = $monthly; $DATA{'NONSPAM_ERROR_RATE'} = $fpratio; $DATA{'OVERALL_ACCURACY'} = $overall; $DATA{'TOTAL_SPAM_CORPUSFED'} = $sc; $DATA{'TOTAL_NONSPAM_CORPUSFED'} = $ic; $DATA{'TOTAL_SPAM_MISSED'} = $real_missed; $DATA{'TOTAL_SPAM_CAUGHT'} = $real_caught; $DATA{'TOTAL_NONSPAM_MISSED'} = $real_fp; $DATA{'TOTAL_NONSPAM_CAUGHT'} = $real_innocent; $DATA{'LOCAL_DOMAIN'} = $CONFIG{'LOCAL_DOMAIN'}; &output(%DATA); } # # Alert Functions # sub AddAlert { if ($FORM{'ALERT'} eq "") { &error("No Alert Specified"); } open(FILE, ">>$USER.alerts"); print FILE "$FORM{'ALERT'}\n"; close(FILE); return; } sub DeleteAlert { my($line, @alerts); $line = 0; if ($FORM{'line'} eq "") { &Error("No Alert Specified"); } open(FILE, "<$USER.alerts"); while() { if ($line != $FORM{'line'}) { push(@alerts, $_); } $line++; } close(FILE); open(FILE, ">$USER.alerts"); foreach(@alerts) { print FILE $_; } close(FILE); return; } sub DisplayAlerts { my($supp); $DATA{'ALERTS'} = <<_end; _end my($rowclass); $rowclass = "rowEven"; do { my($line) = 0; open(FILE, "<$USER.alerts"); while() { s//>/g; $DATA{'ALERTS'} .= qq!\n!; $line++; if ($rowclass eq "rowEven") { $rowclass = "rowOdd"; } else { $rowclass = "rowEven"; } } }; $DATA{'ALERTS'} .= <<_end;
Alert Name  
$_[Remove]
_end &output(%DATA); exit; } # # Global Functions # sub redirect { my($loc) = @_; print "Expires: now\n"; print "Pragma: no-cache\n"; print "Cache-control: no-cache\n"; print "Location: $loc\n\n"; exit(0); } sub output { if ($FORM{'template'} eq "" || $FORM{'template'} !~ /^([A-Z0-9]*)$/i) { $FORM{'template'} = "performance"; } print "Expires: now\n"; print "Pragma: no-cache\n"; print "Cache-control: no-cache\n"; print "Content-type: text/html\n\n"; my(%DATA) = @_; $DATA{'WEB_ROOT'} = $CONFIG{'WEB_ROOT'}; # Check admin permissions do { if ($CONFIG{'ADMIN'} == 1) { $DATA{'NAV_ADMIN'} = qq!
  • Administrative Suite
  • !; $DATA{'FORM_USER'} = qq!
    Statistical SPAM Protection for
    !; } else { $DATA{'NAV_ADMIN'} = ''; $DATA{'FORM_USER'} = "Statistical SPAM Protection for $CURRENT_USER"; } }; open(FILE, "<$CONFIG{'TEMPLATES'}/nav_$FORM{'template'}.html"); while() { s/\$CGI\$/$CONFIG{'ME'}/g; if ($CONFIG{'ADMIN'} == 1 && $FORM{'user'}) { s/\$USER\$/user=$FORM{'user'}&/g; } else { s/\$USER\$//g; } s/\$([A-Z0-9_]*)\$/$DATA{$1}/g; print; } close(FILE); exit(0); } sub SafeVars { my(%PAIRS) = @_; my($url, $key); foreach $key (keys(%PAIRS)) { my($value) = $PAIRS{$key}; $value =~ s/([^A-Z0-9])/sprintf("%%%x", ord($1))/gie; $url .= "$key=$value&"; } $url =~ s/\&$//; return $url; } sub error { my($error) = @_; $FORM{'template'} = "error"; $DATA{'MESSAGE'} = <<_end; The following error occured while trying to process your request:
    $error

    If this problem persists, please contact your administrator. _end &output(%DATA); exit; } sub ReadParse { my(@pairs, %FORM); if ($ENV{'REQUEST_METHOD'} =~ /GET/i) { @pairs = split(/&/, $ENV{'QUERY_STRING'}); } else { my($buffer); read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); @pairs = split(/\&/, $buffer); } foreach(@pairs) { my($name, $value) = split(/\=/, $_); $name =~ tr/+/ /; $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $value =~ s///g; if ($name =~ /^msgid[\d]+$/) { push(@{ $FORM{retrain_checked} }, $value); } else { $FORM{$name} = $value; } } return %FORM; } sub CheckQuarantine { my($f)=0; open(FILE, "<$MAILBOX"); while() { next unless (/^From /); $f++; } close(FILE); if ($f == 0) { $f = "Empty"; } $DATA{'TOTAL_QUARANTINED_MESSAGES'} = $f; } sub To12Hour { my($h) = @_; if ($h < 0) { $h += 24; } if ($h>11) { $h -= 12 if ($h>12) ; $h .= "p"; } else { $h = "12" if ($h == 0); $h .= "a"; } return $h; } sub GetPath { my($USER) = @_; my($PATH); # Domain-scale if ($CONFIG{'DOMAIN_SCALE'} == 1) { my($VPOPUSERNAME, $VPOPDOMAIN); $VPOPUSERNAME = (split(/@/, $USER))[0]; $VPOPDOMAIN = (split(/@/, $USER))[1]; $VPOPDOMAIN = "local" if ($VPOPDOMAIN eq ""); $PATH = "$CONFIG{'DSPAM_HOME'}/data/$VPOPDOMAIN/$VPOPUSERNAME/" . "$VPOPUSERNAME"; return $PATH; # Normal scale } elsif ($CONFIG{'LARGE_SCALE'} == 0) { $PATH = "$CONFIG{'DSPAM_HOME'}/data/$USER/$USER"; return $PATH; # Large-scale } else { if (length($USER)>1) { $PATH = "$CONFIG{'DSPAM_HOME'}/data/" . substr($USER, 0, 1) . "/". substr($USER, 1, 1) . "/$USER/$USER"; } else { $PATH = "$CONFIG{'DSPAM_HOME'}/data/$USER/$USER"; } return $PATH; } &error("Unable to determine filesystem scale"); } sub GetPrefs { my(%PREFS); my($FILE) = "$USER.prefs"; if ($CONFIG{'PREFERENCES_EXTENSION'} == 1) { open(PIPE, "$CONFIG{'DSPAM_BIN'}/dspam_admin agg pref " . quotemeta($CURRENT_USER) . "|"); while() { chomp; my($directive, $value) = split(/\=/); $PREFS{$directive} = $value; } close(PIPE); } if (keys(%PREFS) eq "0" || $CONFIG{'PREFERENCES_EXTENSION'} != 1) { if (! -e "./default.prefs") { &error("Unable to load default preferences"); } open(FILE, "<./default.prefs"); while() { chomp; my($directive, $value) = split(/\=/); $PREFS{$directive} = $value; } close(FILE); if( -e $FILE) { open(FILE, "<$FILE"); while() { chomp; my($directive, $value) = split(/\=/); $PREFS{$directive} = $value; } close(FILE); } } return %PREFS }