#!/usr/bin/perl -w
#
# warden-server.pl
#
# Copyright (C) 2011-2012 Cesnet z.s.p.o
# Author(s): 	Tomas PLESNIK 	<plesnik@ics.muni.cz>
#               Jan SOUKAL      <soukal@ics.muni.cz>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 3. Neither the name of the Cesnet z.s.p.o nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# This software is provided ``as is'', and any express or implied
# warranties, including, but not limited to, the implied warranties of
# merchantability and fitness for a particular purpose are disclaimed.
# In no event shall the Cesnet z.s.p.o or contributors be liable for
# any direct, indirect, incidental, special, exemplary, or consequential
# damages (including, but not limited to, procurement of substitute
# goods or services; loss of use, data, or profits; or business
# interruption) however caused and on any theory of liability, whether
# in contract, strict liability, or tort (including negligence or
# otherwise) arising in any way out of the use of this software, even
# if advised of the possibility of such damage.

package Warden;

use strict;
use SOAP::Lite;
use SOAP::Transport::TCP;
use File::Pid;
use POSIX;
use DBI;
use Format::Human::Bytes;
use Sys::Syslog qw(:DEFAULT setlogsock);
Sys::Syslog::setlogsock('unix');
use File::Basename;
use FindBin;
use Data::Dumper;
use Net::CIDR::Lite;
use DateTime;

our $VERSION = "0.1";

################################################################################
#			CONFIG FILE VARIABLES
################################################################################

my $script_name = $FindBin::Script;
my $conf_file = "/opt/warden-server/etc/warden-server.conf";

# first declaration of globa variables from config file
our $ADDRESS		= undef;
our $PORT		= undef;
our $LOGDIR		= undef;
our $PIDDIR		= undef;
our $VARDIR		= undef;
our $SSL_KEY_FILE	= undef;
our $SSL_CERT_FILE	= undef;
our $SSL_CA_FILE	= undef;
our $FACILITY		= undef;

# read config file
if ( ! open( TMP, $conf_file) ) {
  die errMsg("Can't read config file '$conf_file': $!\n");
}
close TMP;

# load set variables by user
if ( !do $conf_file ) {
  die errMsg("Errors in config file '$conf_file': $@");
}



################################################################################
#				VARIABLES
################################################################################
my $die_now	= 0;

# PID path
my $pid_file    = $PIDDIR . $script_name . ".pid";

# DB file
my $db_file 	= "warden.db";
my $db		= $VARDIR . $db_file;

# connect to DB - DBH is GLOBAL variable
my $dbargs = {AutoCommit => 0, PrintError => 1};
our $DBH = DBI->connect("dbi:SQLite:dbname=$db","","",$dbargs) or die errMsg("Can't connect to DB: $!");
#our $DBH = DBI->connect("DBI:mysql:database=warden;host=localhost", "root", "", {RaiseError => 1, mysql_auto_reconnect => 1}) || die "Could not connect to database: $DBI::errstr";


################################################################################
#				LOCAL FUNCTIONS
################################################################################

#-------------------------------------------------------------------------------
# errMsg - print error message and die
#-------------------------------------------------------------------------------
sub errMsg
{
  my $msg = shift;
  $msg = trim($msg);
  print $msg . "\n";
  exit 1;
} # End of errMsg


#-------------------------------------------------------------------------------
# trim - remove whitespace from the start and end of the string
#-------------------------------------------------------------------------------
sub trim
{
  my $string = shift;
  $string =~ s/^\s+//;
  $string =~ s/\s+$//;
  return $string;
} # End of trim


#-------------------------------------------------------------------------------
# write2log - writing message to syslog
#-------------------------------------------------------------------------------
sub write2log
{
  my $priority	= shift;
  my $msg 	= shift;
  my $filename	= File::Basename::basename($0);

  Sys::Syslog::openlog($filename, "cons,pid", $FACILITY);
  Sys::Syslog::syslog("$priority", "$msg");
  Sys::Syslog::closelog();
} # End of write2log


#-------------------------------------------------------------------------------
# signalHandler - catch signals and end the program if one is caught.
#-------------------------------------------------------------------------------
sub signalHandler
{
  $die_now = 1;    		# this will cause the "infinite loop" to exit
} # End of signalHandler


