#!/bin/bash

# read rc files if exist
[ -e ~/.profile ] && . ~/.profile
[ -e ~/.thruk   ] && . ~/.thruk

BASEDIR=$(dirname $0)/..

# git version
if [ -d $BASEDIR/.git -a -e $BASEDIR/lib/Thruk.pm ]; then
  export PERL5LIB="$PERL5LIB:$BASEDIR/lib";
  if [ "$OMD_ROOT" != "" -a "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG="$OMD_ROOT/etc/thruk"; fi
  if [ "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG="$BASEDIR/"; fi

# omd
elif [ "$OMD_ROOT" != "" ]; then
  export PERL5LIB=$OMD_ROOT/share/thruk/lib:$PERL5LIB
  if [ "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG="$OMD_ROOT/etc/thruk"; fi

# pkg installation
else
  export PERL5LIB=$PERL5LIB:/usr/share/thruk/lib:/usr/lib64/thruk/perl5;
  if [ "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG='/etc/thruk'; fi
  if [ "$REMOTEURL" = "" ]; then export REMOTEURL='http://localhost/thruk/cgi-bin/remote.cgi'; fi
fi

eval 'exec perl -x $0 ${1+"$@"} ;'
    if 0;

#! -*- perl -*-
# vim: expandtab:ts=4:sw=4:syntax=perl
#line 33

use warnings;
use strict;
use File::Slurp;
use Time::HiRes qw( gettimeofday tv_interval );
use Thruk::Config;
use Thruk::Utils::CookieAuth;
use Thruk::Utils::IO;
use Thruk::Utils::APIKeys;

$| = 1;

##########################################################
my $config              = Thruk::Config::get_config();
my $verbose             = $ENV{'THRUK_COOKIE_AUTH_VERBOSE'} // $config->{'cookie_auth_verbose'} // 0;
my $guest               = $ENV{'THRUK_COOKIE_AUTH_GUEST_USER'} || undef;
my $secret_file         = $config->{'var_path'}.'/secret.key';
my $urlprefix           = $config->{'url_prefix'};
my $loginurl            = $config->{'cookie_auth_login_url'}       || $urlprefix."cgi-bin/login.cgi";
my $sessiontimeout      = $config->{'cookie_auth_session_timeout'} || 86400;
my $sessioncachetimeout = defined $config->{'cookie_auth_session_cache_timeout'} ? $config->{'cookie_auth_session_cache_timeout'} : 30;
my $sessioncache        = {};
my $login_timeout       = defined $config->{'cookie_auth_login_timeout'} ? $config->{'cookie_auth_login_timeout'} : 10;
$loginurl               = "/redirect/".$loginurl;
$loginurl               =~ s|//|/|gmx;
my $line_regex          = qr|^/(.*?)/(.*?)/____/(.*)$|mx;
my $prefix              = $urlprefix; $prefix =~ s|^/||mx;
# if changed, adjust AddDefaults.pm as well
my $pass_regex          = qr#^$prefix(startup\.html|themes|javascript|cache|vendor|images|usercontent|cgi\-bin/(login|remote|restricted)\.cgi)#mx;
my $cookie_regex        = qr/thruk_auth=(\w+)/mx;
my $last_cache_clean    = 0;

##########################################################
while (<STDIN>) {
    chomp(my $in = $_);

    # do not accidentally print to stdout
    select STDERR;

    my $t0 = [gettimeofday];
    print STDERR "thruk_auth: url '$in' " if $verbose > 1;
    my $res = process($in);
    print STDERR "\nthruk_auth:  -> $res" if $verbose > 1;
    print STDOUT $res;
    my $elapsed = tv_interval ( $t0 );
    printf(STDERR " (took %.3f sec)\n",$elapsed) if $verbose;
}

##########################################################
sub process {
    my($in) = @_;

    my($cookies, $extra, $path);
    if($in =~ $line_regex) {
        ($cookies, $extra, $path) = ($1, $2, $3);
    } else {
        # everything else redirects to login
        print STDERR "thruk_auth: unknown url ".$in if $verbose;
        return $loginurl."\n";
    }

    $extra = [split(/~~/mx, $extra)];

    # remove last &
    $path =~ s|/____/|?|mxo;
    $path =~ s|\?$||mxo;

    # some urls must pass
    if($path =~ $pass_regex) {
        print STDERR "thruk_auth: pass $path" if $verbose;
        return "/pass/$path\n";
    }

    # direct login by secret/api key
    if($extra->[2] && $extra->[2] =~ m/[a-zA-Z0-9_]+/mx) {
        my $secret_key  = read_file($secret_file) if -s $secret_file;
        chomp($secret_key);
        if($extra->[3] && $extra->[2] eq $secret_key) {
            my $user = $extra->[3];
            return "/loginok/".$user."/".$path."\n";
        }
        if($config->{'api_keys_enabled'}) {
            my $data = Thruk::Utils::APIKeys::get_key_by_private_key($config, $extra->[2]);
            if($data) {
                my $user = $data->{'user'};
                if($data->{'superuser'}) {
                    $user = (defined $extra->[3] && $extra->[3] ne '') ? $extra->[3] : '(api)';
                }
                Thruk::Utils::IO::json_lock_patch($data->{'file'}, { last_used => time() }, { pretty => 1});
                return "/loginok/".$user."/".$path."\n" if defined $user;
                return "/pass/".$path."\n";
            }
        }
        return "/pass/".$prefix."cgi-bin/error.cgi?error=27\n";
    }

    # direct access with basic auth
    if($extra->[1]) {
        print STDERR "thruk_auth: script bypass cookie auth for $path" if $verbose;
        my $sessionid = Thruk::Utils::CookieAuth::external_authentication($config, {'Authorization' => $extra->[1]}, undef, $extra->[0]);
        if($sessionid && $sessionid ne '-1') {
            print STDERR "thruk_auth: script login successful" if $verbose;
            my $session = Thruk::Utils::CookieAuth::retrieve_session(config => $config, id => $sessionid);
            unlink($session->{'file'});
            return "/loginok/".$session->{'username'}."/".$path."\n";
        } else {
            print STDERR "thruk_auth: script login failed" if $verbose;
        }
    }

    # did we get a cookie
    if($cookies eq '' or $cookies !~ $cookie_regex) {
        print STDERR "thruk_auth: no cookie $path" if $verbose;
        return "/loginok/".$guest."/".$path."\n" if $guest;
        return $loginurl."?".$path."\n";
    }
    my $auth = $1;

    # use session cache for a few seconds
    my $now = time();
    if($sessioncachetimeout > 0 and defined $sessioncache->{$auth} and $sessioncache->{$auth}->{'time'} > $now - $sessioncachetimeout) {
        print STDERR "thruk_auth: login by cache hit: ".$sessioncache->{$auth}->{'login'} if $verbose;
        return "/loginok/".$sessioncache->{$auth}->{'login'}."/".$path."\n";
    }

    # does our sessionfile exist?
    print STDERR "thruk_auth: id => ".$auth."\n" if $verbose;
    my $session = Thruk::Utils::CookieAuth::retrieve_session(config => $config, id => $auth);
    if(!$session) {
        print STDERR "thruk_auth: session expired: ".($sessioncache->{$auth}->{'login'} || '?') if $verbose;
        delete $sessioncache->{$auth} if defined $sessioncache->{$auth};
        return "/loginok/".$guest."/".$path."\n" if $guest;
        return $loginurl."?expired&".$path."\n";
    }

    # fill sessioncache from mtime initially, so we don't have to revalidate new sessions immediately
    my $user = $session->{'username'};
    if(!$sessioncache->{$auth}) {
        my @stat = stat($session->{'file'});
        $sessioncache->{$auth} = {
                'login' => $user,
                'time'  => $stat[9],
        };
    }

    # session timeout reached?
    if($sessioncachetimeout > 0 && defined $sessioncache->{$auth} && $sessioncache->{$auth}->{'time'} < $now - $sessiontimeout) {
        print STDERR "thruk_auth: session timeout: ".$sessioncache->{$auth}->{'login'} if $verbose;
        unlink($session->{'file'});
        delete $sessioncache->{$auth};
        return "/loginok/".$guest."/".$path."\n" if $guest;
        return $loginurl."?expired&".$path."\n";
    }

    # revalidation disabled
    my $basicauth = $session->{'hash'};
    if($sessioncachetimeout == 0 || ($basicauth && $basicauth eq 'none')) {
        # no validation enabled
        print STDERR "thruk_auth: session ok (not revalidated)" if $verbose;
        # uptime mtime of sessionfile
        utime($now, $now, $session->{'file'});
    }
    else {
        # revalidate session data
        my $rc;
        $SIG{'ALRM'} = sub { die("timeout during auth check"); };
        eval {
            alarm($login_timeout);
            $rc = Thruk::Utils::CookieAuth::verify_basic_auth($config, $basicauth, $user, $login_timeout-1);
        };
        alarm(0);
        if($rc == -1 or $@) {
            print STDERR "thruk_auth: technical problem during login for ".$user if $verbose;
            print STDERR "thruk_auth: ".$@ if $@;
            unlink($session->{'file'});
            delete $sessioncache->{$auth};
            return $loginurl."?problem&".$path."\n";
        }
        if($rc == 0) {
            print STDERR "thruk_auth: basic auth verify failed for ".$user if $verbose;
            unlink($session->{'file'});
            delete $sessioncache->{$auth};
            return $loginurl."?invalid&".$path."\n";
        }
        # update mtime of sessionfile
        utime($now, $now, $session->{'file'});
    }

    # clean old session cache every hour
    clean_session_cache() if($last_cache_clean < $now - 3600);

    # grant access
    $sessioncache->{$auth} = {
            'login' => $user,
            'time'  => $now,
    };
    print STDERR 'thruk_auth: basic auth ok for '.$user if $verbose;
    return "/loginok/".$user."/".$path."\n";
}

##########################################################
sub clean_session_cache {
    print STDERR "thruk_auth: session cache house keeping" if $verbose;
    $last_cache_clean = time();
    my $clean_before  = time() - $sessiontimeout - $sessioncachetimeout;
    for my $key (keys %{$sessioncache}) {
        delete $sessioncache->{$key} if $sessioncache->{$key}->{'time'} < $clean_before;
    }
    return;
}
