#!/usr/bin/perl -w

use 5.006;
use strict;
use warnings;

# $Id: check_aol_block,v 1.5 2006/02/14 17:24:27 sysadm Exp $

{

    package _::undef;
    sub new { bless [], __PACKAGE__ }
    sub readline { }
}

use FindBin ();
use lib do { $FindBin::Bin =~ /^(.*)/; "$1/.perllib" };
use Date::Parse 'str2time';
use File::ReadBackwards ();
use Getopt::Long 'GetOptions';
use noris::NetSaint ();

use constant RE_EXIM_DATE => qr/\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?: [+-]\d{4})?/;
use constant RE_SYSLOG_DATE => qr/\w\w\w [ 123]\d \d\d:\d\d:\d\d/;

our %Status = ( '' => 'Critical' );

GetOptions
  'help|?'    => \our $Help,
  'ignore=s'  => \our @Ignore,
  'max-age=i' => \( our $MaxAge = 3600 ),
  'status=s'  => \%Status;

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

my %ignore;
@ignore{@Ignore} = ();

our $NetSaint = new noris::NetSaint;

while ( my ( $k, $v ) = each %Status ) {
    $Status{$k} = noris::NetSaint::Status->new($v);
}

my %errors;
for ( my ( $age, $log ), my $last_time = '', my $fh = new _::undef ; ; ) {
    my $line;
    until ( defined( $line = $fh->readline ) ) {
        defined( $log = shift )
          or die +(
            defined $age ? "Nur $age Sekunde" . ( $age != 1 && 'n' ) : 'Keine' )
          . " Log-Daten vorhanden.\n";
        $fh = File::ReadBackwards->new($log)
          or die qq(Kann "$log" nicht ffnen: $!\n);
    }
    $line =~ /^(${\RE_EXIM_DATE}|${\RE_SYSLOG_DATE}) / or next;
      #or die
      #qq(Kann in folgender Zeile aus "$log" keinen Timestamp finden: $line);
    if ( $last_time ne $1 ) {    # Performance-Optimierung
        my $time = str2time( $last_time = $1 )
          or die qq(Kann Timestamp "$1" aus "$log" nicht parsen: $line);
        last if $MaxAge && ( $age = $^T - $time ) > $MaxAge;
    }
    next
      if $line !~ m# http://postmaster\.info\.aol\.com/errors/#
      || $line =~ / F=<> /;
    my ( $code, $name ) =
      $line =~
m#: (\d\d\d)-?:? +\((\w+:[\w\d]+)\) +http://postmaster\.info\.aol\.com/errors/#
      or die
qq(Folgende Zeile aus "$log" enthlt eine unbekannte Meldung von AOL: $line);
    next if exists $ignore{"$code $name"};
    ++$errors{"$code $name"};
}

if ( keys %errors ) {
    for ( reverse sort keys %errors ) {
        my ( $code, $name ) = split;
        my $status;
        next
          unless defined( $status = $Status{$name} )
          or defined( $status = $Status{$code} )
          or defined( $status = $Status{ substr $code, 0, 1 } )
          or defined( $status = $Status{''} );
        $NetSaint->update( $status => $errors{$_} . " Fehler $code Typ $name" );
    }
}
elsif ( !$NetSaint->message ) {
    $NetSaint->update( OK => 'Keine AOL-Sperrmeldungen gefunden.' );
}

=head1 NAME

check_aol_block -- Nagios-Plugin um festzustellen, wenn AOL uns sperrt

=head1 SYNOPSE

  check_aol_block 
    --ignore '554 HVU:B1' --ignore '554 HVU:B2' \
    --ignore '554 HVU:IP' --ignore '554 HVU:NR' \
    --ignore '554 RLY:FA' --status 4=Warning    \
    /var/log/exim4/mainlog /var/log/exim4/mainlog.1

Durchsucht L<rckwrts|File::ReadBackwards> die angegebenen Log-Dateien (aber
jede fr sich rckwrts. d. h. hier zuerst C</var/log/exim4/mainlog>, dann ggf.
C</var/log/exim4/mainlog.1>), bis es auf einen Eintrag stt, der lter als
eine Stunde ist.
Bei Erkennung (mindestens) einer Fehlermeldung von AOL wird ein kritischer
Zustand erzeugt, auer bei Fehler der Klasse 4; hier wird nur eine Warnung
erzeugt.
Weitere Ausnahmen: Fehler mit Namen C<HVU:B1>, C<HVU:IP> oder C<RLY:FA> werden
ganz ignoriert, auerdem sowieso alle Meldungen zu Bounces, erkennbar an der
Zeichenkette " F=<> ".

=head1 BEGRIFFSDEFINITIONEN

Das Script schaut sich erstmal alle Log-Eintrge genauer an, in denen die
Zeichenkette "http://postmaster.info.aol.com/errors/" vorkommt, z. B. (zur
Besseren Lesbarkeit an Whitespace umgebrochen):

  1Ce1Ci-0001rH-L2 ** wstimpfle@aol.com
  F=<Kim@hotdak.net>
  R=dnslookup
  T=remote_smtp:
  SMTP error from remote mailer after end of data:
  host mailin-04.mx.aol.com [64.12.137.184]: 
  554-:  (HVU:B1)
  http://postmaster.info.aol.com/errors/554hvub1.html\n554
  TRANSACTION FAILED

Es versucht dann, den Fehler anhand folgender Kriterien nher zu klassifizieren:

=over 4

=item Code

der numerische SMTP-Fehlercode, im obigen Beispiel C<554>

=item Fehler-Klasse

die erste Ziffer des L<Codes|/Code>, hier also C<5>

=item Name

der Kurzname, den AOL dem Fehler gegeben hat, hier C<HVU:B1>

=item URL

die URL, unter der man nhere Informationen erhlt, im Beispiel
L<http://postmaster.info.aol.com/errors/554hvub1.html>

=back

Scheitert dies, stirbt das Script, was einen Unknown-Status auslsen sollte.

=head1 OPTIONEN

=over 4

=item ignore=s @

AOL-Fehlermeldungen mit der angegebenen Code-Namens-Kombination ignorieren

=item max-age=i

Maximales Alter der zu durchsuchenden Log-Eintrge in Sekunden, gemessen ab dem
Start-Zeitpunkt des Plugins, Default: 3600 (also eine Stunde).
Um alle Zeilen der angegebenen Logs durchsuchen zu lassen, bitte ggf.

	--max-age 0

angeben.

=item status=s %

Bei Fehler des angegebenen Namens, des angegebenen Codes oder der angegebenen
Fehlerklasse den L<Rckgabe-Status|noris::NetSaint::Status> "mindestens" auf den
angegebenen setzen.
Per Default lsen alle nicht ignorierten Fehler einen kritischen Status aus;
dieses Default kann ggf. folgendermaen berschrieben werden (Beispiel: nderung
auf "Warning"):

	--status =Warning

=item help|?

um (nur) diese Dokumentation anzeigen zu lassen

=back

=head1 AUTOR

 Martin H. Sluka <fany@noris.net>
 fr die noris network AG

=cut
