#!/usr/bin/perl

# Imap Interface to SpamAssassin          v0.04
# ------------------------------          -----
#
# Connects to an imap server, and filters any unread messages
# through spam assassin.
#
# Artistic License.
#
# Uses Mail::IMAPClient and Mail::SpamAssassin
#
# You probably want to configure something like cron to call this every
# 5-15 minutes (depending on system load, new mail frequency etc)
#
# **WARNING** A bug in SpamAssassin 2.11 means that the blank line
#             seperating the headers from the body will get lost, leading
#             to the first message paragraph being classed as a header!
#             Ensure you use 2.20 not 2.11, as 2.20 has a fix for this
# **WARNING** This version (0.04) assumes spamassassin 3.xx. If you have
#             2.xx, change lines 129 and 165
#
#      Nick Burch <nick@tirian.magd.ox.ac.uk>
#           20/02/2005

use Mail::IMAPClient;
use Mail::SpamAssassin;

my %usernames;
my %passwords;
# Define our server and credentials here
# Do them in the form:
#    $usernames{'servername'} = "user";
#    $passwords{'servername'} = "pass";
# And ensure you have a username and password for each server
$usernames{'my.imap.provider.invalid'} = 'john.smith';
$passwords{'my.imap.provider.invalid'} = 'bitter';
$usernames{'other.imap.server.invalid'} = 'bud';
$passwords{'other.imap.server.invalid'} = 'beer';

# Define what to do with a bad message
my %spam_action;
 $spam_action{'Move'} = 'Yes';
 $spam_action{'MoveFolder'} = 'SpamTrap';
 $spam_action{'Munge'} = 'Yes';

# Normal (1), Debugging (2), or silent(0)?
my $debug = 1;

foreach my $server (keys %usernames) {
   unless(defined $passwords{$server}) {
      die("Error, no password for server $server\n");
   }

   my $username = $usernames{$server};
   my $password = $passwords{$server};

   if($debug >= 1) {
      print "Checking mailbox $username on $server\n";
   }

   # Connect to the IMAP server in peek (i.e. don't set read flag) mode
   my $imap = Mail::IMAPClient->new(Server   => $server,
				    User     => $username,
				    Password => $password,
				    Peek     => 1);

   # Pick the Inbox
   $imap->select('INBOX');

   # Check for new mail
   my @newmail = $imap->unseen();

   unless (@newmail) {
      if( $debug >= 1 ) {
         print "No new emails on $username:$server\n";
      }
      $imap->close;
      next;
   }

   # Get a new SpamAssassin Factory
   my $spam = Mail::SpamAssassin->new;

   foreach my $msg_num (@newmail) {
     if( $debug >= 1 ) {
        print "Checking message $msg_num \n";
     }

     my $message = $imap->message_string($msg_num);

     if( $debug >= 2 ) {
        print "-> Original Message Saved as email-".$msg_num."-orig\n";
        open OUTFILE, "> email-".$msg_num."-orig";
        print OUTFILE $message;
        close OUTFILE;
     }

     # to store the headers we're interested in
     my $hashref;

     if( $debug >= 1 ) {
        $hashref = $imap->parse_headers($msg_num,"From","Subject","X-Spam-Status");
        my $from = $hashref->{From}->[0];
        my $sub  = $hashref->{Subject}->[0];

        print "$from | $sub\n";
     }
     else {
        $hashref = $imap->parse_headers($msg_num,"X-Spam-Status");
     }

     if( exists $hashref->{"X-Spam-Status"} ) {
        if( $debug >= 1 ) {
           print "Already Filtered Message $msg_num, skipping\n";
        }
     }
     else {
        my $status = $spam->check_message_text($message);

        if( $status->is_spam ) {
           if( $debug >= 1 ) {
              print " -> Message is spam\n";
           }

           if( $spam_action{'Munge'} eq 'Yes' ) {
              # flag as spam
              
              # SpamAssassin 2.xx
              #$status->rewrite_mail();
              #$message = $status->get_full_message_as_text();
              # SpamAssassin 3.xx
              $message = $status->rewrite_mail();

              if( $debug >= 2 ) {
                 print "-> Munged Message Saved as email-".$msg_num."-munged\n";
                 open OUTFILE, "> email-".$msg_num."-munged";
                 print OUTFILE $message;
                 close OUTFILE;
              }
           } # Munge

           if( $spam_action{'Move'} eq "Yes" ) {
              # upload version, modified or not
              $imap->append($spam_action{'MoveFolder'}, $message );

              # delete original
              $imap->delete_message( $msg_num );

              # expunge it
              $imap->expunge();

              if( $debug >= 1 ) {
                 print " -> Moved to ".$spam_action{'MoveFolder'}."\n";
              }
           } # Move

           # Add other things to do here
        } # Spam
        else {
           # Not Spam
           if( $spam_action{'Munge'} eq 'Yes' ) {
              # flag as ham

              # SpamAssassin 2.xx
              #$status->rewrite_mail();
              #$message = $status->get_full_message_as_text();
              # SpamAssassin 3.xx
              $message = $status->rewrite_mail();

              if( $debug >= 2 ) {
                 print "-> Munged Message Saved as email-".$msg_num."-munged\n";
                 open OUTFILE, "> email-".$msg_num."-munged";
                 print OUTFILE $message;
                 close OUTFILE;
              }

              # upload
              $imap->append('INBOX', $message );

              # delete original
              $imap->delete_message( $msg_num );

              # expunge it
              $imap->expunge();

              if( $debug >= 1 ) {
                 print " -> Not Spam, Added X-Spam-Status Header\n";
              }
           } # Munge
        } # Not Spam
     } # Not previously scanned
   } # Foreach Message
   $imap->close;
   print "finished checking server\n";
}
exit;
