#!/usr/bin/perl
#
#
# Restroing script for CommuniGate Pro Journals
# See <http://old.communigatepro.ru/CGPJournaling/>
#
# version 1.0 28.06.16
# version 1.1 22.11.16


use strict;
use POSIX qw(mktime);
use CLI;


####  YOU SHOLD REDEFINE THESE VARIABLES !!!

my $CGServerAddress='127.0.0.1';  #IP or domain name;

my $Login="postmaster";
my $Password='pass';


my $journalDomain='journals.local';

my $addPatternAttrib="st";
my $skipPatternAttrib="l";

my $logFile='jRestore.log';

#### end of the customizeable variables list

if(@ARGV < 1) {
  die "Usage: ./jRestore.pl [options] username\@domain.com\n";
}


my ($target,$journalName,$restoredAccount);
my $restoreDepth=0;

for(my $i=0;$i<@ARGV;$i++) {
  if($ARGV[$i]=~/^--target/i) {
    $target=$ARGV[++$i];
    unless($target=~/\// ) {    
      die "Error: the target ($target) must be in user\@domain/mailbox format\n";
    }
  }elsif($ARGV[$i]=~/^--journal/i) {
    $journalName=lc($ARGV[++$i]);
  }elsif($ARGV[$i]=~/^--days/i) {
    $restoreDepth=$ARGV[++$i];
    unless($restoreDepth>=1) {
      die "Invalid value: $restoreDepth\n";
    }
  }elsif($ARGV[$i]=~/^-/) {
    die "Unknown option: $ARGV[$i]\n";
  }else {
    $restoredAccount=lc($ARGV[$i]);
  }
}

open(FOUT,">>$logFile") || die "can't append to $logFile: $!\n";
close(FOUT);

myPrint("----------------\nStarted on ".localtime()."\n");

my $nMessagesCopied=0;

my $cli = new CGP::CLI( { PeerAddr => $CGServerAddress,
                          PeerPort => 106,
                          login    => $Login,
                          password => $Password } )
   || die "*** Can't login to CGPro CLI: ".$CGP::ERR_STRING."\n";



if($target) {
  if($target=~/^(.+?)\/(.+)/ ) {
    my $data=checkAddress($1);
    unless($data) {
      myPrintE("the target account '$1' doesn't exist\n");
      $cli->Logout();
      exit(1); 
    }
    $cli->CreateMailbox($data,$2);
  } else {
    myPrintE("the target account name must be in user\@domain/mailbox format\n");
    $cli->Logout();
    exit(1); 
  }
} else  {
  my $data=checkAddress($restoredAccount);
  unless($data) {
    myPrintE("the account '$restoredAccount' doesn't exist\n");
    $cli->Logout();
   exit(1);  
  }
  $restoredAccount=$data;
  $target="$restoredAccount/INBOX";
}
myPrint("restoredAccount='$restoredAccount' Target='$target'\n");


if($journalName) {
  my $data=checkAddress("j-$journalName\@$journalDomain");
  unless($data) {
    myPrintE("the journal '$journalName' doesn't exist\n");
    $cli->Logout();
    exit(1);  
  }
  $journalName="j-".$journalName;
} else {
  $journalName=findJournal($restoredAccount);
  unless($journalName) {
    myPrintE("unable to find the journal for '$restoredAccount'\n");
    $cli->Logout();
    exit(1);  
  }
}


my $mailboxesList=$cli->ListMailboxes(accountName=>"$journalName\@$journalDomain");
unless($mailboxesList) {
  myPrintE("ListMailboxes $journalName\@$journalDomain: ".$cli->getErrMessage."\n");
  $cli->Logout();
  exit(1);
}


$cli->Logout();  # no need anymore


my $imap = IO::Socket::INET->new(PeerAddr => $CGServerAddress,
                                  PeerPort => 143,
                                  Timeout  => 60*60*24);
$imap->autoflush(1);
my $responseLine;

print $imap "x LOGIN $Login $Password\015\012";
do {
  $responseLine = <$imap>;
}until($responseLine =~/^x /);
unless($responseLine =~ /^x OK/) {
  myPrintE("Can't login to CGPro IMAP: $responseLine.\n");
  exit(1);
}

#myPrint("Mailbox:$_\n") foreach(sort keys %$mailboxesList);

my $secs=time();
for(my $day=0;;$day++) {
  last if($restoreDepth && $day>$restoreDepth);

  my $strFolder;
  { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($secs - (($day-1)*24*60*60) );
    $strFolder=sprintf("ar_%04d-%02d-%02d",$year+1900,$mon+1,$mday);
  }  
  myPrint("day=$day, folder=$strFolder\n");
  last if($day!=0 && (!@$mailboxesList{$strFolder}) );
  restoreMails($strFolder) if(@$mailboxesList{$strFolder});
}

myPrint(" $nMessagesCopied messages found total\n");

print $imap "x LOGOUT\015\012";
do {
  $responseLine = <$imap>;
}until($responseLine =~/^x /);

myPrint("Done at ".localtime()."\n");
exit(0);



