#!/usr/bin/perl

use POSIX ":sys_wait_h";
use Mail::IMAPClient;
use Mail::SpamAssassin;
use strict;

my $VERSION = '1.0';

my $ConfDir    = "$ENV{HOME}/.spamassassin/";
    
my $PIDFile    = "$ConfDir/IMAP.pid";
my $ConfFile   = "$ConfDir/imapsa.conf";

# Read in configuration file

my (%Conf) = readConfigFile($ConfFile);

# Set configuration variables with defaults

my $INBOX      = $Conf{inbox} || 'INBOX';
my $SpamFolder = $Conf{spamfolder} || 'spam';
my $Server     = $Conf{server};
my $User       = $Conf{user};
my $Password   = $Conf{password};
my $Delay      = $Conf{delay} || 60;
my $Search     = $Conf{search} || 'UNDELETED NOT HEADER X-Spam-Status ""';

# Catch signals

$SIG{TERM} = \&catch_zap;
$SIG{INT}  = \&catch_zap;

# See if PID mentioned in PID file (if any) is still around

if (-e $PIDFile) {
  open  PIDFILE,"$PIDFile";
  my $OldPID = <PIDFILE>;
  chomp $OldPID;
  close PIDFILE;

  my $Group = getpgrp $OldPID;
  if ($Group > 0) {
    exit;
  }  
}

# Write new PID file

open  PIDFILE,">$PIDFile";
print PIDFILE $$,"\n";
close PIDFILE;

# Setup SA filter, connection to IMAP

my $SpamTest = Mail::SpamAssassin -> new();
my $IMAP = Mail::IMAPClient -> new (Server   => $Server,
                                    User     => $User,
                                    Password => $Password);
$IMAP->Uid(1);								
$IMAP->Peek(1);

while (1) {

	$IMAP->connect() unless $IMAP->IsConnected;
	$IMAP->select($INBOX);

	my @MIDs   =  $IMAP->search($Search);
	foreach my $MID (@MIDs) {
		# Debug only: #	print STDERR "Selected message: " . $IMAP->get_header($MID,"Subject") . "\n";
		my $Message = $IMAP -> message_string($MID);  
		my $MessageObject = $SpamTest->check_message_text($Message);
		
		my $IsSpam  = $MessageObject -> is_spam(); 
		my $RewrittenMessage = $MessageObject->rewrite_mail();
	  	 
		# Put the message back into the mailbox with its original flags
		my ($internaldate) = $IMAP->fetch($MID,'INTERNALDATE');
		$internaldate =~ m/"(.+)"/;
		if( $1 ) {
			$internaldate = $1;
			print STDERR "internal date for $MID (".$IMAP->get_header($MID,"Subject").") is $internaldate\n";
		}
		else {
			$internaldate = undef;
		}
	
		my $AppendedMessageUid = $IMAP -> append_string($SpamFolder,$RewrittenMessage, join(" ", $IMAP->flags($MID)), $internaldate);
		if ($AppendedMessageUid) {
			$IMAP -> delete_message($MID);
		}
	  	  
		print " MID: $MID $IsSpam \n"; # stay compatible with the original IMAPAssassin
		$MessageObject -> finish();	  

		&cleanupchildren;
	} # end foreach message  

	# disconnect and sleep before repeating the loop
	$IMAP->disconnect();
	sleep $Delay;
	
}  # end while(1)


sub catch_zap { # Orderly shutdown, remove PID file
  unlink $PIDFile;
  die "Exiting IMAPAssassin.\n";
}

sub numerically {$a <=> $b;}
sub alphabetically {$a cmp $b;}

sub cleanupchildren {
  my $extrapid = 0;
  while (my $kid = waitpid(-1,&WNOHANG) > 0) {
    ++$extrapid;
  }  
}

sub readConfigFile {
	my %Conf = ();
	open CONF,$ConfFile;
	while (my $line = <CONF>) {
		chomp $line; $line =~ s/^\s+//; $line =~ s/\s+$//;
		next unless $line;
		next if substr($line,0,1) eq "#";
		$line =~ m/^([\w\d]+)\s+(.+)$/;
		my $key = $1;
		my $value = $2;
		$key =~ tr/[A-Z]/[a-z]/;
		$Conf{$key} = $value;
	}
	close CONF;
	return (%Conf);
}

=pod

=head1 NAME

ImapAssassin.pl - use SpamAssassin filtering on any IMAP mailbox

=head1 SYNOPSIS

$ perl ImapAssassin.pl

=head1 HOW TO CONFIGURE

Create or edit the configuration file:  ~/.spamassassin/imapsa.conf

=head1 SAMPLE CONFIGURATION

delay 30
server your.server.com
spamfolder filtered/spam
user name
password secret_pw
inbox INBOX
spamfolder Spam
search UNDELETED NOT HEADER X-Spam-Status ""

=head1 Examples for the SEARCH option

ALL
Returns all messages in the mailbox

UNSEEN
Returns all unread messages

UNDELETED NOT HEADER X-Spam-Status ""
Returns all messages which have not yet been deleted by the user or scanned by SpamAssassin

UNDELETED NOT HEADER X-Spam-Flag ""
Returns all messages which have not been deleted and have not been marked as spam

See http://www.faqs.org/rfcs/rfc2060.html for more details.

=head1 CONFIGURATION

spam folder - if you do NOT want messages moved to a separate spam folder, set the spam
folder to be the same as the mailbox you are scanning. 

=head1 CREDITS

* Adam Kalsey, who wrote IMAPAssassin. http://kalsey.com/2002/09/imapassassin/
* SpamAssassin. http://spamassassin.apache.org/

=head1 AUTHOR

Jonathan Buhacoff <jonathan@buhacoff.net>

=head1 COPYRIGHT AND LICENSE

Copyright 2007 Jonathan Buhacoff

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

=cut
