#!/usr/bin/perl -w

use 5.006;
use strict;
use warnings;

use Fcntl qw(LOCK_EX O_APPEND O_CREAT O_WRONLY);
use Getopt::Long 'GetOptions';
use IO::Socket::INET '$CRLF';
use noris::NetSaint;

use constant DEFAULT_INTERFACES => qw(krempel.backup.noris.net:58123);

my @argv = @ARGV;

GetOptions(
    'expire=i' => \( my $Expire = 86400 ),
    'extern!' => \my $Extern,
    'help|?' => sub {
        exec perldoc => -F => $0
          or die "exec('perldoc -F $0') returned $?: $!\n";
    },
    'info=s'      => \my $Info,
    'interface=s' => \my @Interfaces,
    'key=s'       => \my $Key,
    'kunde=s'     => \my $Kunde,
    'logfile=s'   => \my $Logfile,
    'ok-states=s' => \( my $OK_States = 'OK UP' ),
    'priority=i'  => \my $Priority,
    'status=s'    => \my $Status,
    'status-db=s' => \my $StatusDB,
    'target=s'    => \my $Target,
    'timeout=f'   => \( my $Timeout = 3 )
  )
  or exit 1;

my $NetSaint = noris::NetSaint->new;

die qq(Wrong usage; please call "$0 --help" for details.\n) if @ARGV;

my $log;
if ( defined $Logfile ) {
    unless ( sysopen $log, $Logfile, O_APPEND | O_CREAT | O_WRONLY ) {
        warn "sysopen('$log', O_APPEND | O_CREAT | O_WRONLY): $!\n";
    }
    elsif ( not flock $log, LOCK_EX ) {
        warn "flock('$log', LOCK_EX): $!\n";
    }
    else {
        print $log localtime() . " @argv";
    }
}

@Interfaces = DEFAULT_INTERFACES unless @Interfaces;

my %OK_State;
@OK_State{ split ' ', $OK_States } = ();

my %status;

if ( defined $Key && $Target =~ /\D/ ) {
    die "-key only works in conjunction with -status-db.\n"
      unless defined $StatusDB;
    dbmopen %status, $StatusDB, 0600 or die "dbmopen('$StatusDB',0600): $!\n";
    my ( $timestamp, $ticket ) = split ' ', $status{$Key}
      if defined $status{$Key};
    $Target = $ticket
      if $timestamp && $ticket
      and !$Expire || $timestamp + $Expire >= $^T;
}

my ( $interface, $socket );
for (@Interfaces) {
    $socket = IO::Socket::INET->new(
        PeerAddr => ( $interface = $_ ),
        $Timeout ? ( Timeout => $Timeout ) : ()
      )
      and last;

    # Fehlermeldung lautet bei lteren IO::Socket-Versionen
    # "IO::Socket::INET: Timeout", bei neueren
    # "IO::Socket::INET: connect: timeout"
    warn "Error connect()ing to $_: $@" unless $@ =~ /: Timeout$/i;
}

die "Could not connect to any interface.\n" unless $socket;
$socket->print("X-RT-Queue: noris.$Target\n")
  or warn "Error print()ing to $interface: $!\n";