sub stripComment {
  my ($data)=@_;
  return "" unless($data);
  $data=$1 if($data=~/^(.*?)#/);
  $data=~s/^\s*//; 
  $data=~s/\s*$//;
  return $data; 
}

sub findJournal {
  my ($user)=@_;
  my $accList=$cli->ListAccounts($journalDomain);
  unless($accList) {
    myPrintE("ListAccounts: ".$cli->getErrMessage."\n");
    $cli->Logout();
    exit(0);
  }
  foreach(sort keys %$accList) {  
    next unless(/^j\-/);
    my $account=$_;
    my $accData=$cli->GetAccountSettings("$account\@$journalDomain");
    unless($accData) {
      myPrintE("GetAccountSettings $account\@$journalDomain: ".$cli->getErrMessage."\n");
      $cli->Logout();
      exit(0);
    }
    my $accSize=$cli->GetAccountInfo("$account\@$journalDomain","StorageUsed");
    $accSize=~s/#//;

    my $addP=stripComment( $accData->{$addPatternAttrib} );    
    my $delP=stripComment( $accData->{$skipPatternAttrib} );
    myPrint("Checking journal '$account'. Patterns: '$addP', '$delP'. Size=$accSize.\n");    
    
    next unless($addP);
    if($user=~/^$addP$/) {
      unless($delP && $user=~/^$delP$/) {
        myPrint("Jounal '$account' matches the patterns.\n");    
        return $account; #journal name;
      }
    }  
  }
  undef;
}



sub restoreMails {
  my ($arcBox)=@_;

  my $mboxName="~$journalName\@$journalDomain/$arcBox";  
  myPrint("searching $mboxName (".localtime()."}\n");

  print $imap "x SELECT \"$mboxName\"\015\012";
  my $nMessages=0;
  while(defined($responseLine = <$imap>)) {
    last if($responseLine=~/^x /);
    if($responseLine =~ /^\* (\d*) EXISTS/) {
       $nMessages=$1;
    }
    #myPrint("$responseLine");
  }
  unless($responseLine =~ /^x OK/) {
    myPrintE("Can't select $mboxName: $responseLine.\n");
    return -1;
  }
  myPrint(" nMessages=$nMessages\n");
  my $startTime=time();
  my $step=50;
  for(my $idx2=1;$idx2<=$nMessages;$idx2+=$step+1) {
    my @uids;
    my $uidx=0;
    
    print $imap "x FETCH $idx2:".($idx2+$step)." RFC822.HEADER.PEEK\015\012";

    while(defined($responseLine = <$imap>)) {
      last if($responseLine=~/^x /);

      if($responseLine=~/\* (\d+) FETCH .* \{(\d+)\}/) {
        my ($theID,$size,$msg)=($1,$2,"");
        while($size>0) {
          my $d=<$imap>;
          $msg.=$d; $size-=length($d);
        }
        if($msg=~/X-(rcpt|orcpt|ret|oret): $restoredAccount\s/i) { # || $msg=~/X-ret: $restoredAccount\s/i) {
          myPrint("  message found: $theID\n");
          $uids[$uidx]=$theID;
          $uidx++;
        }   
      }  
    }  
    unless($responseLine =~ /^x OK/) {
      myPrintE("Can't fetch messages $idx2:".($idx2+$step).": $responseLine.\n");
      return -1;
    } 

    for(my $idx=0;$idx<$uidx;$idx++) {
      copyMessage($uids[$idx]);
      $nMessagesCopied++;
    }
    if(time()>$startTime+10) {
#      print "idx=$idx2\n";
      $startTime=time();
    }
  }
  print $imap "x CLOSE\015\012";
  do {
   $responseLine = <$imap>;
  }until($responseLine =~/^x /);
  myPrint(" $nMessagesCopied messages found so far\n");
}

sub copyMessage {
  my ($theUID)=@_;
  myPrint(" copying message #$theUID\n");
  my $theInternaldate;
  my @message;

  print $imap "x FETCH $theUID (INTERNALDATE RFC822.PEEK)\015\012";

  while(defined($responseLine = <$imap>)) {
    last if($responseLine=~/^x /);

    if($responseLine=~/\^* (\d+) FETCH /) {
      if($responseLine=~/INTERNALDATE \"(.*?)\"/) { #"
        $theInternaldate=$1;
      }
      if($responseLine=~/\{(\d+)\}/) {
        my $size=$1;
#        print "size=$size id=$theID\n";
        while($size>0) {
          my $d=<$imap>;
          $size-=length($d);
          push(@message,$d);
        }
      }
    }
  }  
  unless($responseLine =~ /^x OK/) {
    myPrintE("Can't fetch message $theUID: $responseLine.\n");
    return -1;
  } 
  for(my $idx=0;$idx<scalar(@message);$idx++) {
    my $line=$message[$idx];
    last if($line eq "\015\012");
    if($line=~/^X-(rcpt|orcpt|ercpt|ret|oret|Real\-To):/) {
      splice(@message,$idx,1);
      $idx--;
    }
  }
  my $msgSize=0;
  $msgSize+=length($_) foreach(@message);

  print $imap "x APPEND \"~$target\" \"$theInternaldate\" {$msgSize}\015\012";
  $responseLine = <$imap>;
  unless($responseLine =~ /^\+ /) {
    myPrintE("Append failed: $responseLine\n");
    return -1;
  }
  print $imap $_ foreach(@message);
  print $imap "\015\012";
  do {
    $responseLine = <$imap>;
  }until($responseLine =~/^x /);
  unless($responseLine =~ /^x OK/) {
    myPrintE("Can't append message: $responseLine\n");
    return -1;
  } 

}

sub checkAddress {
  my $data=$cli->Route($_[0],"mail");
#                 || die "Error: ".$cli->getErrMessage.", quitting";
  return undef unless($data);
  return undef if($data->[0] ne 'LOCAL');
  return $data->[1];
}

sub myPrint {
  my ($msg)=@_;
  open(FOUT,">>$logFile");
  print FOUT $msg;
  close(FOUT);
}
sub myPrintE {
  my ($msg)=@_;
  myPrint("Error: $msg");
  print "Error: $msg";
}

