#!/usr/bin/perl
#
#
#
use strict;
use Getopt::Std;
use Sys::Syslog;
use Parallel::ForkManager;
use Benno;
use Benno::Emlfile;
use Benno::Import::RESTClient;

no warnings 'utf8';

$main::VERSION = '2.10.3';

our(%opts);
getopts('dDehntTvVc:C:E:H:i:p:P:R:s:u:w:',\%opts);

if ($opts{V}) { print "$main::VERSION\n"; exit; }

my %conf = Benno->config($opts{c}) if $opts{c};

my $delete          = $opts{d} || $conf{delete};
my $DEBUG           = $opts{D} || $conf{DEBUG}          || $ENV{DEBUG};

my $concurrency     = $opts{C} || $conf{concurrency}    || 3;
my $emailregex      = $opts{E} || $conf{emailregex}     || '^.+\.eml$';
my $hostname        = $opts{H} || $conf{hostname};
my $inbox           = $opts{i} || $conf{inbox}          || '/srv/benno/inbox';
my $noverifycert    = $opts{n} || $conf{noverifycert};
my $password        = $opts{p} || $conf{password};              # password or jwt_secret
my $port            = $opts{P} || $conf{port}           || 21543;
my $runuser         = $opts{R} || $conf{runuser}        || 'benno';
my $username        = $opts{u} || $conf{username};
my $suffix          = $opts{s} || $conf{suffix};
my $testmode        = $opts{t} || $conf{testmode};
my $errfile         = $opts{e} || $conf{errorfile};
my $whitelist       = $opts{w} || $conf{whitelist};
my $verbose         = $opts{v};

my $endpoint        = $opts{T} || $conf{endpoint}       || '/rest/inbox';
$endpoint =~ s!/+$!!g;

help_exit() if $opts{h};
help_exit('Hostname not set.') if ! $hostname;

Benno->run_as($runuser);
openlog('benno-cloudimport','nowait,pid','mail');

my $uri = 'https://'.$hostname.':'.$port.$endpoint;
print "Send to $uri\n" if $verbose;

my $RC = Benno::Import::RESTClient->new($uri,
        { noverifycert => $noverifycert,
          DEBUG => $DEBUG,
        });

if ($endpoint =~ /^\/rest\/inbox$/) {
    $RC->auth_basic($username,$password);
}
elsif ($endpoint =~ /^\/rest\/archive$/) {
    $RC->auth_bearer($password);
}
else {
    die "Endpoint not correct: $endpoint\n";
}


my $Whitelist = Whitelist->new($whitelist);

## READ FILES FROM DIRECTORY ###################################################
my $msg = "Read files from \"$inbox\"";
$msg .= ' and delete after sent'    if $delete;

syslog('INFO',$msg);
print "$msg\n" if $verbose;

# read directory and process files
opendir my $dh, $inbox or die "Cannot read directory \"$inbox\": $!";
my $return;