if ( defined $Extern ) {
    $socket->print( 'X-RT-Extern: ' . ( $Extern ? 'yes' : 'no' ) . "\n" )
      or warn "Error print()ing to $interface: $!\n";
}
if ( defined $Info ) {
    $Info =~ s/\n+/ /g;
    $socket->print("X-RT-Info: $Info\n")
      or warn "Error print()ing to $interface: $!\n";
}
if ( defined $Kunde ) {
    $Kunde =~ s/\s+//g;
    $socket->print("X-RT-Kunde: $Kunde\n")
      or warn "Error print()ing to $interface: $!\n";
}
if ( defined $Priority ) {
    $socket->print("X-RT-Priority: $Priority\n")
      or warn "Error print()ing to $interface: $!\n";
}
while (<STDIN>) {
    $_ .= '.' if /^\.+$/;
    $socket->print($_) or warn "Error print()ing to $interface: $!\n";
}
$socket->print(".$CRLF");
my $ticket_seq;
{
    defined( $ticket_seq = <$socket> ) or last;

    # Hinweis auf Zustellung an hnliches Ticket ignorieren:
    redo if $ticket_seq =~ /^\(#\d+\)\n/;
}
$socket->close or die "Error closing connection to $interface: $!\n";

unless ( defined $ticket_seq ) {
    $NetSaint->update( Critical => "RT lieferte keine Ticket-ID.\n" );
}
elsif ( $ticket_seq !~ /^(\d+-\d+)\s*\z/ ) {
    $NetSaint->update( Critical => "Das RT sagt: $ticket_seq\n" );
}
else {
    $NetSaint->update( OK => "Der Comment landete in RT#$1." );
    if ( defined $Key ) {
        if ( defined $Status && exists $OK_State{$Status} ) {
            delete $status{$Key};
        }
        else { $ticket_seq =~ /^(\d+)/ and $status{$Key} = "$^T $1" }
    }
}

if ($log) {
    if ( defined $ticket_seq ) {
        $ticket_seq =~ /(.*)/;
        print $log " => RT#$1";
    }
    else {
        print $log " ! RT-Fehler\n";
    }
}

END { print $log "\n" if $log }

=head1 NAME

notify_rt - Alarme ans RT senden

=head1 SYNOPSE

 cat Alarmtext |
 notify_rt -key "$HOSTALIAS$/$SERVICEDESC$" \
           -status "$SERVICESTATE$"         \
           -target hotline

=head1 BESCHREIBUNG

Die Meldung selbst wird in Form einer Mail (inkl. Subject:-, jedoch ohne
To:-Header) von STDIN gelesen.

Man knnte Alarme auch ans RT mailen, aber die Verwendung dieses Scripts
bringt mehrere Vorteile:

=over 4

=item *

Es sagt einem, in welchem Ticket die Nachricht gelandet ist
und welche Sequence-Nummer sie hat (und zwar auf STDOUT).

=item *

Bei Angabe eines eineindeutigen L<Schlssels|key=s> fr den Dienst werden
Folgealarme (bis zum nchsten OK-Alert fr denselben Dienst) ins selbe Ticket
geschickt.
(Ausnahme: Wenn eine L<Expire-Zeit|expire=i> angegeben und der letzte Alarm
lter als diese Zeitspanne ist, wird ein neues Ticket begonnen.)

=back

=head1 OPTIONEN

Alle Optionen werden mittels
L<GetOptions() aus Getopt::Long|Getopt::Long/GetOptions> ausgewertet.

=over 4

=item expire=i

Zeit in Sekunden, nach der ein Eintrag der L<Status-Datenbank|/status-db>
ungltig werden soll

=item extern!

um explizit festzulegen, ob der Comment fr Kunden sichtbar sein soll, d. h. bei
C<--extern> ist er sichtbar, bei C<--noextern> nicht, und wenn man die Option
gar nicht verwendet, greift die allgemeine Magie, vgl. in RT#207789-2.

=item help|?

um (nur) diese Dokumentation anzeigen zu lassen

=item info=s

Info-Text, der im Ticket gesetzt werden soll

=item interface=s @

Host(s) und Port(s) der Socket-Schnittstelle(n) zum RT;
Default: krempel.office.noris.de:58123

=item key=s

Schlssel zur eineindeutigen Zuordnung des Alarms zu einem Service

Damit das funktioniert, muss ein Name fr die L<Status-DB|/-status-db> angegeben
werden!

=item kunde=s

Kunde, den das geffnete RT-Ticket bekommen
soll(, sofern ein neues geffnet wird)

=item logfile=s

Name einer Log-Datei, in der dann festgehalten wird, welche Ticket-Sequence
durch welchen Script-Aufruf entstanden ist, vgl. RT#221924

=item ok-states=s

Whitespace-getrennte Liste von Status, nach denen der Status gelscht werden
soll, so dass fr den nchsten Alarm dann ein neues Ticket begonnen wird.
Default: "OK UP"

=item priority=i

Prioritt, den das geffnete RT-Ticket bekommen
soll(, sofern ein neues geffnet wird)

=item status=s

aka Nagios-SERVICESTATE

=item status-db=s

Name der Datenbank-Datei, in der gespeichert wird, welcher Key zu welchem Ticket
gehrt

=item target=s

Nummer des Tickets bzw. Name der RT-Queue, in dem bzw. der der Alarm landen soll

=item timeout=f

Timeout fr Socket-Verbindungen in Sekunden

=back

=head1 AUTOR

 Martin H. Sluka <fany@noris.net>
 fr die noris network AG, RT#132348

=cut

