#!/usr/local/bin/perl

# $Header: /mhub4/sources/imap-tools/mbxIMAPsync.pl,v 1.1 2008/10/18 15:09:25 rick Exp $

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

    ######################################################################
    #  Program name   mbxIMAPsync.pl                                     #
    #  Written by     Rick Sanders                                       #
    #  Date           12 Feb 2004                                        #
    #                                                                    #
    #  Description                                                       #
    #                                                                    #
    #  mbxIMAPsync is used to synchronize the contents of a Unix         #
    #  mailfiles with an IMAP mailbox.  The user supplies the location   #
    #  & name of the Unix mailbox (eg /var/mail/rfs) and the hostname,   #
    #  username, & password of the IMAP account along with the name      #
    #  of the IMAP mailbox.  For example:                                #
    #                                                                    #
    #  ./mbxIMAPsync.pl -f /var/mail/rfs -i imapsrv/rfs/mypass -m INBOX  #
    #                                                                    #
    #  mbxIMAPsync compares the messages in the mailfile with those in   #
    #  the IMAP mailbox by Message-Id and adds the ones in the mailfile  #
    #  which are not in the IMAP mailbox.  Then it looks for messages    #
    #  in the IMAP mailbox which are not in the mailfile and removes     #
    #  them from the IMAP mailbox.                                       #
    #                                                                    #
    #  See the Usage() for available options.                            #
    ######################################################################

    &init();

   &connectToHost($imapHost, 'IMAP');
   &login($imapUser,$imapPwd, 'IMAP');

   #  Get list of msgs in the mailfile by Message-Id

   $added=$purged=0;
   print STDOUT "Processing $mailfile\n";
   print STDOUT "Checking for messages to add\n";
   @msgs = &readMbox( $mailfile );
   foreach $msg ( @msgs ) {
       @msgid = grep( /^Message-ID:/i, @$msg );
       ($label,$msgid) = split(/:/, $msgid[0]);
       chomp $msgid;
       &trim( *msgid );
       $mailfileMsgs{"$msgid"} = '1';
       push( @sourceMsgs, $msgid );

       if ( !&findMsg( $msgid, $mbx, 'IMAP' ) ) {
          # print STDOUT "Need to add msgid >$msgid<\n";
          my $message;

          foreach $_ ( @$msg ) { chop $_; $message .= "$_\r\n"; }

          if ( &insertMsg($mbx, \$message, $flags, $date, 'IMAP') ) {
             $added++;
             print STDOUT "   Added $msgid\n";
          }
       }
   }

   #  Remove any messages from the IMAP mailbox that no longer
   #  exist in the mailfile

   print STDOUT "Checking for messages to purge\n";
   &getMsgList( $mbx, \@imapMsgs, 'IMAP' );
   foreach $msgid ( @imapMsgs ) {
      if ( $mailfileMsgs{"$msgid"} eq '' ) {
         $msgnum = &findMsg( $msgid, $mbx, 'IMAP' );
         if ( &deleteMsg($msgnum, 'IMAP') ) {
            &Log("   Marked $msgid for deletion");
            print STDOUT "   Marked msgid $msgid for deletion\n";
            $deleted++;
         } 
      }
   }

   if ( $deleted ) {
      #  Need to purge the deleted messages
      $purged = &expungeMbx( $mbx, 'IMAP' );
   }

   &Log("Done");
   &Log("Added  $added messages to IMAP mailbox $mbx");
   &Log("Purged $purged messages from IMAP mailbox $mbx");

   print STDOUT "\nAdded  $added messages to IMAP mailbox $mbx\n";
   print STDOUT "Purged $purged messages from IMAP mailbox $mbx\n";

   exit;


