#!/usr/bin/perl
#
# $Id: 2d89df355cf34f94da27a75902849073a1e26801 $
#
use strict;
use vars qw($GunzipError);
use Getopt::Std;
use Carp;
use IO::Uncompress::Gunzip;
use Digest::SHA qw(sha256);
use MIME::Base64;

my %opts;
getopts('Ehdrvwb:e:i:l:s:',\%opts);

my $box_dir     = $opts{b} || '/srv/benno/archive/repo';
my $inbox_dir   = $opts{i} || '/srv/benno/inbox';
my $logfile     = $opts{l} || '/var/log/benno/reimport.log';
my $error_file  = $opts{e};
my $rename      = $opts{r};
my $suffix      = $opts{s} || '.err';
my $verbose     = $opts{v};
my $warnings    = $opts{w};
my $envelopes   = $opts{E};

help_exit() if $opts{h};
help_exit() if not ($box_dir);
#help_exit() if not ($error_file);


my @error_files;
if ($error_file) {
    @error_files = ($error_file);
}
else {
    @error_files = get_filelist($inbox_dir,$suffix);
}

if (! $error_files[0]) {
    print STDERR "No files to reimport found.\n";
    exit 1;
}

open LOG, ">>$logfile" or die "Cannot open lotgfile $logfile: $!\n";

foreach my $file (@error_files) {
    if ($verbose) {
        print "#############################################################\n";
        print "Errorfile   : $file\n";
    }
    my $file_digest = mailfile_digest($file,$envelopes);
    print "Filedigest  : $file_digest\n" if $verbose;

    my ($p1,$p2,$p3,$filename) = $file_digest =~ /^(..)(..)(..)(.+)$/;
    $filename .= '00';

    # check if repodir or boxdir, boxdir contains the "./journal" directory
    my @boxlist;
    if (-d "$box_dir/journal") {
        @boxlist = ($box_dir);
    }
    else {
        @boxlist = get_dirlist($box_dir);
    }

    # check all boxes
    foreach my $box (@boxlist) {
        next unless -d "$box/journal";   # Boxdir contanins "journal" directory
        my $repofile_path = $box."/$p1/$p2/$p3/$filename".'.gz';
        $repofile_path =~ s/\/\//\//g;
        check_boxdir($repofile_path,$file_digest,$rename);
    }
    # rename errorfile to .eml
    rename_importfile($file) if ($rename);
}

print STDERR "\nFiles in repo and inbox *not* changed! Run with \"-r\" to reimport files.\n" if not $rename;

close LOG;

### SUBS ###

### get_dirlist
sub get_dirlist
{
    my ($dir) = @_;
    my @entries;
    opendir(my $dh, $dir) || die "Cannot open directory $dir: $!\n";
    while (my $entry = readdir($dh)) {
        next if $entry =~ /^\.$/;
        next if $entry =~ /^\.\.$/;
        next unless -d "$dir/$entry";
        push @entries, "$dir/$entry";
    }
    return @entries; 
}


### get_filelist
sub get_filelist
{
    my ($dir,$suffix) = @_;
    my @entries;

    $suffix .= '$';
    opendir(my $dh, $dir) || die "Cannot open directory $dir: $!\n";
    while (my $entry = readdir($dh)) {
        my $filepath = $dir.'/'.$entry;
        next unless $filepath =~ /$suffix/;
        next unless -f $filepath;
        $filepath =~ s/\/\//\//g;
        push @entries, $filepath;
    }
    return @entries; 
}


### check_boxdir
sub check_boxdir
{
    my ($repofile_path,$errorfile_digest) = @_;
    my $repofile_digest;


    eval {

        $repofile_digest = bennofile_digest($repofile_path);
        if ($errorfile_digest eq $repofile_digest) {
            # check ok
            rename_repofile($repofile_path) if $rename;
        }
        else {
            die "Error in digest of Benno Repofile ($errorfile_digest <> $repofile_digest)\n";
        }

    };
    if ($@) {
        #catch exception
        if ($@ =~ s/^WARN\s//) {
            print STDERR "$@\n" if $warnings;
        }
        else {
            print STDERR "$@\n";
        }
    }


}


