#!/usr/bin/perl

# $Header$

#######################################################################
#   Program name    trash.pl                                          #
#   Written by      Rick Sanders                                      #
#   Date            10/7/2003                                         #
#                                                                     #
#   Description                                                       #
#                                                                     #
#   This script checks a user's IMAP mailboxes for deleted messages   #
#   which it moves to the trash mailbox.  Optionally the trash        #
#   mailbox is emptied.                                               #       
#                                                                     #
#   trash.pl is called like this:                                     #
#       ./trash.pl -S host/user/password                              # 
#                                                                     #
#   Optional arguments:                                               #
#	-d debug                                                      #
#       -t <trash mailbox name> (defaults to 'Trash')                 #
#       -e empty the trash mailbox (default is not to empty it)       #
#       -L <logfile>                                                  #
#       -m mailbox list (check just certain mailboxes,see usage notes)#
#######################################################################

use Socket;
use FileHandle;
use Fcntl;
use Getopt::Std;
use IMAP::Utils;


#################################################################
#            Main program.                                      #
#################################################################

   &init();
   &sigprc();

   #  Get list of all messages on the source host by Message-Id
   #
   &connectToHost($sourceHost, 'SRC');
   &login($sourceUser,$sourcePwd, 'SRC');
   @mbxs = &getMailboxList($sourceUser, 'SRC');

   print STDOUT "Checking mailboxes for deleted messages...\n";
   foreach $mbx ( @mbxs ) {
       print STDOUT "   Checking mailbox $mbx for deleted messages\n" if $debug;
       %msgList = ();
       @sourceMsgs = ();
       &getDeletedMsgs( $mbx, \@msgs, 'SRC' ); 
       &moveToTrash( $mbx, $trash, \@msgs, 'SRC' );
       &expungeMbx( $mbx, 'SRC' );
   }

   print STDOUT "\n$total messages were moved to $trash\n";

   if ( $emptyTrash && ($total > 0) ) {
      &expungeMbx( $trash, 'SRC' );
      print STDOUT "The $trash mailbox has been emptied\n\n";
   }

   &logout( 'SRC' );

   exit;


sub init {

   $version = 'V1.0';
   $os = $ENV{'OS'};

   &processArgs;

   if ($timeout eq '') { $timeout = 60; }

   IMAP::Utils::init();
   #  Open the logFile
   #
   if ( $logfile ) {
      openLog($logfile);
   }
   &Log("\n$0 starting");
   $total=0;

}