#-------------------------------------------------------------------------------
# sslErrorHandler - handle errors in SSL negitiation
#-------------------------------------------------------------------------------
sub sslErrorHandler
{
  my $socket 	= shift;
  my $msg	= shift;

  my $ip = $socket->peerhost;
  print $socket $msg;
  $socket->close;
  write2log ("err", "Caught SSL handshake error from $ip: $msg");
  return 1;
} # End of sslErrorHandler


#-------------------------------------------------------------------------------
# altNamesFilter - parse hostnames from subjectAltNames array for SQL
#		   IN operator in database query
#-------------------------------------------------------------------------------
sub altNamesFilter
{
  my $alt_names_array_ref = shift;
  my @alt_names_array = @$alt_names_array_ref;

  our $CN;
  my @an_array;

  push @an_array, $DBH->quote($CN);
  my $i = 1;
  while ($i <= scalar @alt_names_array) {
    push @an_array, $DBH->quote($alt_names_array[$i]);
    $i+=2;
  }
  my $an_filter = join(',', @an_array);
  return $an_filter;
}



################################################################################
# 				SOAP Functions
################################################################################

#-----------------------------------------------------------------------------
# saveNewEvent - save new received event into database
#-----------------------------------------------------------------------------
sub saveNewEvent
{
  my ($class, $data) = @_;
  my ($sth, $cidr_list);

  # variables defined by server
  our $IP;		# IP address of sender
  our $CN;		# common name of sender
  our $AN_FILTER;	# alternate names of sender

  my $cn_db = $DBH->quote($CN);

  # variables defined by server
  my $client_type	= "s";			# incoming client MUST be sender
  my $client_type_db	= $DBH->quote($client_type);
  my $valid		= "t";			# registered sender has valid events
  my $valid_db		= $DBH->quote($valid);
  my $received		= DateTime->now;	# time of event delivery (UTC)
  my $received_db	= $DBH->quote($received);

  # parse object (event) parameters
  my $service		= $data->{'SERVICE'};
  my $service_db	= $DBH->quote($service);
  my $detected		= $data->{'DETECTED'};
  my $detected_db	= $DBH->quote($detected);
  my $type		= $data->{'TYPE'};
  my $type_db		= $DBH->quote($type);
  my $source_type	= $data->{'SOURCE_TYPE'};
  my $source_type_db	= $DBH->quote($source_type);
  my $source		= $data->{'SOURCE'};
  my $source_db		= $DBH->quote($source);
  my $target_proto	= $data->{'TARGET_PROTO'};
  my $target_proto_db	= $DBH->quote($target_proto);
  my $target_port	= $data->{'TARGET_PORT'};
  my $target_port_db	= $DBH->quote($target_port);
  my $attack_scale 	= $data->{'ATTACK_SCALE'};
  my $attack_scale_db 	= $DBH->quote($attack_scale);
  my $note		= $data->{'NOTE'};
  my $note_db		= $DBH->quote($note);
  my $priority		= $data->{'PRIORITY'};
  my $priority_db	= $DBH->quote($priority);
  my $timeout		= $data->{'TIMEOUT'};
  my $timeout_db	= $DBH->quote($timeout);

  # Authorization of incomming client
  #-----------------------------------------------------------------------------

  # obtain cidr based on rigth common name and alternate names, service and client_type
 # $sth = $DBH->prepare("SELECT hostname, ip_net_client FROM clients WHERE hostname IN ($AN_FILTER) AND service = $service_db AND client_type = $client_type_db limit 1;");
 # if ( !defined $sth ) {die("Cannot prepare authorization statement in saveNewEvent: $DBI::errstr\n")}
 # $sth->execute;
 # my ($an, $cidr) = $sth->fetchrow();

 # # check if client is registered
 # if (!defined $cidr) {
 #   write2log ("err", "Unauthorized access to saveNewEvent from: $IP (CN: $CN; AN: $an) - client is not registered");
 #   die("Access denied - client is not registered at warden server!");
 # } else {
 #   $cidr_list = Net::CIDR::Lite
 #     -> new
 #     -> add($cidr);
 # }

  # check if client has IP from registered CIDR
 # if (!$cidr_list->bin_find($IP)) {
 #   write2log ("err", "Unauthorized access to saveNewEvent from: $IP (CN: $CN; AN: $an) - access from bad subnet: $cidr");
 #   die("Access denied - access from bad subnet!");
 # } else {
{
    # insert new event
    $DBH->do("INSERT INTO events VALUES (null,$cn_db,$service_db,$detected_db,$received_db,$type_db,$source_type_db,$source_db,$target_proto_db,$target_port_db,$attack_scale_db,$note_db,$priority_db,$timeout_db,$valid_db);");
    if ($DBH->err()) {die("Cannot do insert statement in saveNewEvent: $DBI::errstr\n")}
    #$DBH->commit();

    # log last inserted ID
    $sth = $DBH->prepare("SELECT last_insert_rowid()");
    if ( !defined $sth ) {die("Cannot prepare last ID statement in saveNewEvent: $DBI::errstr\n")}
    $sth->execute;
    my $id= $sth->fetchrow();
    write2log ("info", "Stored new event (#$id) from $IP (CN: $CN; AN: )");

    if (! defined $id) {
      write2log ("err", "Event from $IP ($CN) was not save: INSERT INTO events VALUES (null,$cn_db,$service_db,$detected_db,$received_db,$type_db,$source_type_db,$source_db,$target_proto_db,$target_port_db,$attack_scale_db,$note_db,$priority_db,$timeout_db,$valid_db);");
      die("Event was not save at warden server - database return empty ID!");
    } else {
      return 1;
    }
 } 
} # END of saveNewEvent


