#!/usr/bin/perl -w

use strict;
use utf8;
use warnings;

use Config::Simple;
use DBI;
use Getopt::Long qw(GetOptions);
use noris::NetSaint;

GetOptions(
    'blacklist=s'  => \our $Blacklist,
    'config=s'     => \our $ConfigFile,
    'customerbl=s' => \our $CBlacklist,
    'facility=s'   => \our $Facility,
    'c|critical-ago=i' => \our $CriticalAgo,
    'w|warning-ago=i'  => \our $WarningAgo,
    'help|?'       => \our $Help,
    'H|host=s'     => \our $Host,
) or exit 1;

exec perldoc => -F => $0 or die "exec('perldoc -F $0'): $!\n" if $Help;

die "Kein Config File angegeben!\n" unless defined $ConfigFile;
die "Config File nicht lessbar!\n"  unless -r $ConfigFile;
die "Keine Facility angegeben!\n"   unless defined $Facility;
die "Keinen Host angegeben!\n"      unless defined $Host;

my %Config;
Config::Simple->import_from($ConfigFile, \%Config);

my @BL;
if ( defined $Blacklist ) {

    # read blacklist file;
    open( BL, "<$Blacklist" ) or die "Datei wurde nicht gefunden\n";
    while (<BL>) {
        s/^#.*//;
        next unless /\S/;
        chomp;
        push @BL, qr/$_/;
    }
    close(BL);
}

if ( defined $CBlacklist ) {

    # read customer blacklist file;
    open( CBL, "<$CBlacklist" ) or die "Datei wurde nicht gefunden\n";
    while (<CBL>) {
        s/^#.*//;
        next unless /\S/;
        chomp;
        push @BL, qr/$_/;
    }
    close(CBL);
}

my $NetSaint = new noris::NetSaint;

sub RaiseUnknown {
    $NetSaint->message('No Connect to DB');
    $NetSaint->status('UNKNOWN');
    exit;
}

# Zur Datenbank verbinden
my $dbh = DBI->connect(
    "DBI:mysql:$Config{'default.DB_NAME'}:$Config{'default.DB_HOST'}",
    $Config{'default.DB_USER'},
    $Config{'default.DB_PASS'},
    { RaiseError => 0, AutoCommit => 0 }
) or RaiseUnknown();
$dbh->{RaiseError} = 1;

my $query = <<_;
    SELECT id, priority, message, program
      FROM events
     WHERE host = ?
       AND facility = ?
       AND acknowledged = 0
       AND priority IN ( 'err', 'warning' )
_

my $sth = $dbh->prepare($query);
$sth->execute( $Host, $Facility )
  or die "Fehler beim ausführen des Querys: " . $sth->errstr;

my @AckOneIDs;
my @AckTwoIDs;
LOG:
while ( my ($id, $priority, $message, $program) = $sth->fetchrow_array ) {

    for (@BL) {
        #my $brow = qr/$_/;
        if ($message =~ m/$_/i) {
            push @AckTwoIDs, $id;
            next LOG;
        }
    }

    if ( $priority eq 'err' ) {
        push @AckOneIDs, $id;
        $NetSaint->update( Critical => $program . " : " . $message );
    }
    elsif ( $priority eq 'warning' ) {
        push @AckOneIDs, $id;
        $NetSaint->update( Warning => $program . " : " . $message );
    }
}

$sth->finish;

if (@AckOneIDs) {
    $dbh->do(
        "UPDATE events SET acknowledged = 1 WHERE id IN ("
          . join( ", ", ('?') x @AckOneIDs ) . ")",
        {}, @AckOneIDs
    );
}
if (@AckTwoIDs) {
    $dbh->do(
        "UPDATE events SET acknowledged = 2 WHERE id IN ("
          . join( ", ", ('?') x @AckTwoIDs ) . ")",
        {}, @AckTwoIDs
    );
}

