123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- #!/usr/bin/perl
- #https://github.com/zorkian/nagios-irc-bot/blob/master/nagiosirc.pl
- #http://www.update.uu.se/~zrajm/programs/irssi-scripts/fifo_remote.pl-0.5
- #https://github.com/mikegrb/irssi-scripts/blob/master/nagios-ack.pl
- # DONE
- # 1/ check nagios.log
- # 2/ setup an array of alerts
- # 3/ ability to remove-change alerts on new notification for same host/[service]
- # 4/ dialog with livestatus to replay full alerts on load/setup : irssi command /nagrefresh
- # 5/ define !recheck to nagios
- # 6/ ability to define chan/server on irssi configuration instead of bot config.
- # TODO
- # 3/ define an ACK command
- # define !ack irc command
- # use %ACK_INDEX by alert type HOST / SERVICE
- # use %ACK_INDEX_LEVEL by alert type CRIT/UP/DOWN/WARN/UNK…
- use strict;
- use vars qw($VERSION %IRSSI);
- use Irssi;
- use POSIX "sys_wait_h";
- use POSIX qw(strftime);
- use Term::ANSIColor qw/ :constants /;
- use Fcntl; # provides `O_NONBLOCK' and `O_RDONLY' constants
- our ( $FIFO, # fifo absolute filename (expanded from Irssi config)
- $FIFO_HANDLE, # fifo filehandle for `open' et al.
- $FIFO_TAG ); # fifo signal tag for `input_add'
- # probably not very useful for other people but who knows?
- $VERSION = "0.2.0";
- %IRSSI = (
- authors => 'asr',
- contact => 'root@lautre.net',
- name => 'nagios-ack',
- description => 'ack nagios alerts in irc / follow nagios log',
- license => 'GPLv2',
- url => 'http://www.lautre.net/',
- changed => '20141006',
- modules => ''
- );
- # Put signals to irssi ###################################################
- Irssi::settings_add_str($IRSSI{name}, # default fifo_remote_file
- 'fifo_remote_file', '/home/tc-14/var/nagios-fifo'); #
- Irssi::settings_add_str("nagios_ack", "nagios_ack_channel", "#naglautre");
- #Irssi::settings_add_str("nagios_ack", "nagios_ack_channel", "#root");
- Irssi::settings_add_str("nagios_ack", "nagios_ack_nick", "");
- Irssi::settings_add_str("nagios_ack", "nagios_command", "/var/lib/nagios3/rw/live");
- #Irssi::command_bind( 'ack', \&nagios_ack );
- #Irssi::command_bind( 'nagstat', \&nagios_status );
- Irssi::command_bind( 'nagrefresh', \&query_nagios_status );
- # CONFIG: point this where your Nagios configuration files live
- #my $nagioslog = "/var/log/nagios3/nagios.log";
- # To be used to check immediately / hosts-stats
- my $nagioscmd = "/var/lib/nagios3/rw/live";
- my %renot; # { "host" or "host:service" => time_last_notification }
- my @cmdqueue = ();
- my %ignore = ();
- my @ACKS;
- my %ACK_Ind;
- my %C = (
- K => "\x0301", # 00. White
- B => "\x0302", # 01. Black
- G => "\x0303", # 02. Blue (Navy)
- R => "\x0304", # 03. Green
- # 04. Red
- V => "\x0306", # 05. Brown (Maroon)
- O => "\x0307", # 06. Purple
- Y => "\x0308", # 07. Orange
- Gg => "\x0309", # 08. Yellow
- # 09. Light Green (Lime)
- C => "\x0311", # 10. Teal (Green/Blue Cyan)
- Bb => "\x0312", # 11. Light Cyan (Cyan) (Aqua)
- Ma => "\x0313", # 12. Light Blue (Royal)
- Gr => "\x0314", # 13. Pink (Light Purple) (Fuchsia)
- W => "\x0315", # 14. Grey
- # 15. Light Grey (Silver)
- Z => "\x03",
- );
- my @NagStates = qw/UP DOWN WARNING UNKNOWN .. .. .. .. .. ..
- OK WARNING CRITICAL UNKNOWN/;
- # Bold: U+0002 ("0x02") — Example: ^Bold Text^ whereas ^ represents the control character.
- # Italics: U+001D ("0x1D") — Example: ^Italicized Text^ whereas ^ represents the control character.
- # Underline: U+001F ("0x1F") — Example: ^Underlined Text^ whereas ^ represents the control character.
- # The control character used for color is U+0003 ("0x03").
- my $state_to_color = {
- OK => $C{G},
- UP => $C{G},
- WARNING => $C{Y},
- CRITICAL => $C{R},
- DOWN => $C{V},
- UNKNOWN => $C{Gr},
- };
- my ($status_line);
- my @match;
- my ($msg,$num);
- my ($message,$i);
- my ($type,$data);
- my ($d,$type,$host,$service,$state,$output);
- my ( $host, $svc, $state, $msg, $id );
- #my $state_to_color = { OK => '', UP => '', WARNING => '', CRITICAL => '', DOWN => '', UNKNOWN => '' };
- # simple subs
- sub TRUE() { 1 } # some constants [perlsyn(1)
- sub FALSE() { "" } # "Constant Functions"]
- sub DEBUG(@) { print "%B", join(":", @_),"%n" }# DEBUG thingy
- # Acknowledge alerts #####################################################
- my $last_alert;
- my ($server, $msg, $nick, $addr, $target);
- my ( $param, $server, $window );
- my (@issue, $issue);
- #sub on_public {
- # ($server, $msg, $nick, $addr, $target) = @_;
- # # return unless $target eq Irssi::setting_get_str("nagios_ack_channel") && $nick eq Irssi::setting_get_str("nagios_ack_nick");
- # $last_alert = $msg if $msg =~ m/^PROBLEM/;
- # return;
- #}
- my $DEBUG=Irssi::settings_get_str("nagios_ack_channel");
- sub nagios_ack {
- ( $param, $server, $window ) = @_;
- @issue = parse_status();
- if (!@issue) {
- #$window->print("Failed to parse last status: $last_alert");
- }
- my $message = " ACK ".$param . join ' ', reverse @issue;
- Irssi::active_server->command('MSG ' . $DEBUG . $message);
- }
- sub nagios_check {
- #1412375470;filer2;DISK_all;2;W=10% C=5%
- ( $param, $server, $window ) = @_;
- @issue = parse_status();
- if (!@issue) {
- #$window->print("Failed to parse last status: $last_alert");
- }
- my $message = " CHECK ".$param . join ' ', reverse @issue;
- Irssi::active_server->command('MSG ' . $DEBUG . $message);
- # validate_alert($$$$$$) { # $d,$type,$host,$state,$output,$service
- }
- sub nagios_status {
- (undef, undef, $window) = @_;
- $issue = join ',', map { "'" . $_ . "'" } reverse parse_status();
- $window->print("Last issue: '$last_alert' ($issue)");
- }
- sub parse_status {
- return $1 if $last_alert =~ /^PROBLEM - (\S+) is DOWN/;
- return ($1, $2) if $last_alert =~ /^PROBLEM - (\S+) on (\S+) is/;
- return;
- }
- sub nagios_inject {
- $last_alert = shift;
- }
- sub time2date($) {
- my ($d)=@_;
- return strftime("%d/%m/%y %H:%M", localtime($d));
- }
- sub mark($$$) {
- my ($Flag,$I,$state)=@_;
- my $M=sprintf('%02d', scalar @ACKS % 100);
- my $N=sprintf('%02d', $I % 100);
- if (($I == 0) && (scalar @ACKS == 0)) {
- return "[".$C{W}.$Flag.$C{Z}." ]/$M ";
- } else {
- return "[".$C{W}.$Flag.$state_to_color->{$state}.$N.$C{Z}."]/$M ";
- }
- }
- # Add alert to array / index
- sub ackable {
- #Irssi::print(">> On vire $host/$svc");
- my $i;
- ( $host, $svc, $state, $msg, $d ) = @_;
- # ALERT
- if ( $state eq 'WARNING' || $state eq 'CRITICAL' || $state eq 'UNKNOWN' || $state eq 'DOWN' ) {
- while ($i <= $#ACKS) {
- # Update, or insert ?
- if (($ACKS[$i]->[0] eq $host) && ($ACKS[$i]->[1] eq $svc)) {
- $ACKS[$i]->[4]=$d;
- Irssi::print(">> found $i");
- if ($ACKS[$i]->[2] ne $state) {
- # Same alert, but different level
- $ACKS[$i]->[2]=$state;
- return mark ('C', $i, $state);
- } else {
- # Same alert
- return mark ('!', $i, $state);
- }
- }
- $i++;
- }
- # New alert, insert.
- push (@ACKS, [ $host, $svc, $state, $msg, $d ] );
- return mark('+',scalar @ACKS,$state);
- # Clear alert
- } else {
- # Irssi::print(">> On vire $host/$svc");
- $i=0;
- # Find alert (need to be used by service/host index)
- while ($i <= $#ACKS) {
- if (($ACKS[$i]->[0] eq $host) && ($ACKS[$i]->[1] eq $svc)) {
- $ACKS[$i] = pop @ACKS;
- $i=$#ACKS;
- Irssi::print(">> found $i");
- return mark('-',$i+1,$state);
- }
- $i++;
- }
- return mark('?',0,$state);
- }
- }
- ##########################################################################
- # Logfile management #####################################################
- # disable fifo and erase fifo file
- sub destroy_fifo($) { # [2004-08-14]
- my ($fifo) = @_; # get args
- if (defined $FIFO_TAG) { # if fifo signal is active
- Irssi::input_remove($FIFO_TAG); # disable fifo signal
- undef $FIFO_TAG; # and forget its tag
- } #
- if (defined $FIFO_HANDLE) { # if fifo is open
- close $FIFO_HANDLE; # close it
- undef $FIFO_HANDLE; # and forget handle
- } #
- if (-p $fifo) { # if named fifo exists
- unlink $fifo; # erase fifo file
- undef $FIFO; # and forget filename
- } #
- return 1; # return
- } #
- # Open logfile/fifo
- sub open_fifo($) { # [2004-08-14]
- my ($fifo) = @_; # get args
- if (not sysopen $FIFO_HANDLE, $fifo, # open fifo for non-blocking
- O_NONBLOCK | O_RDONLY) { # reading
- print CLIENTERROR "could not open nagios logfile for reading";
- return ""; #
- } #
- Irssi::input_remove($FIFO_TAG) # disable fifo reading signal
- if defined $FIFO_TAG; # if there is one
- $FIFO_TAG = Irssi::input_add # set up signal called when
- fileno($FIFO_HANDLE), INPUT_READ, # there's input in the pipe
- \&read_fifo, ''; #
- return 1; #
- }
- # read from fifo
- # (called by fifo input signal)
- sub read_fifo() { # [2004-08-14]
- # Read logfile ###########################################################
- foreach (<$FIFO_HANDLE>) { # for each input line
- chomp; # strip trailing newline
- parse_naglog($_); #if (/ALERT/ && /HARD;/);
- #Irssi::active_win->print( # show incoming commands (debug)
- # "\u$IRSSI{name} received command: \"$_\"", #
- # MSGLEVEL_CLIENTNOTICE); #
- # Irssi::active_win->command($_); # run incoming commands
- } #
- open_fifo($FIFO); # re-open fifo
- # TODO: Is the above re-opening of fifo really necessary? -- If not
- # invoked here `read_fifo' is called repeatedly, even though no input
- # is to be found on the fifo. (This seems a waste of resources to me.)
- }
- # create new fifo (erase any old) and get command prefix
- # (called on script loading and on user /set)
- sub setup() { # [2004-08-13]
- my $new_fifo = Irssi::settings_get_str # setting from Irssi
- 'fifo_remote_file'; # (and add path to it)
- return if $new_fifo eq $FIFO and -p $FIFO; # do nada if already exists
- destroy_fifo($FIFO) if -p $FIFO; # destroy old fifo
- create_fifo($new_fifo) # create new fifo
- and $FIFO = $new_fifo; # and remember that fifo
- # To ADD :
- # request to livestatus to fetch stored alerts
- }
- # create named fifo and open it for input
- # (called on script load and fifo name changes)
- sub create_fifo($) { # [2004-08-14]
- my ($new_fifo) = @_; # get args
- if (not -p $new_fifo) { # create fifo if non-existant
- if (system "mkfifo '$new_fifo' &>/dev/null" and
- system "chmod 777 '$new_fifo' &>/dev/null" and
- system "mknod '$new_fifo' &>/dev/null"){
- print CLIENTERROR "`mkfifo' failed -- could not create named pipe";
- # TODO: capture `mkfifo's stderr and show that here
- return ""; #
- } #
- } #
- $FIFO = $new_fifo; # remember fifo name
- open_fifo($new_fifo); # open fifo for reading
- } #
- # Query nagios ###########################################################
- sub query_nagios_status($){
- # 1412478149;hyppocampe;apt;2;CRITICAL : unknown: qemu-utils, qemu-kvm, qemu-keymaps
- my ($server)=@_;
- my @unixcat;
- my $unixline;
- foreach $unixline ( `echo "GET services
- Columns: last_state_change host_name display_name state plugin_output
- Filter: state > 1" | unixcat $nagioscmd` ) {
- ($d,$host,$service,$state,$output)=split /;/,$unixline;
- validate_alert($d,'SERVICE',$host,$NagStates[$state+10],$output,$service);
- Irssi::print("%B>>%n $unixline", MSGLEVEL_CLIENTCRAP);
- }
- return;
- foreach $unixline ( `echo "GET hosts
- Columns: last_state_change host_name state plugin_output
- Filter: state > 1" | unixcat $nagioscmd` ) {
- ($d,$host,$state,$output)=split /;/,$unixline;
- validate_alert($d,'HOST',$host,$NagStates[$state],$output,"");
- Irssi::print("%B>>%n $unixline", MSGLEVEL_CLIENTCRAP);
- }
- # cat << EOF | unixcat /var/lib/nagios3/rw/live
- # GET services
- # Columns: last_state_change host_name display_name state plugin_output
- # Filter: state > 1
- # EOF
- }
- # Parse alert to find vars ###############################################
- sub parse_naglog(){
- $d=0; $status_line=""; $host="";$output="";$service="";
- # Search for services/host informations, and post them to IRC ############
- #
- # Delimiter : ; for logs; @ for direct nagios custom notification command
- #
- # [1412330770] SERVICE ALERT: ella;IMAPs_LOGIN;OK;SOFT;2;OK - CO1N OK LOGIN Ok.
- # /\[\d+\] (\w+) ALERT: (\w+);(\w+);(\w+);HARD;(\d+);(.+)/
- # [1410969598] HOST ALERT: filou;DOWN;SOFT;1;PING CRITICAL - Paquets perdus = 100%
- # PROCESS type ALERT: host;service;STATE1;HARD;num;commentaire
- if (@match=$status_line =~ /\[?(\d+)\]? HOST ALERT: ([^@;]+)[@;](\w+)[@;]HARD[@;].*[@;](.+)/) {
- # HOST ########################
- ($d,$host,$state,$output)=@match;
- validate_alert($d,"HOST",$host,$state,$output,$service);
-
- # SERVICE #####################
- } elsif (@match=$status_line =~ /\[?(\d+)\]? (\w+) ALERT: ([^;@]+)[@;]([^;@]+)[@;](\w+)[@;]HARD[@;].*[@;](.+)/) {
- ($d,$type,$host,$service,$state,$output)=@match;
- $service=~s/[^\w\d_-]/_/g;
- validate_alert($d,$type,$host,$state,$output,$service);
- # OTHER #######################
- } elsif (@match=$status_line =~ /\[\d+\] (\w+) ALERT: (.*)/) {
- ($type,$data)=@match;
- Irssi::print( "%B>>%n $IRSSI{name} $type - $data", MSGLEVEL_CLIENTCRAP) unless ($status_line =~ /[;@]SOFT[@;]/);
- # FALLBACK ####################
- } else {
- Irssi::print( #
- "%B>>%n $IRSSI{name} received command: \"$_\"",
- MSGLEVEL_CLIENTCRAP); #
- next
- }
- }
- sub validate_alert($$$$$$) { # $d,$type,$host,$state,$output,$service
- ($d,$type,$host,$state,$output,$service)=@_;
- next if exists $renot{"$host:$service"} && $renot{"$host:$service"} >= time() - 5;
- $renot{"$host:$service"} = time();
- #################
- # HOST
- $id = ackable($host,$service,$state,$output,$d);
- $msg = "$id".$state_to_color->{$state} . "$host:$service".$C{Z}." is $state : $output";
- #Irssi::print( "%B>>%n $IRSSI{name} $msg", MSGLEVEL_CLIENTCRAP);
- $last_alert="NAGIOS - $service/$host is $state";
- $d=time2date($d);
- $message = " $d - $last_alert - $msg";
- Irssi::active_server->command('MSG ' . Irssi::settings_get_str("nagios_ack_channel") .
- " ".$message);
- # Irssi::print( "%B>>%n $IRSSI{name} $msg", MSGLEVEL_CLIENTCRAP);
- }
- sub event_privmsg {
- # Commamd channel
- my $K;
- my ($server, $data, $nick, $mask) =@_;
- my ($target, $text) = $data =~ /^(\S*)\s:(.*)/;
- #print ( "C:$target X:$text A:$admin D:$warndate L:$last W:$warn N:$nick D:$data" );
- if ( $text =~ /^!nagios ?(.*)/i ) {
- # !nagios [*] :
- $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
- " ".scalar @ACKS." alertes");
- return unless $#ACKS > 0;
- my $i=0;
-
- # !nagios list [*] :
- if ($1 =~ /^refresh/i) {
- query_nagios_status($server);
- }
- if ($1 =~ /^list/i) {
- foreach $K (@ACKS) {
- $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
- " ". mark(' ',$i++,$K->[2]) ." ".$K->[0]." / $K->[1] / $K->[2] / $K->[3] / ".time2date $K->[4]);
- }
- }
- elsif ($1 =~ /^help/i) {
- $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
- " !nagios list : liste des alertes nagios reçuesici");
- $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
- " !nagios help : l'aide");
- $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
- " !nagios ack <#ALERTE> <message> : aquitte l'alerte");
- $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
- " !nagios check <#ALERTE> : recheck une alerte donnée (service/host)");
- $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
- " !nagios refresh : interroge le nagios pour avoir la liste de toutes les alertes");
- }
- return 1;
- } elsif ( $text =~ /^check ?(.*)/i ){
- nagios_check($1,$server,undef);
- } elsif ( $text =~ /^ack ?(.*)/i ){
- nagios_ack($1,$server,undef);
- }
- }
- ##########################################################################
- # Main ###################################################################
- ##########################################################################
- print "starting...\n";
- # clean up fifo on unload
- # (called on /script unload)
- Irssi::signal_add_first #
- 'command script unload', sub { # [2004-08-13]
- my ($script) = @_; # get args
- return unless $script =~ # only do cleanup when
- /(?:^|\s) $IRSSI{name} # unloading *this* script
- (?:\.[^. ]*)? (?:\s|$) /x; #
- destroy_fifo($FIFO) if -p $FIFO; # destroy old fifo
- Irssi::print("%B>>%n $IRSSI{name} $VERSION unloaded", MSGLEVEL_CLIENTCRAP);
- }; #
- setup(); # initialize setup values
- Irssi::signal_add('event privmsg', 'event_privmsg');
- Irssi::signal_add("message public", "event_privmsg");
- #Irssi::signal_add_first("message public", "on_public");
- Irssi::signal_add('setup changed', \&setup); # re-read setup when it changes
- print CLIENTCRAP "%B>>%n $IRSSI{name} $VERSION (by $IRSSI{authors}) loaded";
- 1;
|