#-----------------------------------------------------------------------------
# getNewEvents - get new events from the DB greater than received ID
#-----------------------------------------------------------------------------
sub getNewEvents
{
  my ($class, $data) = @_;
  my ($sth, @events, $event, @ids);
  my ($id, $hostname, $service, $detected, $type, $source_type, $source, $target_proto, $target_port, $attack_scale, $note, $priority, $timeout);

  # variables defined by server
  our $IP;		# IP address of receiver
  our $CN;		# common name of receiver
  our $AN_FILTER;	# alternate name of receiver

  my $cn_db 		= $DBH->quote($CN);
  my $client_type	= "r";	# incoming client MUST be sender
  my $client_type_db	= $DBH->quote($client_type);
  my $cidr_list;

  # parse SOAP data object
  my $requested_type	= $data->{'REQUESTED_TYPE'};
  my $requested_type_db	= $DBH->quote($requested_type);
  my $last_id		= $data->{'LAST_ID'};
  my $last_id_db	= $DBH->quote($last_id);

  # Authorization of incomming client
  #-----------------------------------------------------------------------------

  # obtain cidr based on rigth common name, service and client_type
  $sth = $DBH->prepare("SELECT hostname, receive_own_events, ip_net_client FROM clients WHERE hostname IN ($AN_FILTER) AND type = $requested_type_db AND client_type = $client_type_db limit 1;");
  if ( !defined $sth ) {die("Cannot prepare authorization statement in getNewEvents: $DBI::errstr\n")}
  $sth->execute;
  my ($an, $receive_own_events, $cidr) = $sth->fetchrow();

  # check if client is registered
  if (!defined $cidr) {
    write2log ("err", "Unauthorized access to getNewEvents from: $IP (CN: $CN; AN: $an) - client is not registered");
    die("Access denied - client is not registered at warden server!");
  } else {
    $cidr_list = Net::CIDR::Lite
      -> new
      -> add($cidr);
  }

  # check if client has IP from registered CIDR
  if (!$cidr_list->bin_find($IP)) {
    write2log ("err", "Unauthorized access to getNewEvents from: $IP (CN: $CN; AN: $an) - access from bad subnet: $cidr");
    die("Access denied - access from bad subnet!");
  } else {

    # check if client want your own events or not
    if ($receive_own_events eq 't') {
      $sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > $last_id_db AND type = $requested_type_db AND valid = 't' ORDER BY id ASC;");
    } else {
      my ($domain) = $CN =~ /([^\.]+\.[^\.]+)$/;
      my $domain_db = $DBH->quote("%$domain");
      $sth = $DBH->prepare("SELECT * FROM events WHERE type != 'test' AND id > $last_id_db AND type = $requested_type_db AND valid = 't' AND hostname NOT LIKE $domain_db ORDER BY id ASC;");
    }
    if ( !defined $sth ) { die("Cannot prepare statement in getNewEvents: $DBI::errstr\n") }
    $sth->execute;

    # parse items of events stored in DB
    while (my @result = $sth->fetchrow()) {
      $id 		= $result[0];
      $hostname		= $result[1];
      $service		= $result[2];
      $detected 	= $result[3];
      $type 		= $result[5];
      $source_type 	= $result[6];
      $source	 	= $result[7];
      $target_proto	= $result[8];
      $target_port 	= $result[9];
      $attack_scale 	= $result[10];
      $note 		= $result[11];
      $priority 	= $result[12];
      $timeout	 	= $result[13];

      # create SOAP data object
      $event = SOAP::Data->name(event => \SOAP::Data->value(
        SOAP::Data->name(ID		=> $id),
        SOAP::Data->name(HOSTNAME	=> $hostname),
        SOAP::Data->name(SERVICE	=> $service),
        SOAP::Data->name(DETECTED	=> $detected),
        SOAP::Data->name(TYPE		=> $type),
        SOAP::Data->name(SOURCE_TYPE	=> $source_type),
        SOAP::Data->name(SOURCE		=> $source),
        SOAP::Data->name(TARGET_PROTO	=> $target_proto),
        SOAP::Data->name(TARGET_PORT	=> $target_port),
        SOAP::Data->name(ATTACK_SCALE	=> $attack_scale),
        SOAP::Data->name(NOTE		=> $note),
        SOAP::Data->name(PRIORITY	=> $priority),
        SOAP::Data->name(TIMEOUT	=> $timeout)
      ));
      push(@events, $event);
      push(@ids, $id);
    }

    # log sent ID of events
    if (scalar(@events)!=0) {
      write2log("info", "Sent events with ID: [@ids] to $IP (CN: $CN; AN: $an)");
    }
    return @events;
  }
} # END of getNewEvents