sub init {

   if ( ! getopts('f:m:i:L:dx') ) {
      &usage();
      exit;
   }

   ($imapHost,$imapUser,$imapPwd) = split(/\//, $opt_i);
   $mailfile = $opt_f;
   $mbx      = $opt_m;
   $logfile  = $opt_L;
   $debug    = 1 if $opt_d;

   IMAP::Utils::init();
   if ( $logfile ) {
      openLog($logfile);
   }
   Log("\nThis is mbxIMAPsync\n");

   if ( !-e $mailfile ) {
      &Log("$mailfile does not exist");
      exit;
   }

}

sub usage {

   print "Usage: iu-mbximapsync\n";
   print "    -f <location of mailfiles>\n";
   print "    -i imapHost/imapUser/imapPassword\n";
   print "    -m <IMAP mailbox>\n";
   print "    [-L <logfile>]\n";
   print "    [-d debug]\n";

}

sub readMbox {

my $file  = shift;
my @mail  = ();
my $mail  = [];
my $blank = 1;
local *FH;
local $_;

    &Log("Reading the mailfile") if $debug;
    open(FH,"< $file") or die "Can't open $file";

    while(<FH>) {
        if($blank && /\AFrom .*\d{4}/) {
            push(@mail, $mail) if scalar(@{$mail});
            $mail = [ $_ ];
            $blank = 0;
        }
        else {
            $blank = m#\A\Z#o ? 1 : 0;
            push(@{$mail}, $_);
        }
    }

    push(@mail, $mail) if scalar(@{$mail});
    close(FH);

    return wantarray ? @mail : \@mail;
}

#
sub insertMsg {

my $mbx = shift;
my $message = shift;
my $flags = shift;
my $date  = shift;
my $conn  = shift;
my ($lsn,$lenx);

   &Log("   Inserting message into mailbox $mbx") if $debug;
   $lenx = length($$message);

   #  Create the mailbox unless we have already done so
   ++$lsn;
   if ($destMbxs{"$mbx"} eq '') {
        &Log("creating mailbox $mbx") if $debug;
	&sendCommand (IMAP, "$lsn CREATE \"$mbx\"");
	while ( 1 ) {
	   $response = readResponse (IMAP);
	   if ( $response =~ /^$rsn OK/i ) {
		last;
	   }
	   elsif ( $response !~ /^\*/ ) {
		if (!($response =~ /already exists|reserved mailbox name/i)) {
			&Log ("WARNING: $response");
		}
		last;
	   }
       }
   } 

   $destMbxs{"$mbx"} = '1';

   ++$lsn;
   $flags =~ s/\\Recent//i;

   # &sendCommand (IMAP, "$lsn APPEND \"$mbx\" ($flags) \"$date\" \{$lenx\}");
   &sendCommand (IMAP, "$lsn APPEND \"$mbx\" \{$lenx\}");
   $response = readResponse (IMAP);
   if ( $response !~ /^\+/ ) {
       &Log ("unexpected APPEND response: $response");
       # next;
       push(@errors,"Error appending message to $mbx for $user");
       return 0;
   }

   print IMAP "$$message\r\n";

   undef @response;
   while ( 1 ) {
       $response = readResponse (IMAP);
       if ( $response =~ /^$lsn OK/i ) {
	   last;
       }
       elsif ( $response !~ /^\*/ ) {
	   &Log ("unexpected APPEND response: $response");
	   # next;
	   return 0;
       }
   }

   return 1;
}

#  getMsgList
#
#  Get a list of the user's messages in the indicated mailbox on
#  the IMAP host
#
sub getMsgList {

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

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

   &sendCommand ( $conn, "$rsn FETCH 1:* (uid flags internaldate body[header.fields (Message-Id)])");
   undef @response;
   while ( 1 ) {
	$response = readResponse ( $conn );
	if ( $response =~ /^$rsn OK/i ) {
		# print STDERR "response $response\n";
		last;
	}
	elsif ( $XDXDXD ) {
		&Log ("unexpected response: $response");
		&Log ("Unable to get list of messages in this mailbox");
		push(@errors,"Error getting list of $user's msgs");
		return 0;
	}
   }

   #  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
	    $response[$i] =~ /FLAGS \(([^\)]*)/;
	    $flags = $1;
   	    $flags =~ s/\\Recent//i;
	}
        if ( $response[$i] =~ /INTERNALDATE ([^\)]*)/ ) {
	    ### $response[$i] =~ /INTERNALDATE (.+) ([^BODY]*)/i; 
	    $response[$i] =~ /INTERNALDATE (.+) BODY/i; 
            $date = $1;
            $date =~ s/"//g;
	}
	if ( $response[$i] =~ /^Message-Id:/i ) {
	    ($label,$msgid) = split(/: /, $response[$i]);
	    push (@$msgs,$msgid);
	}
   }
}

sub findMsg {

my $msgid = shift;
my $mbx   = shift;
my $conn  = shift;
my $msgnum;
my $noSuchMbx;

   &Log("Searching for $msgid in $mbx") if $debug;
   &sendCommand ( $conn, "1 SELECT \"$mbx\"");
   while (1) {
	$response = readResponse ($conn);
        if ( $response =~ /^1 NO/ ) {
           $noSuchMbx = 1;
           last;
        }
	last if $response =~ /^1 OK/;
   }
   return '' if $noSuchMbx;

   &Log("Search for $msgid") if $debug;
   &sendCommand ( $conn, "$rsn SEARCH header Message-Id \"$msgid\"");
   while (1) {
	$response = readResponse ($conn);
	if ( $response =~ /\* SEARCH /i ) {
	   ($dmy, $msgnum) = split(/\* SEARCH /i, $response);
	   ($msgnum) = split(/ /, $msgnum);
	}

	last if $response =~ /^1 OK/;
	last if $response =~ /complete/i;
   }

   if ( $msgnum ) {
      &Log("Message exists") if $debug;
   } else {
      &Log("Message does not exist") if $debug;
   }

   return $msgnum;
}
