#!/usr/bin/perl
#
#
#
use strict;
use warnings;
use Getopt::Std;
use MIME::Base64 ();
use File::Temp qw( :POSIX );
use Digest::SHA qw(sha256);

$main::VERSION = '2.8.0';

our %opts;
getopts('DhVa:c:C:g:i:K:l:p:P:R:u:',\%opts);

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

my %conf = read_config($opts{c}) if $opts{c};
my $auth_module    = $opts{a} || $conf{auth_module}  ;
my $background     =             $conf{background}   || 1;
my $DEBUG          = $opts{D} || $conf{DEBUG}        ;

my $header_name    =             $conf{header_name} || 'X-BENNO-GW';
my $inbox          = $opts{i} || $conf{inbox}        || '/srv/benno/inbox';
my $ipversion      =             $conf{ipversion}    || '*';
my $loglevel       = $opts{l} || $conf{log_level}    || 1;
my $port           = $opts{p} || $conf{port}         || 21543;
my $pidfile        = $opts{P} || $conf{pid_file}     || undef;
my $runuser        = $opts{u} || $conf{runuser}      || 'benno';
my $rungroup       = $opts{g} || $conf{rungroup}     || 'www-data';
my $secretheader   = $opts{S} || $conf{secretheader} || 'X-REAL-MAILFROM,X-REAL-RCPTTO,X-BENNO-GW';

my $ssl_cert       = $opts{C} || $conf{ssl_cert}     ;
my $ssl_key        = $opts{K} || $conf{ssl_key}      ;
                                  
my $logfile        =             $conf{log_file}     || 'Sys::Syslog';

# only config file
my $server_type    = $conf{server_type}              || 'PreForkSimple';
my $max_servers    = $conf{max_servers}              || 10;
my $max_requests   = $conf{max_requests}             || 100;



if ($DEBUG) {
    $background = 0;
    $loglevel   = 4;
    $logfile    = undef;
}
help_exit('No auth module given') if ! $conf{auth_module};

Benno::REST::Server->run(
    background      => $background,
    log_level       => $loglevel,
    port            => $port.'/SSL',
    SSL_cert_file   => $ssl_cert,
    SSL_key_file    => $ssl_key,

    #access_log      => 'log/access.log',
    log_file        => $logfile,
    syslog_ident    => 'benno-import',
    pid_file        => $pidfile,
    server_type     => $server_type,
    max_servers     => $max_servers,
    max_requests    => $max_requests,
    ipv             => $ipversion,
);
exit;

sub Benno::REST::Server::configure_hook {
    my $self = shift;

    $self->{BennoAuth}     = new Benno::Auth($auth_module);
    $self->{benno_inbox}   = $inbox;
    $self->{benno_header}  = $header_name;

    my $prop = $self->{'server'};
    $prop->{user}     = $runuser;
    $prop->{group}    = $rungroup;
    $prop->{pid_file} = $pidfile;

    $prop->{syslog_logsock} = 'native';

    my $hregex = '^(';
    foreach my $h (split /,/,$secretheader) {
        $hregex .= $h.'|';
    }
    chop($hregex);
    $hregex .= '):';
    $self->{benno_sheaderRE} = qr($hregex);
}


### SUBS ###
sub help_exit
{
    my $msg = shift;

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

    print "USAGE $0 [-c <configfile>] <args>\n";
    print "\n";
    print "  -C <ssl cert>      SSL certificate\n";
    print "  -K <ssl key>       SSL key\n";
    print "\n";
    print "  -P <port>          bind port (default: 21543)\n";
    print "  -r <runuser>       run as user <runuser> (default: benno)\n";
    print "  -a <authmodule>    filename auf authmodule\n";
    print "  -i <inboxdir>      store files in inbox directory (/srv/benno/inbox)\n";
    print "  -l <loglevel>      loglevel (default: 1)\n";
    print "  -D                 debug, run in foreground with loglevel 4\n";
    print "  -V                 print version\n";
    print "\n";

    exit 0;
}


### read_config
#
# 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);
      next if ! $val;
      # strip ws
      $var =~ s/\s//g;
      $val =~ s/^\s+//g;
      $val =~ s/\s+$//g;
      $config{$var} = $val;
  }
  close CONF;
  return %config;
}


### EOP ###
1;

package Benno::Auth;
#
#
#
use strict;
use IPC::Open3;

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

    my $self = {
        module      => $module,
        retlines    => [],
        errlines    => [],
        AUTH_OK     => 0,
    };
    bless $self, $class;

    return $self;
}