#-----------------------------------------------------------------------------
# getLastId - get lastest saved event ID
#-----------------------------------------------------------------------------
sub getLastId
{
  my ($class, $arg) = @_;

  my $sth = $DBH->prepare("SELECT max(id) FROM events;");
  if ( !defined $sth ) { die("Cannot prepare statement in getLastId: $DBI::errstr\n") }
  $sth->execute;
  my $result = $sth->fetchrow();
  return $result;
} # END of getLastID


#-----------------------------------------------------------------------------
# registerSender - register new sender
#-----------------------------------------------------------------------------
sub registerSender
{
  my ($class, $data) = @_;

  my $sth;
  our $IP;
  our $LOCAL_IP;
  our $CN;

  if ($LOCAL_IP ne $IP) {
    write2log ("err", "Unauthorized access to registerSender from: $IP ($CN) - access allowed only from localhost");
    die("Access denied - access allowed only from localhost!");
  } else {
    # defined variables by server
    my $client_type 		= "s";
    my $client_type_db 		= $DBH->quote($client_type);
    my $registered 		= DateTime->now;
    my $registered_db 		= $DBH->quote($registered);
    my $type			= "null";
    my $type_db			= $DBH->quote($type);
    my $receive_own_events	= "null";
    my $receive_own_events_db	= $DBH->quote($receive_own_events);

    # parse SOAP data oject
    my $hostname		= $data->{'HOSTNAME'};
    my $hostname_db		= $DBH->quote($hostname);
    my $requestor		= $data->{'REQUESTOR'};
    my $requestor_db		= $DBH->quote($requestor);
    my $service			= $data->{'SERVICE'};
    my $service_db		= $DBH->quote($service);
    my $description_tags	= $data->{'DESCRIPTION_TAGS'};
    my $description_tags_db	= $DBH->quote($description_tags);
    my $ip_net_client		= $data->{'IP_NET_CLIENT'};
    my $ip_net_client_db	= $DBH->quote($ip_net_client);

    # check if sender has been already registered
    $sth = $DBH->prepare("SELECT registered FROM clients WHERE hostname = $hostname_db AND requestor = $requestor_db AND service = $service_db AND client_type = $client_type_db AND type = $type_db AND receive_own_events = $receive_own_events_db AND description_tags = $description_tags_db AND ip_net_client = $ip_net_client_db;");
    if ( !defined $sth ) {die("Cannot prepare check statement in registerSender: $DBI::errstr\n")}
    $sth->execute;
    my $result = $sth->fetchrow();

    # register new sender
    if (defined $result) {
      write2log ("err", "Attempt to re-register the sender");
      die("Error - sender has already been registered at $result");
    } else {
      $DBH->do("INSERT INTO clients VALUES (null,$hostname_db,$registered_db,$requestor_db,$service_db,$client_type_db,$type_db,$receive_own_events_db,$description_tags_db,$ip_net_client_db);");
      if ($DBH->err()) {die("Cannot do statement in registerSender: $DBI::errstr\n")}
      $DBH->commit();
      write2log("info", "New sender $hostname (service: $service, cidr: $ip_net_client) was registered");
      return 1;
    }
  }
} # END of registerSender


