# WardenWatchdog.pm # # Copyright (C) 2011-2014 Cesnet z.s.p.o # # Use of this source is governed by a BSD-style license, see LICENSE file. package WardenWatchdog; use strict; use warnings; use DBI; use DBD::mysql; use DateTime; use Email::Simple; use Sys::Hostname; our $VERSION = "2.2"; #------------------------------------------------------------------------------- # sendmailWrapper # # Simple wrapper function for an mailserver. # # Input: # message = prepared message # email_conf = configuration of a mailserver # # Output: - # # Return: # On Success (1) # On Failure (0, 'Error message') #------------------------------------------------------------------------------- sub sendmailWrapper { 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 => } #------------------------------------------------------------------------------- # sendReport # # Function for creating and sending of an Watchdog status report via email to # administrators of an clients. # # Input: # Hash of parameters: # contact => email address of a message recipient # domain => domain name of server where script runs # subject => subject of an email # text => body of an email # email_conf => configuration of a mailserver # # Output: - # # Return: # On Success (1) # On Failure (0, 'Error message') #------------------------------------------------------------------------------- sub sendReport { 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) = sendmailWrapper($message->as_string,$email_conf); if(!$rc) { return (0, $err); } return (1); } #------------------------------------------------------------------------------- # connectToDB # # Just simple database wrapper for Watchdog which creates db's handler. # # Input: # Hash of parameters: # platform => database platform name according to Perl DBI # name => name of a database # hostname => database hostname # user => username # passwd => password # # Output: # dbh_ref = reference on a database handler # # Return: # On Success (1) # On Failure (0, 'Error message') #------------------------------------------------------------------------------- sub connectToDB { my $db_conf = shift; my $dbh_ref = shift; my $db_platform = $$db_conf{'platform'}; my $db_name = $$db_conf{'name'}; my $db_hostname = $$db_conf{'hostname'}; my $db_user = $$db_conf{'user'}; my $db_passwd = $$db_conf{'passwd'}; my $dbh; if($dbh = DBI->connect( "dbi:$db_platform:database=$db_name;host=$db_hostname", $db_user, $db_passwd, {mysql_auto_reconnect => 1})) { $$dbh_ref = $dbh; return (1); } else{ return (0,"Cannot connect to database! ".DBI->errstr); } } #------------------------------------------------------------------------------- # updateProcedures # # Function takes DB handler and executes all database procedures in the array. # # Input: # dbh_ref = reference on a database handler # proc_ref = reference on an array of database procedures # # Output: - # # Return: # On Success (1) # On Failure (0, 'Error message') #------------------------------------------------------------------------------- sub updateProcedures { my $dbh_ref = shift; my $proc_ref = shift; my $dbh = $$dbh_ref; if(!defined($dbh)) { return (0, "updateProcedures: no database handle defined") } my @sql_queries = @{$proc_ref}; foreach my $proc (@sql_queries) { $dbh->do($proc); } return (1); } #------------------------------------------------------------------------------- # sendQuery # # # # Input: # dbh_ref = reference on a database handler # config_ref = Hash of parameters: # query => sql query of an action (check) on Warden database # text => body of an email which is send to a admin of an client # in case of nonempty check result # contact => contact for message, which overrides contact collumn # in a database table. # # Output: # events_ref = Hash of parameters: # contact = email address of an client administrator # 'contact' => predefined email text + information from database obtained # by a query # # Return: # On Success (1) # On Failure (0, 'Error message') #------------------------------------------------------------------------------- sub sendQuery { my $dbh_ref = shift; my $config_ref = shift; my $events_ref = shift; my $dbh = $$dbh_ref; if(!defined($dbh)) { return (0, "sendQuery: no database handle defined") } my @config = @{$config_ref}; 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})) { # override contact from 'requestor' collumn $contact = $config[$i]{contact}; } else{ $contact = $result->{'requestor'}; } # information header if($msg_text) { $bad_events{$contact} .= $config[$i]{text} . "\n\n"; $bad_events{$contact} .= join(" | ", map {$_ // "UNKNOWN" } keys %$result) . "\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++; } %$events_ref = %bad_events; return (1); } #------------------------------------------------------------------------------- # run # # Main module function which takes configure file, interval and performs Warden # database checks described in a configuration file. Results are send to client # administrators or on a defined addresses if they are set in a config. # # Input: # conf_file = path to a config file with database procedures and other params # period = interval in days from now back to the past for database check # # Output: - # # Return: # On Success (1) # On Failure (0, 'Error message') #------------------------------------------------------------------------------- sub run { my $conf_file = shift; my $period = shift; my $err_msg; # 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 ($@) { $err_msg = "Errors in config file '$conf_file': $@"; #syslog("err|$err_msg"); print $err_msg; return (0,"Warden watchdog - $err_msg"); } if (!(defined $_)) { $err_msg = "Can't read config file '$conf_file': $!"; #syslog("err|$err_msg"); print $err_msg; return (0,"Warden watchdog - $err_msg"); } } # 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 ($@) { $err_msg = "Errors in config file '$SERVER_CONF': $@"; #syslog("err|$err_msg"); print $err_msg; return (0,"Warden watchdog - $err_msg"); } if (!(defined $_)) { $err_msg = "Can't read config file '$SERVER_CONF': $!"; #syslog("err|$err_msg"); print $err_msg; return (0,"Warden watchdog - $err_msg"); } } #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(time_zone => 'UTC')->subtract(days => $period); $dt->set_time_zone('local'); $date = $dt->strftime("%Y-%m-%d %H:%M:%S"); } or do { #print "Warden watchdog - can't work with date\n"; syslog("Warden watchdog - can't work with date\n"); }; my $dbh; # connect to DB my ($rc,$err) = connectToDB(\%db_conf,\$dbh); if (!$rc) { $err_msg = "Warden watchdog can\'t connect do DB: $err"; return (0,"Warden watchdog - $err_msg"); } if(@SQL_PRECONDITION) { ($rc,$err) = updateProcedures(\$dbh,\@SQL_PRECONDITION); if (!$rc) { #print "Warden watchdog - $err\n"; return (0,"Warden watchdog - $err\n"); } } my %bad_events; if(@SQL_QUERIES) { foreach my $query (@SQL_QUERIES) { $query->{query} =~ s/\$date/$date/; } my ($rc,$err) = sendQuery(\$dbh,\@SQL_QUERIES,\%bad_events); if (!$rc) { #print "Warden watchdog - $err\n"; return (0,"Warden watchdog - $err\n"); } } if(@SQL_POSTCONDITION) { my ($rc,$err) = updateProcedures(\$dbh,\@SQL_POSTCONDITION); if (!$rc) { #print "Warden watchdog - $err\n"; return (0,"Warden watchdog - $err\n"); } } 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) = sendReport(\%input); if (!$rc) { #print $err; return (0,"Warden client - networkReporter $err\n"); } } # disconnect to DB $dbh->disconnect; } 1;