#!/bin/bash

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

if [ -e $(dirname $0)/../lib/Thruk.pm ]; then
  export PERL5LIB="$PERL5LIB:$(dirname $0)/../lib";
  if [ "$OMD_ROOT" != "" -a "$THRUK_CONFIG" = "" ]; then export THRUK_CONFIG="$OMD_ROOT/etc/thruk"; fi
  if [ -z $THRUK_CONFIG ]; then export THRUK_CONFIG="$(dirname $0)/../"; fi
elif [ ! -z $OMD_ROOT ]; then
  export PERL5LIB=$OMD_ROOT/share/thruk/lib:$PERL5LIB
  if [ -z $THRUK_CONFIG ]; then export THRUK_CONFIG="$OMD_ROOT/etc/thruk"; fi
else
  export PERL5LIB=$PERL5LIB:/usr/share/thruk/lib:/usr/lib64/thruk/perl5;
  if [ -z $THRUK_CONFIG ]; then export THRUK_CONFIG='/etc/thruk'; fi
  if [ -z $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 24

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

$|          = 1;
my $verbose = 0;
$verbose    = $ENV{'THRUK_COOKIE_AUTH_VERBOSE'} if $ENV{'THRUK_COOKIE_AUTH_VERBOSE'};

my $guest;
$guest = $ENV{'THRUK_COOKIE_AUTH_GUEST_USER'} if $ENV{'THRUK_COOKIE_AUTH_GUEST_USER'};

##########################################################
my $config              = Thruk::Config::get_config();
my $spath               = $config->{'var_path'}."/sessions";
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;
my $pass_regex          = qr#^$prefix(startup\.html|themes|javascript|images|cgi\-bin/(login|remote|restricted)\.cgi)#mx;
my $direct_auth_agents  = $config->{'cookie_auth_direct_agents'} || '(wget|curl|python|Go-http-client)';
my $direct_auth_regex   = qr#$direct_auth_agents#mxi;
my $cookie_regex        = qr/thruk_auth=(\w+)/mx;
my $session_split       = qr/~~~/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) = @_;

    if($in =~ $line_regex) {
        my($cookies, $extra, $path) = ($1, $2, $3);

        $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 access
        if($extra->[1] && $extra->[2] =~ $direct_auth_regex) {
            print STDERR "thruk_auth: script bypass cookie auth for $path" if $verbose;
            my $session = Thruk::Utils::CookieAuth::external_authentication($config, {'Authorization' => $extra->[1]}, undef, $extra->[0]);
            if($session && $session ne '-1') {
                print STDERR "thruk_auth: script login successful" if $verbose;
                my $sessionfile = $spath.'/'.$session;
                chomp(my $cont = read_file($sessionfile));
                my($basicauth, $ip, $user) = split($session_split, $cont, 3);
                unlink($sessionfile);
                return "/loginok/".$user."/".$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?
        my $sessionfile = $spath.'/'.$auth;
        if(!-f $sessionfile) {
            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";
        }

        chomp(my $cont = read_file($sessionfile));
        my($basicauth, $ip, $user) = split($session_split, $cont, 3);

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

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

        # revalidation disabled
        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, $sessionfile);
        }
        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($sessionfile);
                delete $sessioncache->{$auth};
                return $loginurl."?problem&".$path."\n";
            }
            if($rc == 0) {
                print STDERR "thruk_auth: basic auth verify failed for ".$user if $verbose;
                unlink($sessionfile);
                delete $sessioncache->{$auth};
                return $loginurl."?invalid&".$path."\n";
            }
            # update mtime of sessionfile
            utime($now, $now, $sessionfile);
        }

        # 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";
    }

    # everything else redirects to login
    print STDERR "thruk_auth: unknown url ".$in if $verbose;
    return $loginurl."\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;
    }
    # cleanup old session files
    Thruk::Utils::CookieAuth::clean_session_files($config);
    return;
}