#-----------------------------------------------------------------------------
# registerReceiver - register new receiver
#-----------------------------------------------------------------------------
sub registerReceiver
{
  my ($class, $data) = @_;

  my $sth;
  our $IP;
  our $LOCAL_IP;
  our $CN;

  if ($LOCAL_IP ne $IP) {
    write2log ("err", "Unauthorized access to registerReceiver from: $IP ($CN) - access allowed only from localhost");
    die("Access denied - access allowed only from localhost!");
  } else {
    # variables defined by server  
    my $client_type		= "r";
    my $client_type_db		= $DBH->quote($client_type);
    my $registered 		= DateTime->now;
    my $registered_db 		= $DBH->quote($registered);
    my $service			= "null";
    my $service_db		= $DBH->quote($service);
    my $description_tags	= "null";
    my $description_tags_db	= $DBH->quote($description_tags);

    # parse SOAP data oject
    my $hostname		= $data->{'HOSTNAME'};
    my $hostname_db		= $DBH->quote($hostname);
    my $requestor		= $data->{'REQUESTOR'};
    my $requestor_db		= $DBH->quote($requestor);
    my $type			= $data->{'TYPE'};
    my $type_db			= $DBH->quote($type);
    my $receive_own_events	= $data->{'RECEIVE_OWN_EVENTS'};
    my $receive_own_events_db	= $DBH->quote($receive_own_events);
    my $ip_net_client		= $data->{'IP_NET_CLIENT'};
    my $ip_net_client_db	= $DBH->quote($ip_net_client);

    # check if receiver has been already registered
    $sth = $DBH->prepare("SELECT registered FROM clients WHERE hostname = $hostname_db AND requestor = $requestor_db AND service = $service_db AND client_type = $client_type_db AND type = $type_db AND receive_own_events = $receive_own_events_db AND description_tags = $description_tags_db AND ip_net_client = $ip_net_client_db;");
    if ( !defined $sth ) {die("Cannot prepare check statement in registerReceiver: $DBI::errstr\n")}
    $sth->execute;
    my $result = $sth->fetchrow();

    # register new receiver
    if (defined $result) {
      write2log ("err", "Attempt to re-register the receiver");
      die("Error - receiver has already been registered at $result");
    } else {
      $DBH->do("INSERT INTO clients VALUES (null,$hostname_db,$registered_db,$requestor_db,$service_db,$client_type_db,$type_db,$receive_own_events_db,$description_tags_db,$ip_net_client_db);");
      if ($DBH->err()) {die("Cannot do statement in registerReceiver: $DBI::errstr\n")}
      $DBH->commit();
      write2log("info", "New receiver $hostname (type: $type, cidr: $ip_net_client: receive_own_events: $receive_own_events) was registered");
      return 1;
    }
  }
} # END of registerReceiver