### compute digest of file
sub mailfile_digest 
{
    my ($file,$envelopes) = @_;
    
    my $buffer;
    open FILE, $file or die "Cannot open $file: $!\n";
    while (my $line = <FILE>) {
        if (!$envelopes) {
            next if $line =~ /^X-REAL-(MAILFROM|RCPTTO):/;
        }
        $buffer .= $line;
    }
    close FILE;

    my $filedigest = _calc_digest($buffer);

    return $filedigest;
}


### check digest of file
sub bennofile_digest
{
    my ($repofile) = @_;

    if (! open REPO, $repofile) {
        die "WARN Cannot check: $repofile: $!\n";
    }
    print "Archive file: " if $verbose;
    print "$repofile\n";

    my $z = new IO::Uncompress::Gunzip $repofile
                   or die "gunzip failed: $GunzipError\n";

    # skip header
    my $headerdigest;
    while (my $line = $z->getline) {
        my $hash_re = qr/^===== Hash:\s(.+)$/;
        last if (($headerdigest) = $line =~ $hash_re);
    }
    $headerdigest =~ s/\r//g;
    if (!$headerdigest) {
        print STDERR "No bennobox file: $repofile\n";
        return 0;
    }

    my $buffer;
    while (my $line = $z->getline) {
        $buffer .= $line;
    }

    my $digest = _calc_digest($buffer);
}


### rename file in repository
sub rename_repofile
{
    my ($repofile_path) = @_;

    (my $repofile_prereimport = $repofile_path) =~
                                            s/([^\/]+\.gz)$/PRE-REIMPORT_$1/;

    if (-e $repofile_prereimport) {
        die "File exists : $repofile_prereimport\n";
    }
    else {
        if (rename $repofile_path, $repofile_prereimport) {
            print_log("Rename $repofile_path -> $repofile_prereimport");
        }
        else {
            die "Rename of $repofile_path to $repofile_prereimport failed: $!\n";
        }
    }
}


### rename_importfile
sub rename_importfile
{
    my ($file) = @_;

    (my $importfile = $file) =~ s/\.err$/.eml/;
    eval {
        link $file, $importfile or die "Rename error: $! $importfile\n";
        unlink $file or die "Cannot unlink $file: $!\n";
    };
    print STDERR "$@\n" if $@;
}


### print_log
sub print_log
{
    my ($message) = @_;

    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
    #                       0-11,+1900,0-6  ,0-366,SZ=(+)
    #                                 ,So-Sa,0-366,WZ=0,NA=(-)
    
    $year += 1900;
    $mon  += 1; $mon = sprintf("%02d",$mon);
    $mday = sprintf("%02d",$mday);
    $hour = sprintf("%02d",$hour);
    $min  = sprintf("%02d",$min);
    $sec  = sprintf("%02d",$sec);

    print LOG "$mday.$mon.$year $hour:$min:$sec $message\n";
}


### calc_digest
sub _calc_digest
{
    my ($buffer) = @_;

    my $ctx = Digest::SHA->new('256');
    my $digest = $ctx->add($buffer)->hexdigest;

    my $filedigest = uc $digest;

    return $filedigest;
}


sub help_exit
{
    print "Usage: benno-reimport [-h] [-v] [-b <boxdir>] [-i <inbox> | -e <errorfile>]>\n";
    print "Renames the Benno MailFile in repo to .error-renamed-* an rename\n";
    print ".err file to .eml file\n";
    print "\n";
    print "  -h             this help\n";
    print "  -b <boxdir>    box or repo directory (/srv/benno/archvive/repo)\n";
    print "  -e <errorfile> .err file in inbox\n";
    print "  -i <inbox>     inbox directory (/srv/benno/inbox)\n";
    print "  -E             envelope header (X-REAL*) in checksum\n";
    print "  -r             rename file (default show files to rename)\n";
    print "  -s             errorfile suffix (.err)\n";
    print "  -v             verbose\n";
    print "  -w             print warnings\n";
    print "  -l <logfile>   logfile path (/var/log/benno/reimport.log)\n";
    print "\n";
    exit 0;
}