my $pm = Parallel::ForkManager->new($concurrency);
CONCURRENTS:
foreach my $filename (readdir $dh) {
    next if $filename =~ /^\./;
    next if $filename !~ /$emailregex/;
    chomp $filename;
    my $file = "$inbox/$filename";
    $file =~ s!//!/!g;

    if (-z $file) {
        syslog('WARNING',"Skip empty file: $file");
        print "Skip empty file: $file\n" if -t STDOUT;
        next;
    }

    $pm->start and next CONCURRENTS; # do the fork
    print "$file will be import to $hostname:$port" if $verbose;
    next if $testmode;
    eval {
        my $Emlfile = new Benno::Emlfile($file);
        if ($Whitelist->check_addresses($Emlfile->envelope_from,$Emlfile->envelope_to)) {
            syslog('INFO',"Email address(es) of $file in whitelist") if $verbose;
            print "Email address(es) of $file in whitelist\n" if -t STDOUT;


            $return = $RC->upload_mail($Emlfile->content());
            syslog('INFO',"Email \"$filename\" archived: $return");
            print "Email \"$filename\" archived: $return\n" if $verbose;
        }
        else {
            syslog('INFO',"Skip $file: address(es) not in whitelist") if $verbose;
            print "Skip $file: address(es) not in whitelist\n" if -t STDOUT;
        }
        if ($suffix) {
            my $ufile = $file.".".$suffix;
            rename $file, $ufile or die "Cannot rename $file to $ufile: $!\n";
        }
    };
    if ($@) {
        my $err = $@;
        syslog('ERR',"Cannot send file \"$file\": $err");
        print "Cannot send file \"$file\": $err";
        if ($errfile) {
            my $errfile = $file.'.err';
            link $file, $errfile or warn "Cannot rename $filename to $errfile: $!\n" and $pm->finish;
            unlink $file;
        }
        # do not remove file
        $pm->finish;
    }
    if ($delete) {
        unlink $file or print STDERR "Cannot delete $file: $!\n" and $pm->finish;
        print "Delete $file from $inbox.\n" if -t STDOUT;
    }

    $pm->finish; # do the exit in the child process
}
$pm->wait_all_children;
closedir $dh;
closelog;
## /READ FILES FROM DIRECTORY ##################################################



### SUBS ###
###
# read configuration from file
#
sub read_config
{
my $configfile = shift;
my %config;
# _very_ simple config file parser
#
# Config format:   var = val
#
open CONF, "$configfile" or die "Cannot open config file $configfile. $!\n";
foreach my $line (<CONF>) {
  next if $line =~ /^$/;
  next if $line =~ /^#/;
  chomp $line;
  my ($var,$val) = split(/=/, $line,2);
  # strip ws
  $var =~ s/\s//g;
  $val =~ s/^\s+//g;
  $val =~ s/\s+$//g;
  $config{$var} = $val;
}
close CONF;
return %config;
}


### print help and exit
sub help_exit
{
my $msg = shift;

if ($msg) {
    print $msg,"\n\n";
}

print "Usage: $0 [-h] <-H <hostname> [<options>]\n";
print "\n";
print "     -c <configfile>  configuration file\n";
print "     -d               delete email after processing\n";
print "     -H <hostname>    hostname of benno-import-rest server\n";
print "     -P <port>        port (default: 21543)\n";
print "\n";
print "     -u <username>    username\n";
print "     -p <password>    password or jwt-secret for archive endpoint\n";
print "\n";
print "     -C <concurrency> number of concurrent uploads (default: 3)\n";
print "     -e               rename upload file to .err on errors (default: leave unchanged)\n";
print "     -s <extension>   append <extension> to uploaded file (default: leave unchanged)\n";
print "     -i <importdir>   read eml-files from this dir\n";
print "     -n               no verification of server certficate\n";
print "     -t               test mode do not upload or delete mail\n";
print "     -w <whitelist>   whitelist file (localpart\@domain or \@domain per line)\n";
print "                      an empty whitelist matches always true\n";
print "     -E <emailregex>  email name regex (default: \"^.+\.eml\")\n";
print "     -R <username>    run as user (default: benno)\n";
print "\n";
print "     -V               print version\n";
print "     -v               verbose\n";
print "     -h               print this help\n";
print "\n";

exit 1;
}


### EOP ###
1;

package Maildata;

sub new {
my $class = shift;
my ($filename) = @_;

my $self = {
    filename    => $filename,
    content     => '',
    sender      => [],
    recipients  => [],
};
bless $self, $class;

$self->_init();

return $self;
}


sub content         { return $_[0]->{content};          }
sub sender_list     { return @{$_[0]->{sender}};        }
sub recipient_list  { return @{$_[0]->{recipients}};    }


sub _init
{
my ($self) = @_;

my $filename = $self->{filename};
my $fh;
my $content;
if ($filename eq '--') {
    $fh = \*STDIN;
    $self->_read_file($fh);
}
else {
    open $fh, $filename or die "Error read $filename: $!\n";
    $self->_read_file($fh);
    close $fh;
}
}