#-----------------------------------------------------------------------------
# unregisterClient - unregister client
#-----------------------------------------------------------------------------
sub unregisterClient
{
  my ($class, $data) = @_;

  my $sth;
  our $IP;
  our $LOCAL_IP;
  our $CN;

  if ($LOCAL_IP ne $IP) {
    write2log ("err", "Unauthorized access to unregisterClients from: $IP ($CN) - access allowed only from localhost");
    die("Access denied - access allowed only from localhost!");
  } else {
    # parse SOAP data oject
    my $client_id	= $data->{'CLIENT_ID'};
    my $client_id_db	= $DBH->quote($client_id);

    # check if receiver has been already registered
    $sth = $DBH->prepare("SELECT client_id, hostname, service, client_type FROM clients WHERE client_id = $client_id_db;");
    if ( !defined $sth ) {die("Cannot prepare check statement in unregisterClient: $DBI::errstr\n")}
    $sth->execute;
    my ($id, $hostname, $service, $client_type) = $sth->fetchrow();
    my $hostname_db	= $DBH->quote($hostname);
    my $service_db	= $DBH->quote($service);

    # delete registered client
    if (!defined $id) {
      write2log ("err", "Attempt to delete unregister client");
      die("Error - client (#$client_id) is not registered");
    } else {
      if ($client_type eq 's') {
        $DBH->do("DELETE FROM clients WHERE client_id = $client_id_db;");
        if ($DBH->err()) {die("Cannot do delete statement of sender in unregisterClient: $DBI::errstr\n")}
        $DBH->commit();

        $DBH->do("UPDATE events SET valid = 'f' where hostname = $hostname_db AND service = $service_db;");
        if ($DBH->err()) {die("Cannot do unvalidation statement in unregisterClient: $DBI::errstr\n")}
        $DBH->commit();

        write2log("info", "Sender $hostname (client_id: $client_id, service: $service) was deleted and its data were invalidated" );
        return 1;
      } else {
        $DBH->do("DELETE FROM clients WHERE client_id = $client_id_db;");
        if ($DBH->err()) {die("Cannot do delete statement of receiver in unregisterClient: $DBI::errstr\n")}
        $DBH->commit();
        write2log("info", "Receiver $hostname (client_id: $client_id) was deleted" );
        return 1;
      }
    }
  }
} # END of unregisterClient


#-----------------------------------------------------------------------------
# getClients -  get list of clients which were registered at warden server
#-----------------------------------------------------------------------------
sub getClients
{
  my ($class, $arg) = @_;

  our $IP;
  our $LOCAL_IP;
  our $CN;

  if ($LOCAL_IP ne $IP) {
    write2log ("err", "Unauthorized access to getClients from: $IP ($CN) - access allowed only from localhost");
    die("Access denied - access allowed only from localhost!");
  } else {
    my (@clients, $client);
    my ($client_id, $hostname, $registered, $requestor, $service, $client_type, $type, $receive_own_events, $description_tags, $ip_net_client);
    my $sth = $DBH->prepare("SELECT * FROM clients;");
    if (!defined $sth) { die("Cannot prepare statement in getClients: $DBI::errstr\n") }
    $sth->execute;

    while ( my @result = $sth->fetchrow() ) {
      $client_id		= $result[0];
      $hostname			= $result[1];
      $registered		= $result[2];
      $requestor		= $result[3];
      $service			= $result[4];
      $client_type 		= $result[5];
      $type 			= $result[6];
      $receive_own_events	= $result[7];
      $description_tags		= $result[8];
      $ip_net_client		= $result[9];

      $client = SOAP::Data->name(client => \SOAP::Data->value(
      SOAP::Data->name(CLIENT_ID		=> $client_id),
      SOAP::Data->name(HOSTNAME			=> $hostname),
      SOAP::Data->name(REGISTERED		=> $registered),
      SOAP::Data->name(REQUESTOR		=> $requestor),
      SOAP::Data->name(SERVICE			=> $service),
      SOAP::Data->name(CLIENT_TYPE		=> $client_type),
      SOAP::Data->name(TYPE			=> $type),
      SOAP::Data->name(RECEIVE_OWN_EVENTS	=> $receive_own_events),
      SOAP::Data->name(DESCRIPTION_TAGS		=> $description_tags),
      SOAP::Data->name(IP_NET_CLIENT		=> $ip_net_client),
      ));
      push(@clients, $client);
    }
    my $sum = scalar @clients;
    write2log("info", "Sending information about $sum registered clients");
    return @clients;
  }
} # END of getClients