#  getMailboxList
#
#  get a list of the user's mailboxes from the source host
#
sub getMailboxList {

my $user = shift;
my $conn = shift;
my @mbxs;

   #  Get a list of the user's mailboxes
   #
  if ( $mbxList ) {
      #  The user has supplied a list of mailboxes so only processes
      #  the ones in that list
      @mbxs = split(/,/, $mbxList);
      for $i (0..$#mbxs ) { 
	$mbxs[$i] =~ s/^\s+//; 
	$mbxs[$i] =~ s/s+$//; 
      }
      return @mbxs;
   }

   if ($debugMode) { &Log("Get list of user's mailboxes",2); }

   &sendCommand ($conn, "$rsn LIST \"\" *");
   undef @response;
   while ( 1 ) {
	$response = readResponse ($conn);
	if ( $response =~ /^$rsn OK/i ) {
		last;
	}
	elsif ( $response !~ /^\*/ ) {
		&Log ("unexpected response: $response");
		return 0;
	}
   }

   undef @mbxs;
   for $i (0 .. $#response) {
	# print STDERR "$response[$i]\n";
	$response[$i] =~ s/\s+/ /;
	($dmy,$mbx) = split(/"\/"/,$response[$i]);
	$mbx =~ s/^\s+//;  $mbx =~ s/\s+$//;
	$mbx =~ s/"//g;

	if ($response[$i] =~ /NOSELECT/i) {
		if ($debugMode) { &Log("$mbx is set NOSELECT,skip it",2); }
		next;
	}
	if (($mbx =~ /^\#/) && ($user ne 'anonymous')) {
		#  Skip public mbxs unless we are migrating them
		next;
	}
	if ($mbx =~ /^\./) {
		# Skip mailboxes starting with a dot
		next;
	}
	push ( @mbxs, $mbx ) if $mbx ne '';
   }

   if ( $mbxList ) {
      #  The user has supplied a list of mailboxes so only processes
      #  those
      @mbxs = split(/,/, $mbxList);
   }

   return @mbxs;
}


#  getDeletedMsgs
#
#  Get a list of deleted messages in the indicated mailbox on
#  the source host
#
sub getDeletedMsgs {

my $mailbox = shift;
my $msgs    = shift;
my $conn    = shift;
my $seen;
my $empty;
my $msgnum;

   &trim( *mailbox );
   &sendCommand ($conn, "$rsn SELECT \"$mailbox\"");
   undef @response;
   $empty=0;
   while ( 1 ) {
	$response = readResponse ( $conn );
	if ( $response =~ /^$rsn OK/i ) {
		# print STDERR "response $response\n";
		last;
        } elsif ( $response =~ / 0 EXISTS/i ) {
                $empty = 1;
	} elsif ( $response !~ /^\*/ ) {
		&Log ("unexpected response: $response");
		print STDERR "Error: $response\n";
		return 0;
	}
   }

   return if $empty;

   &sendCommand ( $conn, "$rsn FETCH 1:* (uid flags internaldate body[header.fields (Message-ID Subject)])");
   undef @response;
   while ( 1 ) {
	$response = readResponse ( $conn );
	if ( $response =~ /^$rsn OK/i ) {
		# print STDERR "response $response\n";
		last;
	}
        elsif ( $response =~ /Broken pipe|Connection reset by peer/i ) {
              print STDOUT "Fetch from $mailbox: $response\n";
              exit;
        }
   }

   #  Get a list of the msgs in the mailbox
   #
   undef @msgs;
   undef $flags;
   for $i (0 .. $#response) {
	$seen=0;
	$_ = $response[$i];

	last if /OK FETCH complete/;

	if ( $response[$i] =~ /FETCH \(UID / ) {
	   $response[$i] =~ /\* ([^FETCH \(UID]*)/;
	   $msgnum = $1;
	}

	if ($response[$i] =~ /FLAGS/) {
	    #  Get the list of flags
            $deleted = 0;
	    $response[$i] =~ /FLAGS \(([^\)]*)/;
	    $flags = $1;
            $deleted = 1 if $flags =~ /Deleted/i;
	}
        if ( $response[$i] =~ /INTERNALDATE ([^\)]*)/ ) {
	    $response[$i] =~ /INTERNALDATE ([^BODY]*)/i; 
            $date = $1;
            $date =~ s/"//g;
	}
        if ( $response[$i] =~ /^Subject:/ ) {
	   $response[$i] =~ /Subject: (.+)/;
           $subject = $1;
        }
	if ( $response[$i] =~ /^Message-Id:/ ) {
	    ($label,$msgid) = split(/: /, $response[$i]);
            &trim(*msgid);
            $msgid =~ s/^\<//;
            $msgid =~ s/\>$//;
            push( @$msgs, $msgnum ) if $deleted;
	}
   }
}

sub usage {

   print STDOUT "usage:\n";
   print STDOUT " iu-trash -S sourceHost/sourceUser/sourcePassword\n";
   print STDOUT " Optional arguments:\n";
   print STDOUT "    -d debug\n";
   print STDOUT "    -t <trash mailbox name>\n";
   print STDOUT "    -e empty trash mailbox\n";
   print STDOUT "    -L <logfile>\n";
   print STDOUT "    -m <mailbox list> (eg \"Inbox, Drafts, Notes\". Default is all mailboxes)\n";
   exit;

}

sub processArgs {

   if ( !getopts( "dS:L:m:ht:e" ) ) {
      &usage();
   }

   ($sourceHost,$sourceUser,$sourcePwd) = split(/\//, $opt_S);
   $mbxList = $opt_m;
   $logfile = $opt_L;
   $trash   = $opt_t;
   $emptyTrash = 1 if $opt_e;
   $debug = $showIMAP = 1 if $opt_d;

   &usage() if $opt_h;
   $trash = 'Trash' if !$trash;

}

#sub checkForAdds {
#
#my $added=0;
#
#   &Log("Checking for messages to add to $destHost/$destUser");
#   foreach $key ( @sourcekeys ) {
#        if ( $destList{"$key"} eq '' ) {
#             $entry = $sourceList{"$key"};
#             ($msgid,$mbx) = split(/\|\|\|\|\|\|/, $key);
#             ($msgnum,$flags,$date) = split(/\|\|\|\|\|\|/, $entry);
#             &Log("   Adding $msgid to $mbx");
#
#             #  Need to add this message to the dest host
#
#             $message = &fetchMsg( $msgnum, $mbx, 'SRC' );
#
#             &insertMsg( 'DST', $mbx, *message, $flags, $date );
#             $added++;
#        }
#   }
#   return $added;
#
#}


sub checkForUpdates {

my $updated=0;

   #  Compare the flags for the message on the source with the
   #  one on the dest.  Update the dest flags if they are different

   &Log("Checking for flag changes to $destHost/$destUser");
   foreach $key ( @sourcekeys ) {
        $entry = $sourceList{"$key"};
        ($msgid,$mbx) = split(/\|\|\|\|\|\|/, $key);
        ($msgnum,$srcflags,$date) = split(/\|\|\|\|\|\|/, $entry);

        if ( $destList{"$key"} ne '' ) {
             $entry = $destList{"$key"};
             ($msgid,$mbx) = split(/\|\|\|\|\|\|/, $key);
             ($msgnum,$dstflags,$date) = split(/\|\|\|\|\|\|/, $entry);

	     $srcflags  =~ s/\\Recent//;
	     $destflags =~ s/\\Recent//;
	     if ( $srcflags ne $dstflags ) {
		&Log("Need to update the flags for $msgid") if $debug;
		$updated++ if &updateFlags( 'DST', $msgid, $mbx, $srcflags );
	     }
	}
   }
   return $updated;
}

sub updateFlags {

my $conn  = shift;
my $msgid = shift;
my $mbx   = shift;
my $flags = shift;
my $rc;

   if ( $debug ) {
      &Log("Find $msgid");
      &Log("flags $flags");
   }

   $msgnum = &findMsg( $conn, $msgid, $mbx );
   &Log("msgnum is $msgnum") if $debug;

   &sendCommand ( $conn, "1 STORE $msgnum +FLAGS ($flags)");
   while (1) {
        $response = readResponse ($conn);
        if ( $response =~ /^1 OK/i ) {
	   &Log("   Updated flags for $msgid");
	   $rc = 1;
	   last;
	}

        if ( $response =~ /^1 BAD|^1 NO/i ) {
           &Log("Error setting flags for $msgid: $response");
	   $rc = 0;
           last;
        }
   }
   return $rc;
}

sub dieright {
   local($sig) = @_;
   print STDOUT "caught signal $sig\n";
   &logout( 'SRC' );
   exit(-1);
}

sub sigprc {

   $SIG{'HUP'} = 'dieright';
   $SIG{'INT'} = 'dieright';
   $SIG{'QUIT'} = 'dieright';
   $SIG{'ILL'} = 'dieright';
   $SIG{'TRAP'} = 'dieright';
   $SIG{'IOT'} = 'dieright';
   $SIG{'EMT'} = 'dieright';
   $SIG{'FPE'} = 'dieright';
   $SIG{'BUS'} = 'dieright';
   $SIG{'SEGV'} = 'dieright';
   $SIG{'SYS'} = 'dieright';
   $SIG{'PIPE'} = 'dieright';
   $SIG{'ALRM'} = 'dieright';
   $SIG{'TERM'} = 'dieright';
   $SIG{'URG'} = 'dieright';
}

sub moveToTrash {

my $mbx   = shift;
my $trash = shift;
my $msgs  = shift;
my $conn  = shift;
my $msglist;
my $moved;

   return if $mbx eq $trash;
   return if $#$msgs == -1;

   foreach $msgnum ( @$msgs ) {
      $moved++;
      $msglist .= "$msgnum,";
   }

   chop $msglist;

   &sendCommand ($conn, "1 COPY $msglist $trash");
   while (1) {
        $response = readResponse ( $conn );
        last if $response =~ /^1 OK/i;
        if ($response =~ /NO/) {
           print STDOUT "unexpected COPY response: $response\n";
           print STDOUT "Please verify that mailbox $trash exists\n";
           exit;
        }
   }
   print STDOUT "   Moved $moved messages from $mbx to $trash\n";
   $total += $moved;

}