sub check
{
    my ($self,$user,$pass) = @_;

    my $archives = [];
    my($wtr, $rdr, $err);
    use Symbol 'gensym'; $err = gensym;
    my $pid = open3($wtr, $rdr, $err, $self->{module});
    print $wtr "$user\n$pass";

    close $wtr;
    waitpid( $pid, 0 );
    my $child_exit_status = $? >> 8;
    my @err = <$err>;
    my $failed = undef;
    foreach my $line (@err) {
        $failed = 1;
        chomp $line; chomp $line;
        push @{$self->{errlines}}, "$self->{module}: $line";
    }
    my @ret = <$rdr>;
    foreach my $line (@ret) {

        chomp $line; chomp $line;
        if ($line =~ /^ARCHIVE\s+?(.+)$/) {
            push @{$archives},$1;
        }
        if ($line =~ /^ERR/) {
            die $line;
        }
        if ($line =~ /^AUTH\sBREAK/) {
            die 'AUTH BREAK';
        }
        if ($line =~ /^AUTH\s+OK/) {
            $self->{AUTH_OK} = 1;
            next;
        }
        push @{$self->{retlines}}, $line;
    }
    die 'ERR INTERNAL' if $failed;
    die 'ERR AUTH' if ! $self->{AUTH_OK};
    return $archives;
}


sub get_archive { return $_[0]->{archive}; }


# EOP
1;


package Benno::REST::Server;
use base qw(Net::Server::HTTP);
###----------------------------------------------------------------###

#sub post_configure_hook {
#    my $self = shift;
#
#}

sub send_error {
    my ($self,$n,$msg) = @_;

    $self->log(1,"ERROR $n $msg");
    $self->send_status($n);
    print "Content-type: text/plain\r\n\r\n";
    print "Error $n:$msg";
    return $self->send_status($n,$msg);
}


sub log_error {
    my ($self,$msg) = @_;

    my $client = $self->get_client_info;

    my $errno = time;
    $self->log(1,"ERROR [$errno] $msg");
    return $self->send_500('ERROR '.$errno);
}


sub process_http_request {
    my $self = shift;

    my $archives;

    # _lw_
    #print STDERR "$_ => $ENV{$_}\n" for (keys %ENV);

    if ($ENV{REQUEST_URI} !~ /^\/rest\/inbox\/?$/) {
        $self->log_error("BAD ENDPOINT: $ENV{REQUEST_URI}");
        return $self->send_status(400, 'BAD REQUEST');
    }
    if ($ENV{REQUEST_METHOD} !~ /^PUT$/) {
        $self->log_error("BAD METHOD: $ENV{REQUEST_METHOD}");
        return $self->send_status(400, 'BAD REQUEST');
    }

    ## AUTHENTICATION
    my $authstring = (split /\s/, $ENV{HTTP_AUTHORIZATION})[1];
    my ($buser,$bpass) = split /:/, MIME::Base64::decode($authstring),2;
    eval {
        $archives = $self->{BennoAuth}->check($buser,$bpass);
    };
    if ($@) {
        if (my $errlines = join '|', @{$self->{BennoAuth}->{errlines}}) {
            $self->log(1,"ERROR AUTHMODULE: $errlines");
        }
        return $self->send_error(401,'Unauthorized');
    }

    my $buffer = '';
    if($ENV{CONTENT_LENGTH}) { read(STDIN,$buffer,$ENV{CONTENT_LENGTH}); }
    my $secretheader = '';
    if ($buffer =~ /^$/) {
        $self->log(1,"ERROR Empty file sent");
        return $self->send_error(400,'Bad Request');
    }

    my @lines = split /\n/, $buffer, -1;
    my $rawbuffer = '';
    foreach my $line (@lines) {
        if ($line =~ /$self->{benno_sheaderRE}/) {
            $secretheader .= $line."\n";
            next;
        }
        $rawbuffer .= $line."\n";       # first "normal" header
    }
    chomp $rawbuffer;                   # last newline not in original

    # Checksum (without SECRETHEADERS!)
    my $ctx = Digest::SHA->new('256');
    my $digest = uc $ctx->add($rawbuffer)->hexdigest;

    # write file to inbox
    my $alist;
    foreach my $archive (@{$archives}) {
        $alist .= $archive.':'; 
    }
    chop $alist;
    my $tmpfile = $self->{benno_inbox}.'/'.$digest.'_'.$alist.'_'.$$.'-rest.tmp';
    my $emlfile = $self->{benno_inbox}.'/'.$digest.'_'.$alist.'_'.$$.'-rest.eml';
    open my $fh, '>',$tmpfile
        or return $self->log_error("Cannot open file: $tmpfile: $!",$$);

    foreach my $archive (@{$archives}) {
        print $fh "$self->{benno_header}: $archive\r\n";
    }
    print $fh $secretheader;
    print $fh $rawbuffer;
    close $fh
        or return $self->log_error("Cannot create tmpfile file: $tmpfile: $!",$$);

    if (! link $tmpfile, $emlfile) {
        unlink $tmpfile;
        return $self->log_error("Cannot create $emlfile: $!", $$);
    }

    unlink $tmpfile;

    $self->log('info','Email stored as: '.$emlfile);
    print "Content-type: text/plain\r\n";
    print "\r\n";
    print "OK $digest\r\n";
}



### EOP ###
1;