#-----------------------------------------------------------------------------
# getStatus - get list of status items of warden server
#-----------------------------------------------------------------------------
sub getStatus
{
  my ($class, $arg) = @_;

  our $IP;
  our $LOCAL_IP;
  our $CN;

  if ($LOCAL_IP ne $IP) {
    write2log ("err", "Unauthorized access to getStatus from: $IP ($CN) - access allowed only from localhost");
    die("Access denied - access allowed only from localhost!");
  } else {
    my ($sth, @status);

    # size of database events
    my $db_size = Format::Human::Bytes::base10(-s $db);

    # sum of records in table events
    $sth = $DBH->prepare("SELECT count(*) FROM events WHERE valid = 't';");
    if (!defined $sth) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
    $sth->execute;
    my $events_sum = $sth->fetchrow();
    if (!defined $events_sum) { $events_sum = "none" }

    # id of last record in table events
    $sth = $DBH->prepare("SELECT max(id) FROM events;");
    if (!defined $sth) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
    $sth->execute;
    my $events_last_id = $sth->fetchrow();
    if (!defined $events_last_id) { $events_last_id = "none" }

    # timestamp of first record in table events
    $sth = $DBH->prepare("SELECT received FROM events WHERE id = (SELECT min(id) FROM events);");
    if (!defined $sth) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
    $sth->execute;
    my $events_first_timestamp = $sth->fetchrow();
    if (!defined $events_first_timestamp) { $events_first_timestamp = "none" }

    # timestamp of last record in table events
    $sth = $DBH->prepare("SELECT received FROM events WHERE id = (SELECT max(id) FROM events);");
    if (!defined $sth) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
    $sth->execute;
    my $events_last_timestamp = $sth->fetchrow();
    if (!defined $events_last_timestamp) { $events_last_timestamp = "none" }

    # sum of records in table clients
    $sth = $DBH->prepare("SELECT count(*) FROM clients;");
    if (!defined $sth) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
    $sth->execute;
    my $clients_sum = $sth->fetchrow();
    if (!defined $clients_sum) { $clients_sum = "none" }

    my $server_status = SOAP::Data->name(server_status => \SOAP::Data->value(
      SOAP::Data->name(VERSION			=> $VERSION),
      SOAP::Data->name(ADDRESS			=> $ADDRESS),
      SOAP::Data->name(PORT			=> $PORT),
      SOAP::Data->name(LOGDIR			=> $LOGDIR),
      SOAP::Data->name(PIDDIR			=> $PIDDIR),
      SOAP::Data->name(VARDIR			=> $VARDIR),
      SOAP::Data->name(SSL_KEY_FILE		=> $SSL_KEY_FILE),
      SOAP::Data->name(SSL_CERT_FILE		=> $SSL_CERT_FILE),
      SOAP::Data->name(SSL_CA_FILE		=> $SSL_CA_FILE),
      SOAP::Data->name(FACILITY			=> $FACILITY),
      SOAP::Data->name(DB_SIZE			=> $db_size),
      SOAP::Data->name(EVENTS_SUM		=> $events_sum),
      SOAP::Data->name(EVENTS_LAST_ID		=> $events_last_id),
      SOAP::Data->name(EVENTS_FIRST_TIMESTAMP	=> $events_first_timestamp),
      SOAP::Data->name(EVENTS_LAST_TIMESTAMP	=> $events_last_timestamp),
      SOAP::Data->name(CLIENTS_SUM		=> $clients_sum)
    ));
    push(@status, $server_status);

    # statistics of senders
    if ($clients_sum != 0) {
      $sth = $DBH->prepare("SELECT client_id, hostname, service FROM clients WHERE client_type = 's';");
      if (!defined $sth) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
      $sth->execute;
      my ($client_id, $hostname, $service);
      my $client_status;
      while(($client_id, $hostname, $service) = $sth->fetchrow()) {
	my $hostname_db = $DBH->quote($hostname);
	my $service_db = $DBH->quote($service);
        my $sth2;
        # sum of stored events
        $sth2 = $DBH->prepare("SELECT count(*) FROM events WHERE hostname = $hostname_db AND service = $service_db;");
        if ( !defined $sth2 ) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
        $sth2->execute;
        my $count = $sth2->fetchrow();
        if (!defined $count) {$count = "none"}
        # timestamp of last stored event 
        $sth2 = $DBH->prepare("SELECT max(received) FROM events WHERE hostname = $hostname_db AND service = $service_db;");
        if ( !defined $sth2 ) { die("Cannot prepare statement in getStatus: $DBI::errstr\n") }
        $sth2->execute;
        my $timestamp = $sth2->fetchrow();
        if (!defined $timestamp) { $timestamp = "none" }
        # create SOAP data object
        $client_status = SOAP::Data->name(client_status => \SOAP::Data->value(
          SOAP::Data->name(CLIENT_ID	=> $client_id),
          SOAP::Data->name(HOSTNAME	=> $hostname),
          SOAP::Data->name(SERVICE	=> $service),
          SOAP::Data->name(COUNT	=> $count),
          SOAP::Data->name(TIMESTAMP	=> $timestamp),
        ));
	push(@status, $client_status);
      }
    }
    write2log("info", "Sent of warden server status info");
    return @status;
  }
} # END of getStatus



