#!/usr/bin/perl # ---------------- CRM114 email feedback program -------------------- # # The mailfilter.crm program requires feedback when it # incorrectly classifies an email. The "normal" way is to forward # the email to yourself with embedded re-classification commands, but # this isn't very easy to use for non-technophiles. # # This program makes the process easier, by scanning a Maildir for # messages to re-classify. The user drags mis-classified email into this # folder, and this program is run by a cron entry periodically. # # This program reads the header of the mis-classified email to determine # how it was originally classified, and then issues commands to force # the filter to re-learn the text. # # Original Revision can be found here: # http://www.avtechpulse.com/opensource/fix-spam-classification.txt # Author: Michael J. Chudobiak (mjc@avtechpulse.com) # # This version was modified from the original to be used with the # Mbox style mailbox where there is more than one message in a file. # # This version was created by John Johnston jj@johnjohnston.org # http://www.johnjohnston.org/crm114.html # # Version 1.0 - 04/15/2004 - Original Version and Happy Tax Day! # 1.1 - 04/17/2004 - Only searching for the X-CRM114-Status header # in the header instead of the entire message. # 1.2 - 04/18/2004 - Changed to search on a more generic header. # A new header has been added, X-CRM114-Version, # which was causing reclassified mail look as # though it had already been classified. # # Feel free to use/modify this program any way you like. # ------------------------------------------------------------------- use strict; # to catch stupid errors use Mail::Box::Manager; # to manipulate Mbox # ---------- Configuration ------------------------------------------ # set verbosity my $debug_messages=1; # The base directory is normally "/home" my $basedir = "/home"; # This is the sub-directory of /home/user that contains the mailfilter.cf, # spam.css and nonspam.css files my $crmdir = "crm"; # Location of sudo my $sudo = "/usr/local/bin/sudo"; # chmod permission my $filemode = 0600; # Location of procmail my $procmail = "/usr/bin/procmail"; # This is the sub-directory of /home/user where the Imap folders exist # The default directory is usually "mail" but I modified my Imap installation to be "Mail" my $Maildir = "Mail"; # Do you want to save reclassified messages to a file? # This is good for debugging purposes my $SaveReclassify = 1; # This is the name of the folder to scan. # The default value is "Reclassify". # Personally, I have procmail drop spam into Spam. my $IMAPfolderdir = "Reclassify"; # ---------- End of Configuration ----------------------------------- # Read in all the user directories in the base directory, # e.g., /home/alice, /home/bob, /home/charles opendir(DIR,$basedir); my @entries = readdir(DIR); closedir(DIR); foreach my $user (@entries) { # ignore directories starting with a dot next if ($user =~ /^\./); my $userhome = "$basedir/$user"; my $full = "$userhome/$Maildir/$IMAPfolderdir"; # See if the re-classify folder exists. If so, proceed. if (-e $full) { # loop through the messages in the reclassify folder my $mgr = Mail::Box::Manager->new; my $folder = $mgr->open(folder => $full, access => 'rw', lock_method => 'DOTLOCK'); # get the user's uid (my $login, my $pass, my $uid, my $gid) = getpwnam($user); for (my $msgid=1; $msgid<=$folder->messages - 1; $msgid++) { my $msg = $folder->message($msgid); my $entire_msg = $msg->head . $msg->body; my $header = $msg->head; $header =~ s/X-CRM114.*\n//g; my $stripped_msg = $header . $msg->body; # need a file to output the message to # this file is then fed to mailfilter.crm and procmail my $Reclassifyfilename = "$userhome/$Maildir/reclassify_mail.txt"; # open the file and write out the message to it open(fileOUT, ">$Reclassifyfilename") or dienice("Can't open $Reclassifyfilename for writing: $!"); flock(fileOUT, 2); seek(fileOUT, 0, 2); print fileOUT $stripped_msg; close(fileOUT); # just want to make sure that the user can modify any files in their directory chown $uid, -1, $Reclassifyfilename; chmod $filemode, $Reclassifyfilename; # see what the message was originally classified as my $crm_status = $msg->head->get('X-CRM114-Status'); my $is_spam = eval("\$crm_status =~ tr/SPAM//;"); if ($SaveReclassify) { # open the file and write out the message to it my $SaveReclassifyfilename = "$userhome/$Maildir/reclassify_mail.save"; open(fileOUT, ">>$SaveReclassifyfilename") or dienice("Can't open $SaveReclassifyfilename for writing: $!"); flock(fileOUT, 2); seek(fileOUT, 0, 2); if ($is_spam) { print fileOUT "Message reclassified as Non-Spam: " . localtime(time) . "\n"; } else { print fileOUT "Message reclassified as Spam: " . localtime(time) . "\n"; } print fileOUT $entire_msg . "\n\n"; close(fileOUT); # just want to make sure that the user can modify any files in their directory chown $uid, -1, $SaveReclassifyfilename; chmod $filemode, $SaveReclassifyfilename; } # change to CRM114 working directory chdir("$userhome/$crmdir"); if ($is_spam) { # incorrectly marked as spam if ($debug_messages) {print "Change classification of $msgid for $user to non-spam.\n";} # learn as non-spam my $command = "$userhome/$crmdir/mailfilter.crm --learnnonspam < $Reclassifyfilename"; `$command`; # re-deliver message using procmail $command = "$sudo -u $user $procmail $userhome/.procmailrc < $Reclassifyfilename"; `$command`; } else { # incorrectly marked as non-spam if ($debug_messages) {print "Change classification of $msgid for $user to SPAM.\n";} #learn as spam my $command = "$userhome/$crmdir/mailfilter.crm --learnspam < $Reclassifyfilename"; `$command`; } # delete the message $folder->message($msgid)->delete; } # write the changes to the imap folder $folder->write; # For some reason the combination of deleting the message and writing the folder # change the permissions to 644 and the owner and group to root. # This is to set the ownership and permissions back to user writable only. chown $uid, -1, $full; chmod $filemode, $full; # remove the file lock $folder->locker->unlock; } }