#!/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 SOAP::Transport::HTTP;
#use File::Pid;
#use POSIX;
use DBI;
use DBD::mysql;
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;
#use Crypt::OpenSSL::X509;
use MIME::Base64;
use Crypt::X509;

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;

#DB options
our $DB_NAME		= undef;
our $DB_USER		= undef;
our $DB_PASS		= undef;
our $DB_HOST		= undef;

#
#$ADDRESS = "147.228.52.72";
##
###-------------------------------------------------------------------------------
### PORT - used TCP port for Warden server
###-------------------------------------------------------------------------------
#$PORT = "8889";
##
###-------------------------------------------------------------------------------
### BASEDIR - base directory of Warden server
###-------------------------------------------------------------------------------
#our $BASEDIR = "/opt/warden-server/";
##
###-------------------------------------------------------------------------------
### VARDIR - var directory
###-------------------------------------------------------------------------------
#$VARDIR = "$BASEDIR/var/";
##
###-------------------------------------------------------------------------------
### LOGDIR - logging directory
###-------------------------------------------------------------------------------
#$LOGDIR = "/var/log/";  
##
###-------------------------------------------------------------------------------
### PIDDIR - process ID directory
###-------------------------------------------------------------------------------
#$PIDDIR = "/var/run/";
##
##
##
## 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=$DB_NAME;host=$DB_HOST", $DB_USER, $DB_PASS, {RaiseError => 1, mysql_auto_reconnect => 0}) || die "Could not connect to database: $DBI::errstr";
#our $DBH;

################################################################################
#				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 $CN = $ENV{'SSL_CLIENT_S_DN_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;


  push @an_array, "'$CN'";
  my @a = split("\n", $ENV{'SSL_CLIENT_CERT'});
  pop @a;
  shift @a;
  my $der = decode_base64(join("", @a));
  my $decoded= Crypt::X509->new(cert => $der);
 
  foreach my $tmp (@{$decoded->SubjectAltName}){
  	if($tmp =~ s/dNSName=//){
		push @an_array, "'$tmp'"; 
	} 
  } 

  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 @alt_names_array = $socket->peer_certificate("subjectAltNames");
  my $CN = $ENV{'SSL_CLIENT_S_DN_CN'};
  my $AN_FILTER = altNamesFilter(undef);
  my $IP = $ENV{'REMOTE_ADDR'};
  my $LOCAL_IP = $ENV{'SERVER_ADDR'};

  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_id()");
    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!");
      return 0;
    } else {
      return 1;
    }
 }
 
  #$DBH->disconnect();
} # 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 = $ENV{'SSL_CLIENT_S_DN_CN'};
  my $AN_FILTER = altNamesFilter(undef);
  my $IP = $ENV{'REMOTE_ADDR'};

  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') {
      write2log("info", "SELECT * FROM events WHERE type != 'test' AND id > $last_id_db AND type = $requested_type_db AND valid = 't' ORDER BY id ASC;");
      $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");
      write2log("info", "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;");
      $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;

  my $CN = $ENV{'SSL_CLIENT_S_DN_CN'};
  my $IP = $ENV{'REMOTE_ADDR'};
  my $LOCAL_IP = $ENV{'SERVER_ADDR'};



  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;
  my $CN = $ENV{'SSL_CLIENT_S_DN_CN'};
  my $IP = $ENV{'REMOTE_ADDR'};
  my $LOCAL_IP = $ENV{'SERVER_ADDR'};



  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;
  my $CN = $ENV{'SSL_CLIENT_S_DN_CN'};
  my $IP = $ENV{'REMOTE_ADDR'};
  my $LOCAL_IP = $ENV{'SERVER_ADDR'};


  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;

  my $CN = $ENV{'SSL_CLIENT_S_DN_CN'};
  my $IP = $ENV{'REMOTE_ADDR'};
  my $LOCAL_IP = $ENV{'SERVER_ADDR'};


  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;
  my $CN = $ENV{'SSL_CLIENT_S_DN_CN'};
  my $IP = $ENV{'REMOTE_ADDR'};
  my $LOCAL_IP = $ENV{'SERVER_ADDR'};


  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);
    
    $sth = $DBH->prepare("SELECT data_length + index_length FROM information_schema.TABLES WHERE table_schema = ? AND TABLE_NAME = ?");
    $sth->execute("warden", "events");
    my $db_size_db = $sth->fetchrow();
    my $db_size = (defined $db_size_db ? Format::Human::Bytes::base10($db_size_db) : "none");

    # 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		=> 5,
##    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 = $ENV{'SSL_CLIENT_S_DN_CN'};
#  my @alt_names_array = $socket->peer_certificate("subjectAltNames");
#  our $AN_FILTER = altNamesFilter(undef);
#  our $IP = $ENV{'REMOTE_ADDR'};
#  our $LOCAL_IP = $ENV{'SERVER_ADDR'};
##
##  # 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;
##  }
##}
#
1;