################################################################################
#				MAIN warden-server
################################################################################

#-------------------------------------------------------------------------------
# Superuser controle
#-------------------------------------------------------------------------------
my $UID = $<;
if ($UID != 0) {
  die errMsg("You must be root for running this script!")
}

#-------------------------------------------------------------------------------
# Daemonize section
#-------------------------------------------------------------------------------
use POSIX qw(setsid);
chdir '/';
umask 0;
# all STDERR messages are printed on terminal
open STDIN, '/dev/null' or die errMsg("Can't read /dev/null: $!");
open STDOUT, '/dev/null' or die errMsg("Can't write to /dev/null: $!");
defined( my $pid = fork ) or die errMsg("Can't fork: $!");
exit if $pid;

#-------------------------------------------------------------------------------
# Dissociate this process from the controlling terminal
# that started it and stop being part of whatever
# process group this process was a part of.
#------------------------------------------------------------------------------
POSIX::setsid() or die errMsg("Can't start a new session.");

#-------------------------------------------------------------------------------
# Callback signal handler for signals.
#-------------------------------------------------------------------------------
$SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signalHandler;
$SIG{PIPE} = 'ignore';

#-------------------------------------------------------------------------------
# Create pid file in /var/run/
#-------------------------------------------------------------------------------
my $pfh = File::Pid->new( { file => $pid_file, } );
$pfh->write or die errMsg("Can't write PID file $pid_file: $!");
my $pid_number = $pfh->pid;

#-------------------------------------------------------------------------------
# 			Starting of Warden server
#-------------------------------------------------------------------------------
write2log("info", "Starting WARDEN server daemon with pid $pid_number");

# log of warden database size
my $db_size_human = Format::Human::Bytes::base10(-s $db);
write2log("info", "Size of DB file ($db_file) is: $db_size_human");

# start TCP server
my $server = SOAP::Transport::TCP::Server
  ->new(
    Listen		=> 20,
    LocalAddr		=> $ADDRESS,
    LocalPort		=> $PORT,
    Proto		=> "tcp",
    ReuseAddr		=> 1,
    SSL_verify_mode	=> 0x02,
    SSL_use_cert	=> 1,
    SSL_server		=> 1,
    SSL_key_file	=> $SSL_KEY_FILE,
    SSL_cert_file	=> $SSL_CERT_FILE,
    SSL_ca_file 	=> $SSL_CA_FILE,
    SSL_error_trap	=>\&sslErrorHandler,
  );

# check if socket exist
$server or die errMsg("Socket error: $!");

# start SOAP server
my $soap = SOAP::Server
  ->new()
  ->dispatch_to('Warden');


#-------------------------------------------------------------------------------
# Process of incoming client's requests and send response
#-------------------------------------------------------------------------------
write2log("info", "Starting TCP and SOAP server at $ADDRESS:$PORT");
while ($die_now != 1)
{
  my $socket = $server->accept();
  next if (!$socket);

  our $CN = $socket->peer_certificate("cn");
  my @alt_names_array = $socket->peer_certificate("subjectAltNames");
  our $AN_FILTER = altNamesFilter(\@alt_names_array);
  our $IP = $socket->peerhost;
  our $LOCAL_IP = $socket->sockhost;

  # read input serialized SOAP envelope and data
  my ($request, $buf);
  while (defined($buf = <$socket>))
  {
    $request .= $buf;
  }

  # handle of called server function from client and send response to client
  my $response = $soap->handle($request);
  print $socket $response;

  $socket->close;
  undef($socket);
  undef($CN);
  undef($AN_FILTER);
  undef($IP);
  undef($LOCAL_IP);
}



################################################################################
#				Cleanup section
################################################################################
END {
  if ($die_now == 1)
  {
    my $pid = trim(`cat $pid_file`);
    write2log("info", "Stopping WARDEN server daemon with pid $pid");

    # close connection to DB
    $DBH->disconnect();

    # remove pid file
    $pfh->remove if defined $pfh;
  }
}