if ( ( $NetSaint->status eq 'Unknown' ) and ( $WarningAgo or $CriticalAgo ) ) {
    my $time_query = <<_;
   SELECT UNIX_TIMESTAMP(datetime)
     FROM events
    WHERE host = ?
 ORDER BY datetime DESC
    LIMIT 1
_

    my $tth = $dbh->prepare($time_query);
    $tth->execute( $Host )
      or die "Fehler beim ausführen des Querys: " . $tth->errstr;
    my ($ts) = $tth->fetchrow_array();
    $tth->finish;

    my $diff = ( time() - $ts ) / 60 / 60;
    my $msg = sprintf "Der letzte Eintrag liegt über %.2f Stunden zurück!", $diff;
    if ( $CriticalAgo and $diff > $CriticalAgo ) {
        $NetSaint->update( Critical => "Critical: $msg" );
    }
    elsif ( $WarningAgo and $diff > $WarningAgo ) {
        $NetSaint->update( Warning => "Warning: $msg" );
    }

    $NetSaint->message("OK: $msg") unless $NetSaint->message;
    $NetSaint->status('OK') if $NetSaint->status eq 'Unknown';
}

$NetSaint->message('No errors detected.') unless $NetSaint->message;
$NetSaint->status('OK') if $NetSaint->status eq 'Unknown';

$dbh->commit();
$dbh->disconnect;

__END__

=encoding utf8

=head1 NAME

check_syslog

=head1 BESCHREIBUNG

Nagios-kompatibles Script zum Monitoring von Windows Events

=head1 SYNOPSE

    check_syslog -host 1.2.3.4 -facility Security \
                 -blacklist /tmp/config_file

    check_syslog -H 1.2.3.4 -facility Security \
                 -blacklist /tmp/config_file

=head1 NOTWENDIGE ARGUMENTE

=over 4

=item -host IPADDR, -host HOSTNAME

=item -H IPADDR, -H HOSTNAME

den Hostnamen oder die IP Adresse des zu überwachenden Hosts. Der Name sollte
das Format haben wie es in der EventDatenbank landet.

=item -facility (Security|Application|System)

gibt die zu überwachende Facility an.

=item -blacklist FILE

das Blacklist File das die Einträge enthält die nicht alarmieren soll
beinhaltet.

Vorsicht: Das Blacklistfile sollte latin-1 kodiert sein. Sonst kann es passieren
das die Strings nicht korrekt verglichen werden und somit ggf. nicht alarmiert
wird.

=item -customerbl FILE

ein zusätzliches Blacklist File das die Einträge enthält die nicht alarmieren
sollen beinhaltet.

Vorsicht: Das Blacklistfile sollte latin-1 kodiert sein. Sonst kann es passieren
das die Strings nicht korrekt verglichen werden und somit ggf. nicht alarmiert
wird.

=back

=head1 OPTIONEN

=over 4

=item -critical-ago INT

=item -c INT

gibt die Zeit in Stunden an, wie weit der letzte Event zurückliegen soll,
der ein Critical werfen soll.

=item -warning-ago INT

=item -w INT

gibt die Zeit in Stunden an, wie weit der letzte Event zurückliegen soll,
der ein Warnung werfen soll.

=item -help

=item -?

um (nur) diese Dokumentation anzeigen zu lassen

=back

=head1 BEGRIFFLICHKEITEN

Das Plugin unterscheidet zwei Arten von Störungen:

=over 4

=item schwere Störung

Beim Auftreten einer I<schweren Störung> wird sofort Status I<Critical>
gemeldet.

=item minderschwere Inkonsistenz

Wird eine I<minderschwere Inkonsistenz> beobachtet, wird eine I<Warnung>
ausgegeben.

=back

=head1 FUNKTIONSWEISE

Das Tool verbindet sich zur Datenbank und versucht Einträge zu finden die zu
den oben angegebenen Optionen passen.
Sobald er Einträge findet gleicht er sie mit der im Blacklist File angegebenen
Werten. Falls der Eintrag matcht wird nicht alarmiert und der Event-Log wird
mit einer 2 markiert.
Ansonsten wird es eskaliert und der Eintrag wird mit einer 1 markiert.
Sollte das Tool sich nicht zu der Datenbank verbinden können, wirft er einen
Status 'Unknown'.

=head1 AUTOREN

 Stelios Gikas <entwicklung@noris.net>
 Stelios Gikas <10074868@ticket.noris.net>

=cut
