diff --git a/src/warden-server/bin/wardenWatchdog.t b/src/contrib/wardenWatchdog.t similarity index 100% rename from src/warden-server/bin/wardenWatchdog.t rename to src/contrib/wardenWatchdog.t diff --git a/src/contrib/wardenWatchdog/WardenWatchdog.conf b/src/contrib/wardenWatchdog/WardenWatchdog.conf new file mode 100644 index 0000000000000000000000000000000000000000..bdcdbb11e319c68a45768b88743d7da4d28e865b --- /dev/null +++ b/src/contrib/wardenWatchdog/WardenWatchdog.conf @@ -0,0 +1,67 @@ +# +# wardenWatchdog.conf - configuration file for Wachdog script +# + +#------------------------------------------------------------------------------- +# server_conf - warden server configuration file path +#------------------------------------------------------------------------------- +$server_conf = '/opt/warden-server/etc/warden-server.conf'; + +#------------------------------------------------------------------------------- +# domain_name - server full domain name +#------------------------------------------------------------------------------- +$domain_name = "warden-dev.cesnet.cz"; + +#------------------------------------------------------------------------------- +# email_subject - +#------------------------------------------------------------------------------- +$email_subject = "Kontrola stavu udalosti warden serveru na stroji $domain_name"; + +#------------------------------------------------------------------------------- +# email_server_conf - +#------------------------------------------------------------------------------- +$email_server_conf = '|/usr/sbin/sendmail -oi -t'; + +#------------------------------------------------------------------------------- +# sql_precondition - +#------------------------------------------------------------------------------- +@sql_precondition = ('DROP FUNCTION IF EXISTS iptest;', 'CREATE FUNCTION iptest(ip VARCHAR(15)) RETURNS TINYINT(1) DETERMINISTIC +BEGIN + SET @nip = INET_ATON(ip); + IF( + ISNULL( @nip) OR + @nip BETWEEN 0 AND 16777216 OR + @nip BETWEEN 167772160 AND 171966464 OR + @nip BETWEEN 2130706432 AND 2130706433 OR + @nip BETWEEN 2851995648 AND 2851995649 OR + @nip BETWEEN 2886729728 AND 2886729729 OR + @nip BETWEEN 3221225472 AND 3221225473 OR + @nip BETWEEN 3221225984 AND 3221225985 OR + @nip BETWEEN 3227017984 AND 3227017985 OR + @nip BETWEEN 3232235520 AND 3232235521 OR + @nip BETWEEN 3323068416 AND 3323068417 OR + @nip BETWEEN 3325256704 AND 3325256705 OR + @nip BETWEEN 3405803776 AND 3405803777 OR + @nip BETWEEN 3758096384 AND 3758096385 OR + @nip BETWEEN 4026531840 AND 4026531841 OR + @nip > 4294967295) THEN + RETURN TRUE; + ELSE + RETURN FALSE; + END IF; +END;'); + +#------------------------------------------------------------------------------- +# sql_queries - +# {query => ; text => ; contact => } +#------------------------------------------------------------------------------- +@sql_queries = ( + {query => "SELECT hostname, service, MAX(received) FROM events WHERE valid = 't' GROUP BY hostname, service ORDER BY MAX(received) ASC;", text => "Uvedeny klient, nebo klienti jiz delsi dobu nereportovali zadne udalosti do Wardenu. Je mozne, ze nefunguji spravne.", contact => 'jakubcegan@cesnet.cz, ph@cesnet.cz'}, + {query => "SELECT requestor FROM clients WHERE service IN (SELECT service FROM events WHERE detected > '$date' AND type NOT IN ('portscan', 'bruteforce', 'probe', 'spam', 'phishing', 'botnet_c_c', 'dos', 'malware', 'copyright', 'webattack', 'test', 'other') AND valid = 't' GROUP BY service) GROUP BY requestor;", text => "Uvedeny klient, nebo klienti zasilaji nepodporovany nebo zastaraly typ udalosti na server Warden", contact => 'jakubcegan@cesnet.cz, ph@cesnet.cz'}, + {query => "SELECT hostname, service, type, COUNT(*) FROM events WHERE detected - received > 0 AND received > '$date' GROUP BY hostname, service, type;", text => "Uvedeny klient, nebo klienti odesilaji odesilaji udalosti s casem z budoucnosti. Cas prirazeny serverem pri prichodu udalosti (received) musi byt vzdy roven nebo vetsi casu detekce (detected).", contact => 'jakubcegan@cesnet.cz, ph@cesnet.cz'}, + {query => "SELECT hostname, service, received, source, count(source) AS c, min(received), max(received) FROM events WHERE valid = 't' AND source_type = 'IP' AND iptest(source) GROUP BY hostname, service, source ORDER BY c DESC;", text => "Uvedeni klient, nebo klienti odesilaji udalosti se zdrojovou adresou, ktera by se nemela objevit v internetu (privatni rozsah), nebo je neplatna (prazdny oktet, oktet je vetsi nez 255, apod.). kvuli omezeni verzi MySQL serveru funguje zatim pouze pro IPv6.", contact => 'jakubcegan@cesnet.cz, ph@cesnet.cz'}); + +#------------------------------------------------------------------------------- +# sql_postcondition - +#------------------------------------------------------------------------------- +#@sql_postcondition = (); diff --git a/src/contrib/wardenWatchdog/WardenWatchdog.pm b/src/contrib/wardenWatchdog/WardenWatchdog.pm new file mode 100644 index 0000000000000000000000000000000000000000..13325325b4870092fd2c02138e59d443fe90dba9 --- /dev/null +++ b/src/contrib/wardenWatchdog/WardenWatchdog.pm @@ -0,0 +1,295 @@ +#!/usr/bin/perl +# +# WardenWatchdog.pm +# +# Copyright (C) 2011-2012 Cesnet z.s.p.o +# +# Use of this source is governed by a BSD-style license, see LICENSE file. + +package WardenWatchdog; + +#use Data::Dumper; +#use WardenConf; +use strict; +use warnings; +use DBI; +use DBD::mysql; +use DateTime; +use Email::Simple; +use Sys::Hostname; + +sub sendmail_wrapper{ + + my $message = shift; + my $email_conf = shift; + + if(open(my $sendmail, $email_conf)){ + print $sendmail $message; + close $sendmail; + return (1); + } else { + return (0, "Sending email failed: $!"); + } +} + +# Array of hashes +#{query => ; text => ; contact => } + +sub send_report{ + + my $input_data = shift; + my $contact = $$input_data{'contact'}; + my $domain = $$input_data{'domain'}; + my $subject = $$input_data{'subject'}; + my $text = $$input_data{'text'}; + my $email_conf = $$input_data{'email_conf'}; + + my $message; + if(!($contact)){ + return (0, "Empty 'To' email header!\n"); + } + + if(!($domain)){ + return (0, "No sender's domain! Can't send email\n"); + } + + if(!($text)){ + return (0, "No text! Nothing to send\n"); + } + + eval{ + $message = Email::Simple->create( + header => [ + To => $contact, + From => 'warden_watchdog@'.$domain, + Subject => $subject], + body => $text); + } or do { + return (0, "Can't create email message\n"); + }; + + my ($rc, $err) = sendmail_wrapper($message->as_string,$email_conf); + if(!$rc){ + return (0, $err); + } + return (1); +} + +sub connect_to_DB { + + my $dbConf = shift; + my $dbhRef = shift; + + my $dbPlatform = $$dbConf{'platform'}; + my $dbName = $$dbConf{'name'}; + my $dbHostname = $$dbConf{'hostname'}; + my $dbUser = $$dbConf{'user'}; + my $dbPasswd = $$dbConf{'passwd'}; + + my $dbh; + if($dbh = DBI->connect( "dbi:$dbPlatform:database=$dbName;host=$dbHostname", $dbUser, $dbPasswd, {mysql_auto_reconnect => 1})){ + $$dbhRef = $dbh; + return (1); + } + else{ + return (0,"Cannot connect to database! ".DBI->errstr); + } +} + + +sub update_procedures{ + + my $dbhRef = shift; + my $procRef = shift; + + my $dbh = $$dbhRef; + if(!defined($dbh)){ + return (0, "update_procedures: no database handle defined") + } + + my @sqlQueries = @{$procRef}; + + + foreach my $proc (@sqlQueries) { + $dbh->do($proc); + } + + return (1); +} + + +sub send_query{ + + my $dbhRef = shift; + my $configRef = shift; + my $eventsRef = shift; + + my $dbh = $$dbhRef; + if(!defined($dbh)){ + return (0, "send_query: no database handle defined") + } + + my @config = @{$configRef}; + my %bad_events; + my ($rc,$err); + my $i = 0; + + while ($i < scalar(@config)) { + # run DB query -> requestor, client name + my $sth; + if (defined($config[$i]{query})){ + $sth = $dbh->prepare($config[$i]{query}); + } + else{ + return (0, "No query available\n"); + } + + if (!($sth->execute)){ + return (0, "Couldn't get data from my database: $sth->errstr\n"); + }; + + my $contact; + my $msg_text = 1; + + while(my $result = $sth->fetchrow_hashref()){ + if (defined($config[$i]{contact})){ + $contact = $config[$i]{contact}; + } + else{ + $contact = $result->{'requestor'}; + } + if($msg_text){ + $bad_events{$contact} .= $config[$i]{text} . "\n\n"; + $msg_text = 0; + } + $bad_events{$contact} .= join(", ", map { $_ // "NULL" } values %$result) . "\n"; + } + foreach my $key (keys %bad_events){ + $bad_events{$key} .= "\n\n"; + } + + $sth->finish; + $i++; + } + + %$eventsRef = %bad_events; + return (1); +} + + +sub run{ + + my $conf_file = shift; + my $period = shift; + + my $errMsg; + # server config + + if(!defined($conf_file)){ + return (0,"No conf file is available"); + } + + if(!defined($period)){ + return (0,"No time period is defined"); + } + + our $server_conf = undef; + our $domain_name = undef; + our $email_subject = undef; + our $email_server_conf = undef; + our @sql_precondition = undef; + our @sql_queries = undef; + our @sql_postcondition = undef; + + # script config + if (!(do $conf_file)) { + if ($@){ + $errMsg = "Errors in config file '$conf_file': $@"; + #syslog("err|$errMsg"); + print $errMsg; + return (0,"$errMsg"); + } + if (!(defined $_)){ + $errMsg = "Can't read config file '$conf_file': $!"; + #syslog("err|$errMsg"); + print $errMsg; + return (0,"$errMsg"); + } + } + + # server config + our $SYSLOG_VERBOSE = undef; + our $SYSLOG_FACILITY = undef; + our $DB_NAME = undef; + our $DB_USER = undef; + our $DB_PASS = undef; + our $DB_HOST = undef; + + # TODO replace with function call from Wardencommon + if (!(do $server_conf)) { + if ($@){ + $errMsg = "Errors in config file '$server_conf': $@"; + #syslog("err|$errMsg"); + print $errMsg; + return (0,"$errMsg"); + } + if (!(defined $_)){ + $errMsg = "Can't read config file '$server_conf': $!"; + #syslog("err|$errMsg"); + print $errMsg; + return (0,"$errMsg"); + } + } + + #print "SYSLOG_VERBOSE $SYSLOG_VERBOSE, SYSLOG_FACILITY $SYSLOG_FACILITY, DB_NAME $DB_NAME, DB_USER $DB_USER, DB_PASS $DB_PASS, DB_HOST $DB_HOST"; + my %db_conf = (platform => 'mysql', name => $DB_NAME, hostname => $DB_HOST, user => $DB_USER, passwd => $DB_PASS); + + my $date; + eval{ + my $dt = DateTime->now(); + $dt = DateTime->now()->subtract(days => $period); + $date = $dt->date(); + } or do { + #print "Warden watchdog - can't work with date\n"; + syslog("err|Warden watchdog - can't work with date\n"); + }; + + my $dbh; + # connect to DB + my ($rc,$err) = connect_to_DB(\%db_conf,\$dbh); + if (!$rc){ + $errMsg = "Warden watchdog can\'t connect do DB: $err"; + syslog("err|$errMsg"); + return (0,"$errMsg"); + } + + ($rc,$err) = update_procedures(\$dbh,\@sql_precondition); + if (!$rc){ + #print "Warden watchdog - $err\n"; + syslog("err|Warden watchdog - $err\n"); + } + + my %bad_events; + my $i = 0; + while ($i < scalar(@sql_queries)) { + my ($rc,$err) = send_query(\$dbh,\@sql_queries,\%bad_events); + if (!$rc){ + print "Warden watchdog - $err\n"; + #syslog("err|Warden watchdog - $err\n"); + } + $i++; + } + + while (my ($contact, $text) = each(%bad_events)){ + my %input = (contact => $contact, domain => $domain_name, text => $text, subject => $email_subject, email_conf => $email_server_conf); + my ($rc,$err) = send_report(\%input); + if (!$rc){ + print $err; + #syslog("err|Warden client - networkReporter $err\n"); + } + } + + # disconnect to DB + $dbh->disconnect; +} +1; diff --git a/src/contrib/wardenWatchdog/WardenWatchdog.t b/src/contrib/wardenWatchdog/WardenWatchdog.t new file mode 100644 index 0000000000000000000000000000000000000000..a7014f198a61e858682fa08c8419e8fb7f3f138c --- /dev/null +++ b/src/contrib/wardenWatchdog/WardenWatchdog.t @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use DBI; +use DBD::mysql; +use Data::Dumper; + +use Test::More tests => 8; +use Test::MockModule; +use Test::Exception; + +use lib '..'; +use WardenWatchdog; + + +my (%input, $retCode, $result); + +print "== WardenWatchdog::send_report ==\n"; + +*WardenWatchdog::sendmail_wrapper = sub {print "sendmailWrapper is set to broken!\nSending of emails will fail\n"; return 0;}; + +%input = (contact => 'test@test.receiver.cz', domain => 'test.domain.cz', text => 'TEST MSG!'); +($retCode, $result) = WardenWatchdog::send_report(\%input); +ok ($retCode == 0, 'Broken sendmail'); + +*WardenWatchdog::sendmail_wrapper = sub {print "sendmailWrapper is OFF!\nSending of emails is blocked\n"; return 1;}; + +%input = (contact => '', domain => 'test.domain.cz', text => 'TEST MSG!'); +($retCode, $result) = WardenWatchdog::send_report(\%input); +ok ($retCode == 0, 'No contact'); + +%input = (contact => 'test@test.receiver.cz', domain => '', text => 'TEST MSG!'); +($retCode, $result) = WardenWatchdog::send_report(\%input); +ok ($retCode == 0, 'No domain'); + +%input = (contact => '', domain => 'test.domain.cz', text => ''); +($retCode, $result) = WardenWatchdog::send_report(\%input); +ok ($retCode == 0, 'No text'); + +%input = (contact => 'test@test.receiver.cz', domain => 'test.domain.cz', text => 'TEST MSG!'); +($retCode, $result) = WardenWatchdog::send_report(\%input); +ok ($retCode == 1, 'All OK'); + +print "== WardenWatchdog::connect_to_DB ==\n"; + +my ($dbh, %db_conf); +%db_conf = (platform => 'mysql', name => 'a', hostname => 'a', user => 'a', passwd => 'a'); +# FAILED DB CONNECTION +($retCode, $result) = WardenWatchdog::connect_to_DB(\%db_conf, \$dbh); +ok ($retCode == 0, 'Can\'t connect to the DB - wrong credentials'); + + +#print "== WardenWatchdog::update_procedures ==\n"; + +# CORRECT RUN OF update_procedures + +#print "== WardenWatchdog::send_query ==\n"; + +# RUN WITH BROKEN DB CONNECTION + +# RUN WITH NO query + +# CORRECT RUN OF send_query + +print "== WardenWatchdog::run ==\n"; + +# RUN WITHOUT configuration file +($retCode, $result) = WardenWatchdog::run(undef,7); +ok ($retCode == 0, 'No conf file'); + +# RUN WITHOUT day period +($retCode, $result) = WardenWatchdog::run('wardenWatchdog.conf',undef); +ok ($retCode == 0, 'No conf file'); + +# CORRECT RUN OF send_query diff --git a/src/contrib/wardenWatchdog/wardenWatchdog.pl b/src/contrib/wardenWatchdog/wardenWatchdog.pl new file mode 100755 index 0000000000000000000000000000000000000000..5c8a2582fbdab9b1195076300f2a31e4b256939b --- /dev/null +++ b/src/contrib/wardenWatchdog/wardenWatchdog.pl @@ -0,0 +1,15 @@ +#!/usr/bin/perl +# +# wardenWatchdog.pl +# +# Copyright (C) 2011-2012 Cesnet z.s.p.o +# +# Use of this source is governed by a BSD-style license, see LICENSE file. + +use strict; +use warnings; + +use WardenWatchdog; + +WardenWatchdog::run('WardenWatchdog.conf',7); +1;