# no unfolding!
sub _read_file
{
my ($self,$fh) = @_;

my $in_header = 1;
foreach my $line (<$fh>) {
    if ($in_header) {
        if ($line =~ /^X-REAL-MAILFROM:\s*?(\S.*?)\R/) {
            my $value = $1; $value =~ s/[<>]//g;
            chomp $value;
            push @{$self->{sender}}, $value;
        }
        if ($line =~ /^X-REAL-RCPTTO:\s*?(\S.*?)\R/) {
            my $value = $1; $value =~ s/[<>]//g;
            chomp $value;
            push @{$self->{recipients}}, $value;
        }
    }
    $self->{content} .= $line;
    if ($line =~ /^\R/) { $in_header = 0; }
}

if ($in_header) {
    # exit if no header found
    $ENV{DEBUG} eq 'maildata' &&  print STDERR "DATA READ: ",$self->{base_content},"\n";
    die "Maildata corrupt. Try environment variable DEBUG=maildata to see data\n";
}
}


### EOP ###
1;

package Whitelist;

sub new {
my $class = shift;
my ($wlfile) = @_;

my $self = [];
bless $self, $class;

if (-f $wlfile) {
    $self->_init($wlfile);
}
else {
    if (-t STDOUT) {
        print STDERR "WARNING whitelist file does not exist";
        print STDERR ": $wlfile" if $wlfile;
        print ".\n";
    }
}

return $self;
}


sub _init
{
my ($self,$wlfile) = @_;

my @whitelist;
open my $wl, '<', $wlfile or die "Cannot open whitelist file $wlfile: $!\n";
foreach my $line (<$wl>) {
    chomp $line;
    $line =~ s/^\s+//;
    $line =~ s/\s+$//;
    next if $line =~ /^#/;
    next if $line !~ /\@/;

    print "(DEBUG) add entry $line to whitelist\n" if $DEBUG;
    push @{$self}, $line;
}
close $wl;

return @whitelist;
}



### check if address(es) in whitelist
#           
# returns 1 if address or domain is in whitelist
# returns 1 if whitelist is empty (always true)
#    
sub check_addresses
{
my ($self,@addresslist) = @_;

# empty whitelist
if (!@{$self}) {
    return 1;       # always true
}

my $iswl = 0;
foreach my $mailaddr (@addresslist) {
    foreach my $wladdr (@{$self}) {
        next if $wladdr =~ /^#/;
        next if $wladdr =~ /^$/; 
        my $check_mailaddr = lc $mailaddr;
        my $check_wladdr   = lc $wladdr;
        if ($wladdr =~ /^\@/) {
            if ($check_mailaddr =~ qr/$check_wladdr$/) {   # domain name
                $iswl = 1;
                print "(DEBUG) whitelist match: $check_mailaddr -> $check_wladdr\n" if $DEBUG;
            }
        }
        else {
            if ($check_mailaddr =~ qr/^$check_wladdr$/) {   # email address
                $iswl = 1;
                print "(DEBUG) whitelist match: $check_mailaddr -> $check_wladdr\n" if $DEBUG;
            }
        }
    }
}

return $iswl;
}


### EOP ###
1;


package LWs::SingleInstance;

use strict;

use Fcntl ':flock';

#
# Exit program if more than one instance is runningg
#
INIT {
if (tell(*main::DATA) == -1) {
    # __DATA__ handle not available
    print STDERR "$0 needs an __END__ literal at the end of the file.\n";
    exit 2;
}
elsif (!flock main::DATA, LOCK_EX | LOCK_NB) {
    use Sys::Syslog;
    openlog('benno-cloudimport','nowait,pid','mail');
        # cannot lock __DATA__ "file"
        print STDERR "An instance of $0 is already running.\n" if -t STDOUT;
        syslog('ERR',"An instance of $0 is already running.\n");
        exit 1;
    }
}

### EOP ###
1;


__END__

