nagios-fifo.pl 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. #!/usr/bin/perl
  2. #https://github.com/zorkian/nagios-irc-bot/blob/master/nagiosirc.pl
  3. #http://www.update.uu.se/~zrajm/programs/irssi-scripts/fifo_remote.pl-0.5
  4. #https://github.com/mikegrb/irssi-scripts/blob/master/nagios-ack.pl
  5. # DONE
  6. # 1/ check nagios.log
  7. # 2/ setup an array of alerts
  8. # 3/ ability to remove-change alerts on new notification for same host/[service]
  9. # 4/ dialog with livestatus to replay full alerts on load/setup : irssi command /nagrefresh
  10. # 5/ define !recheck to nagios
  11. # 6/ ability to define chan/server on irssi configuration instead of bot config.
  12. # TODO
  13. # 3/ define an ACK command
  14. # define !ack irc command
  15. # use %ACK_INDEX by alert type HOST / SERVICE
  16. # use %ACK_INDEX_LEVEL by alert type CRIT/UP/DOWN/WARN/UNK…
  17. use strict;
  18. use vars qw($VERSION %IRSSI);
  19. use Irssi;
  20. use POSIX "sys_wait_h";
  21. use POSIX qw(strftime);
  22. use Term::ANSIColor qw/ :constants /;
  23. use Fcntl; # provides `O_NONBLOCK' and `O_RDONLY' constants
  24. our ( $FIFO, # fifo absolute filename (expanded from Irssi config)
  25. $FIFO_HANDLE, # fifo filehandle for `open' et al.
  26. $FIFO_TAG ); # fifo signal tag for `input_add'
  27. # probably not very useful for other people but who knows?
  28. $VERSION = "0.2.0";
  29. %IRSSI = (
  30. authors => 'asr',
  31. contact => 'root@lautre.net',
  32. name => 'nagios-ack',
  33. description => 'ack nagios alerts in irc / follow nagios log',
  34. license => 'GPLv2',
  35. url => 'http://www.lautre.net/',
  36. changed => '20141006',
  37. modules => ''
  38. );
  39. # Put signals to irssi ###################################################
  40. Irssi::settings_add_str($IRSSI{name}, # default fifo_remote_file
  41. 'fifo_remote_file', '/home/tc-14/var/nagios-fifo'); #
  42. Irssi::settings_add_str("nagios_ack", "nagios_ack_channel", "#naglautre");
  43. #Irssi::settings_add_str("nagios_ack", "nagios_ack_channel", "#root");
  44. Irssi::settings_add_str("nagios_ack", "nagios_ack_nick", "");
  45. Irssi::settings_add_str("nagios_ack", "nagios_command", "/var/lib/nagios3/rw/live");
  46. #Irssi::command_bind( 'ack', \&nagios_ack );
  47. #Irssi::command_bind( 'nagstat', \&nagios_status );
  48. Irssi::command_bind( 'nagrefresh', \&query_nagios_status );
  49. # CONFIG: point this where your Nagios configuration files live
  50. #my $nagioslog = "/var/log/nagios3/nagios.log";
  51. # To be used to check immediately / hosts-stats
  52. my $nagioscmd = "/var/lib/nagios3/rw/live";
  53. my %renot; # { "host" or "host:service" => time_last_notification }
  54. my @cmdqueue = ();
  55. my %ignore = ();
  56. my @ACKS;
  57. my %ACK_Ind;
  58. my %C = (
  59. K => "\x0301", # 00. White
  60. B => "\x0302", # 01. Black
  61. G => "\x0303", # 02. Blue (Navy)
  62. R => "\x0304", # 03. Green
  63. # 04. Red
  64. V => "\x0306", # 05. Brown (Maroon)
  65. O => "\x0307", # 06. Purple
  66. Y => "\x0308", # 07. Orange
  67. Gg => "\x0309", # 08. Yellow
  68. # 09. Light Green (Lime)
  69. C => "\x0311", # 10. Teal (Green/Blue Cyan)
  70. Bb => "\x0312", # 11. Light Cyan (Cyan) (Aqua)
  71. Ma => "\x0313", # 12. Light Blue (Royal)
  72. Gr => "\x0314", # 13. Pink (Light Purple) (Fuchsia)
  73. W => "\x0315", # 14. Grey
  74. # 15. Light Grey (Silver)
  75. Z => "\x03",
  76. );
  77. my @NagStates = qw/UP DOWN WARNING UNKNOWN .. .. .. .. .. ..
  78. OK WARNING CRITICAL UNKNOWN/;
  79. # Bold: U+0002 ("0x02") — Example: ^Bold Text^ whereas ^ represents the control character.
  80. # Italics: U+001D ("0x1D") — Example: ^Italicized Text^ whereas ^ represents the control character.
  81. # Underline: U+001F ("0x1F") — Example: ^Underlined Text^ whereas ^ represents the control character.
  82. # The control character used for color is U+0003 ("0x03").
  83. my $state_to_color = {
  84. OK => $C{G},
  85. UP => $C{G},
  86. WARNING => $C{Y},
  87. CRITICAL => $C{R},
  88. DOWN => $C{V},
  89. UNKNOWN => $C{Gr},
  90. };
  91. my ($status_line);
  92. my @match;
  93. my ($msg,$num);
  94. my ($message,$i);
  95. my ($type,$data);
  96. my ($d,$type,$host,$service,$state,$output);
  97. my ( $host, $svc, $state, $msg, $id );
  98. #my $state_to_color = { OK => '', UP => '', WARNING => '', CRITICAL => '', DOWN => '', UNKNOWN => '' };
  99. # simple subs
  100. sub TRUE() { 1 } # some constants [perlsyn(1)
  101. sub FALSE() { "" } # "Constant Functions"]
  102. sub DEBUG(@) { print "%B", join(":", @_),"%n" }# DEBUG thingy
  103. # Acknowledge alerts #####################################################
  104. my $last_alert;
  105. my ($server, $msg, $nick, $addr, $target);
  106. my ( $param, $server, $window );
  107. my (@issue, $issue);
  108. #sub on_public {
  109. # ($server, $msg, $nick, $addr, $target) = @_;
  110. # # return unless $target eq Irssi::setting_get_str("nagios_ack_channel") && $nick eq Irssi::setting_get_str("nagios_ack_nick");
  111. # $last_alert = $msg if $msg =~ m/^PROBLEM/;
  112. # return;
  113. #}
  114. my $DEBUG=Irssi::settings_get_str("nagios_ack_channel");
  115. sub nagios_ack {
  116. ( $param, $server, $window ) = @_;
  117. @issue = parse_status();
  118. if (!@issue) {
  119. #$window->print("Failed to parse last status: $last_alert");
  120. }
  121. my $message = " ACK ".$param . join ' ', reverse @issue;
  122. Irssi::active_server->command('MSG ' . $DEBUG . $message);
  123. }
  124. sub nagios_check {
  125. #1412375470;filer2;DISK_all;2;W=10% C=5%
  126. ( $param, $server, $window ) = @_;
  127. @issue = parse_status();
  128. if (!@issue) {
  129. #$window->print("Failed to parse last status: $last_alert");
  130. }
  131. my $message = " CHECK ".$param . join ' ', reverse @issue;
  132. Irssi::active_server->command('MSG ' . $DEBUG . $message);
  133. # validate_alert($$$$$$) { # $d,$type,$host,$state,$output,$service
  134. }
  135. sub nagios_status {
  136. (undef, undef, $window) = @_;
  137. $issue = join ',', map { "'" . $_ . "'" } reverse parse_status();
  138. $window->print("Last issue: '$last_alert' ($issue)");
  139. }
  140. sub parse_status {
  141. return $1 if $last_alert =~ /^PROBLEM - (\S+) is DOWN/;
  142. return ($1, $2) if $last_alert =~ /^PROBLEM - (\S+) on (\S+) is/;
  143. return;
  144. }
  145. sub nagios_inject {
  146. $last_alert = shift;
  147. }
  148. sub time2date($) {
  149. my ($d)=@_;
  150. return strftime("%d/%m/%y %H:%M", localtime($d));
  151. }
  152. sub mark($$$) {
  153. my ($Flag,$I,$state)=@_;
  154. my $M=sprintf('%02d', scalar @ACKS % 100);
  155. my $N=sprintf('%02d', $I % 100);
  156. if (($I == 0) && (scalar @ACKS == 0)) {
  157. return "[".$C{W}.$Flag.$C{Z}." ]/$M ";
  158. } else {
  159. return "[".$C{W}.$Flag.$state_to_color->{$state}.$N.$C{Z}."]/$M ";
  160. }
  161. }
  162. # Add alert to array / index
  163. sub ackable {
  164. #Irssi::print(">> On vire $host/$svc");
  165. my $i;
  166. ( $host, $svc, $state, $msg, $d ) = @_;
  167. # ALERT
  168. if ( $state eq 'WARNING' || $state eq 'CRITICAL' || $state eq 'UNKNOWN' || $state eq 'DOWN' ) {
  169. while ($i <= $#ACKS) {
  170. # Update, or insert ?
  171. if (($ACKS[$i]->[0] eq $host) && ($ACKS[$i]->[1] eq $svc)) {
  172. $ACKS[$i]->[4]=$d;
  173. Irssi::print(">> found $i");
  174. if ($ACKS[$i]->[2] ne $state) {
  175. # Same alert, but different level
  176. $ACKS[$i]->[2]=$state;
  177. return mark ('C', $i, $state);
  178. } else {
  179. # Same alert
  180. return mark ('!', $i, $state);
  181. }
  182. }
  183. $i++;
  184. }
  185. # New alert, insert.
  186. push (@ACKS, [ $host, $svc, $state, $msg, $d ] );
  187. return mark('+',scalar @ACKS,$state);
  188. # Clear alert
  189. } else {
  190. # Irssi::print(">> On vire $host/$svc");
  191. $i=0;
  192. # Find alert (need to be used by service/host index)
  193. while ($i <= $#ACKS) {
  194. if (($ACKS[$i]->[0] eq $host) && ($ACKS[$i]->[1] eq $svc)) {
  195. $ACKS[$i] = pop @ACKS;
  196. $i=$#ACKS;
  197. Irssi::print(">> found $i");
  198. return mark('-',$i+1,$state);
  199. }
  200. $i++;
  201. }
  202. return mark('?',0,$state);
  203. }
  204. }
  205. ##########################################################################
  206. # Logfile management #####################################################
  207. # disable fifo and erase fifo file
  208. sub destroy_fifo($) { # [2004-08-14]
  209. my ($fifo) = @_; # get args
  210. if (defined $FIFO_TAG) { # if fifo signal is active
  211. Irssi::input_remove($FIFO_TAG); # disable fifo signal
  212. undef $FIFO_TAG; # and forget its tag
  213. } #
  214. if (defined $FIFO_HANDLE) { # if fifo is open
  215. close $FIFO_HANDLE; # close it
  216. undef $FIFO_HANDLE; # and forget handle
  217. } #
  218. if (-p $fifo) { # if named fifo exists
  219. unlink $fifo; # erase fifo file
  220. undef $FIFO; # and forget filename
  221. } #
  222. return 1; # return
  223. } #
  224. # Open logfile/fifo
  225. sub open_fifo($) { # [2004-08-14]
  226. my ($fifo) = @_; # get args
  227. if (not sysopen $FIFO_HANDLE, $fifo, # open fifo for non-blocking
  228. O_NONBLOCK | O_RDONLY) { # reading
  229. print CLIENTERROR "could not open nagios logfile for reading";
  230. return ""; #
  231. } #
  232. Irssi::input_remove($FIFO_TAG) # disable fifo reading signal
  233. if defined $FIFO_TAG; # if there is one
  234. $FIFO_TAG = Irssi::input_add # set up signal called when
  235. fileno($FIFO_HANDLE), INPUT_READ, # there's input in the pipe
  236. \&read_fifo, ''; #
  237. return 1; #
  238. }
  239. # read from fifo
  240. # (called by fifo input signal)
  241. sub read_fifo() { # [2004-08-14]
  242. # Read logfile ###########################################################
  243. foreach (<$FIFO_HANDLE>) { # for each input line
  244. chomp; # strip trailing newline
  245. parse_naglog($_); #if (/ALERT/ && /HARD;/);
  246. #Irssi::active_win->print( # show incoming commands (debug)
  247. # "\u$IRSSI{name} received command: \"$_\"", #
  248. # MSGLEVEL_CLIENTNOTICE); #
  249. # Irssi::active_win->command($_); # run incoming commands
  250. } #
  251. open_fifo($FIFO); # re-open fifo
  252. # TODO: Is the above re-opening of fifo really necessary? -- If not
  253. # invoked here `read_fifo' is called repeatedly, even though no input
  254. # is to be found on the fifo. (This seems a waste of resources to me.)
  255. }
  256. # create new fifo (erase any old) and get command prefix
  257. # (called on script loading and on user /set)
  258. sub setup() { # [2004-08-13]
  259. my $new_fifo = Irssi::settings_get_str # setting from Irssi
  260. 'fifo_remote_file'; # (and add path to it)
  261. return if $new_fifo eq $FIFO and -p $FIFO; # do nada if already exists
  262. destroy_fifo($FIFO) if -p $FIFO; # destroy old fifo
  263. create_fifo($new_fifo) # create new fifo
  264. and $FIFO = $new_fifo; # and remember that fifo
  265. # To ADD :
  266. # request to livestatus to fetch stored alerts
  267. }
  268. # create named fifo and open it for input
  269. # (called on script load and fifo name changes)
  270. sub create_fifo($) { # [2004-08-14]
  271. my ($new_fifo) = @_; # get args
  272. if (not -p $new_fifo) { # create fifo if non-existant
  273. if (system "mkfifo '$new_fifo' &>/dev/null" and
  274. system "chmod 777 '$new_fifo' &>/dev/null" and
  275. system "mknod '$new_fifo' &>/dev/null"){
  276. print CLIENTERROR "`mkfifo' failed -- could not create named pipe";
  277. # TODO: capture `mkfifo's stderr and show that here
  278. return ""; #
  279. } #
  280. } #
  281. $FIFO = $new_fifo; # remember fifo name
  282. open_fifo($new_fifo); # open fifo for reading
  283. } #
  284. # Query nagios ###########################################################
  285. sub query_nagios_status($){
  286. # 1412478149;hyppocampe;apt;2;CRITICAL : unknown: qemu-utils, qemu-kvm, qemu-keymaps
  287. my ($server)=@_;
  288. my @unixcat;
  289. my $unixline;
  290. foreach $unixline ( `echo "GET services
  291. Columns: last_state_change host_name display_name state plugin_output
  292. Filter: state > 1" | unixcat $nagioscmd` ) {
  293. ($d,$host,$service,$state,$output)=split /;/,$unixline;
  294. validate_alert($d,'SERVICE',$host,$NagStates[$state+10],$output,$service);
  295. Irssi::print("%B>>%n $unixline", MSGLEVEL_CLIENTCRAP);
  296. }
  297. return;
  298. foreach $unixline ( `echo "GET hosts
  299. Columns: last_state_change host_name state plugin_output
  300. Filter: state > 1" | unixcat $nagioscmd` ) {
  301. ($d,$host,$state,$output)=split /;/,$unixline;
  302. validate_alert($d,'HOST',$host,$NagStates[$state],$output,"");
  303. Irssi::print("%B>>%n $unixline", MSGLEVEL_CLIENTCRAP);
  304. }
  305. # cat << EOF | unixcat /var/lib/nagios3/rw/live
  306. # GET services
  307. # Columns: last_state_change host_name display_name state plugin_output
  308. # Filter: state > 1
  309. # EOF
  310. }
  311. # Parse alert to find vars ###############################################
  312. sub parse_naglog(){
  313. $d=0; $status_line=""; $host="";$output="";$service="";
  314. # Search for services/host informations, and post them to IRC ############
  315. #
  316. # Delimiter : ; for logs; @ for direct nagios custom notification command
  317. #
  318. # [1412330770] SERVICE ALERT: ella;IMAPs_LOGIN;OK;SOFT;2;OK - CO1N OK LOGIN Ok.
  319. # /\[\d+\] (\w+) ALERT: (\w+);(\w+);(\w+);HARD;(\d+);(.+)/
  320. # [1410969598] HOST ALERT: filou;DOWN;SOFT;1;PING CRITICAL - Paquets perdus = 100%
  321. # PROCESS type ALERT: host;service;STATE1;HARD;num;commentaire
  322. if (@match=$status_line =~ /\[?(\d+)\]? HOST ALERT: ([^@;]+)[@;](\w+)[@;]HARD[@;].*[@;](.+)/) {
  323. # HOST ########################
  324. ($d,$host,$state,$output)=@match;
  325. validate_alert($d,"HOST",$host,$state,$output,$service);
  326. # SERVICE #####################
  327. } elsif (@match=$status_line =~ /\[?(\d+)\]? (\w+) ALERT: ([^;@]+)[@;]([^;@]+)[@;](\w+)[@;]HARD[@;].*[@;](.+)/) {
  328. ($d,$type,$host,$service,$state,$output)=@match;
  329. $service=~s/[^\w\d_-]/_/g;
  330. validate_alert($d,$type,$host,$state,$output,$service);
  331. # OTHER #######################
  332. } elsif (@match=$status_line =~ /\[\d+\] (\w+) ALERT: (.*)/) {
  333. ($type,$data)=@match;
  334. Irssi::print( "%B>>%n $IRSSI{name} $type - $data", MSGLEVEL_CLIENTCRAP) unless ($status_line =~ /[;@]SOFT[@;]/);
  335. # FALLBACK ####################
  336. } else {
  337. Irssi::print( #
  338. "%B>>%n $IRSSI{name} received command: \"$_\"",
  339. MSGLEVEL_CLIENTCRAP); #
  340. next
  341. }
  342. }
  343. sub validate_alert($$$$$$) { # $d,$type,$host,$state,$output,$service
  344. ($d,$type,$host,$state,$output,$service)=@_;
  345. next if exists $renot{"$host:$service"} && $renot{"$host:$service"} >= time() - 5;
  346. $renot{"$host:$service"} = time();
  347. #################
  348. # HOST
  349. $id = ackable($host,$service,$state,$output,$d);
  350. $msg = "$id".$state_to_color->{$state} . "$host:$service".$C{Z}." is $state : $output";
  351. #Irssi::print( "%B>>%n $IRSSI{name} $msg", MSGLEVEL_CLIENTCRAP);
  352. $last_alert="NAGIOS - $service/$host is $state";
  353. $d=time2date($d);
  354. $message = " $d - $last_alert - $msg";
  355. Irssi::active_server->command('MSG ' . Irssi::settings_get_str("nagios_ack_channel") .
  356. " ".$message);
  357. # Irssi::print( "%B>>%n $IRSSI{name} $msg", MSGLEVEL_CLIENTCRAP);
  358. }
  359. sub event_privmsg {
  360. # Commamd channel
  361. my $K;
  362. my ($server, $data, $nick, $mask) =@_;
  363. my ($target, $text) = $data =~ /^(\S*)\s:(.*)/;
  364. #print ( "C:$target X:$text A:$admin D:$warndate L:$last W:$warn N:$nick D:$data" );
  365. if ( $text =~ /^!nagios ?(.*)/i ) {
  366. # !nagios [*] :
  367. $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
  368. " ".scalar @ACKS." alertes");
  369. return unless $#ACKS > 0;
  370. my $i=0;
  371. # !nagios list [*] :
  372. if ($1 =~ /^refresh/i) {
  373. query_nagios_status($server);
  374. }
  375. if ($1 =~ /^list/i) {
  376. foreach $K (@ACKS) {
  377. $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
  378. " ". mark(' ',$i++,$K->[2]) ." ".$K->[0]." / $K->[1] / $K->[2] / $K->[3] / ".time2date $K->[4]);
  379. }
  380. }
  381. elsif ($1 =~ /^help/i) {
  382. $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
  383. " !nagios list : liste des alertes nagios reçuesici");
  384. $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
  385. " !nagios help : l'aide");
  386. $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
  387. " !nagios ack <#ALERTE> <message> : aquitte l'alerte");
  388. $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
  389. " !nagios check <#ALERTE> : recheck une alerte donnée (service/host)");
  390. $server->command ( "msg ".Irssi::settings_get_str("nagios_ack_channel").
  391. " !nagios refresh : interroge le nagios pour avoir la liste de toutes les alertes");
  392. }
  393. return 1;
  394. } elsif ( $text =~ /^check ?(.*)/i ){
  395. nagios_check($1,$server,undef);
  396. } elsif ( $text =~ /^ack ?(.*)/i ){
  397. nagios_ack($1,$server,undef);
  398. }
  399. }
  400. ##########################################################################
  401. # Main ###################################################################
  402. ##########################################################################
  403. print "starting...\n";
  404. # clean up fifo on unload
  405. # (called on /script unload)
  406. Irssi::signal_add_first #
  407. 'command script unload', sub { # [2004-08-13]
  408. my ($script) = @_; # get args
  409. return unless $script =~ # only do cleanup when
  410. /(?:^|\s) $IRSSI{name} # unloading *this* script
  411. (?:\.[^. ]*)? (?:\s|$) /x; #
  412. destroy_fifo($FIFO) if -p $FIFO; # destroy old fifo
  413. Irssi::print("%B>>%n $IRSSI{name} $VERSION unloaded", MSGLEVEL_CLIENTCRAP);
  414. }; #
  415. setup(); # initialize setup values
  416. Irssi::signal_add('event privmsg', 'event_privmsg');
  417. Irssi::signal_add("message public", "event_privmsg");
  418. #Irssi::signal_add_first("message public", "on_public");
  419. Irssi::signal_add('setup changed', \&setup); # re-read setup when it changes
  420. print CLIENTCRAP "%B>>%n $IRSSI{name} $VERSION (by $IRSSI{authors}) loaded";
  421. 1;