#!/usr/local/bin/perl -T
#
# ack_matching.cgi
#
# by Doke Scott, doke at udel dot edu, 2009.9.11
#
# $Header: /opt/home/doke/work/nagios/RCS/ack_matching.cgi,v 1.6 2011/04/25 16:07:50 doke Exp $
#


use strict;
use warnings;
use CGI qw/:standard -nosticky/;
use Text::Glob qw( glob_to_regex );

use vars qw( $statusfile $cmdfile $verbose $help $cgi $author %hosts %services_by_host );

$statusfile = "/usr/local/nagios/var/status.dat";  
$cmdfile = "/usr/local/nagios/var/rw/nagios.cmd";

$verbose = 0;
$help = 0;

$author = $ENV{REMOTE_USER};   # get author's username from http basic auth

$cgi = new CGI;
$help = $cgi->param( 'help' );

print $cgi->header(), 
    $cgi->start_html( -title => "nagios ack matching" ),
    "<h3>nagios ack matching</h3>\n";

if ( $help == 1 ) { 
    help();
    }
elsif ( ! $author ) { 
    print "access denied\n"
    }
else { 
    main_form();
    }

print "<p>\n<hr>\nUDel Network and Systems Services\n",
    $cgi->end_html(); 

exit 0;


###################################


sub help { 
    print qq{ 
<p>
This program acknowledges all nagios host and service problems that match a pattern.

<p>
The pattern is compared with each hostname, each service name, and with "hostname/servicename".
So if switch sw-ne37-110-1 has a ping service, the pattern will compared with 'sw-ne37-110-1', then 'ping', then 'sw-ne37-110-1/ping'.  

<p> 
The pattern uses standard unix glob wildcard syntax, except that it ignores case.  
<table border=1>
<tr><td> ? </td><td> matches any single character </td></tr>
<tr><td> * </td><td> matches any number of any character, including zero characters </td></tr>
<tr><td> [abc] </td><td> matches any of the characters in the brackets </td></tr>
<tr><td> [a-n] </td><td> matches the range of characters in the brackets </td></tr>
</table>

<p>
Examples: 
<table border=1>
<tr><td> ups-* </td><td> any host beginning with 'ups-', ie 'ups-ne37-117-1', 'ups-ne37-117-1/ping', or 'ups-ne37-117-1/liebert'  </td></tr>
<tr><td> *ne37* </td><td> anything with 'ne37' in the hostname or service, ie 'sw-ne37-110-1' or 'sw-ne37-110-1/ping' </td></tr>
<tr><td> *ping </td><td> anything with 'ping' at the end of the hostname or service, ie 'sw-ne37-110-1/ping' </td></tr>
</table>

<!--
<p>
Ephemeral means the comments are temporary.  They'll go away the next time
nagios is reloaded.  Unfortunately, nagios gets reloaded a lot; every time
we add or change what's being monitored.  
-->

<p>
The comment field lets you describe why you're acknowledging the problem, ie "building power down".  
If you don't say anything it will default to "acknowledged". 

<p>
The "preview" button will show you which host and service problems the
pattern would match, without actually doing anything.  It's a very good
idea to do this first.

<p>
The "ack" button actually tells nagios to acknowledge the problems.

<p> 
After acknowledging a problem, it may take up to 2 minutes for the
acknowledgement to show in nagios.  Once it shows as acknowledged in
nagios, it will not show in a preview, and this program will not try to
acknowledge an already acknowledged problem.

};
    }




sub main_form { 
    my( $progurl, $pattern, $pattern_re, $comment, $ephemeral, $ephemeral_checked, 
	$preview, $ack, %hosts, %services_by_host, %services_by_service );

    $progurl = "https://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}";

    $pattern = $cgi->param( 'pattern' );
    $comment = $cgi->param( 'comment' );
    $ephemeral = $cgi->param( 'ephemeral' );

    $ephemeral_checked = ( $ephemeral ) ? 'checked' : '';

    print qq{
<p>
<form method=POST>
<table>
<tr><td></td><td></td><td align=right> <a href="$progurl?help=1"><i>help</i></a> </td></tr>
<tr><td>pattern: &nbsp; </td><td> <input name=pattern type=text size=32 value="$pattern"> </td><td></td></tr>
<tr><td>comment: &nbsp; </td><td colspan=2> <input name=comment type=text size=60 value="$comment"> </td></tr>
<!--
This seems to be confusing people
<tr><td>ephemeral: &nbsp; </td><td> <input name=ephemeral type=checkbox value=1 $ephemeral_checked> </td><td></td></tr>
-->
<tr><td></td><td> <input name=preview type=submit value=preview> </td><td></td></tr>
<tr><td></td><td></td><td align=right> <input name="really ack" type=submit value="really ack"> </td></tr>
</table>
</form>
};



    $preview = $cgi->param( 'preview' );
    $preview = ( defined $preview && $preview eq 'preview' ) ? 1 : 0;
    $ack = $cgi->param( 'really ack' );
    $ack = ( defined $ack && $ack eq 'really ack' ) ? 1 : 0;

    if ( $preview || $ack ) { 
	print "<p>\n<hr>\n<p>\n";

	if ( ! defined $pattern || length( $pattern ) == 0 ) { 
	    print "you must enter a pattern\n";
	    return;
	    }

	if ( $preview ) { 
	    print "preview mode, not actually acknowledging\n<p>\n";
	    }

	$Text::Glob::strict_wildcard_slash = 0;
	$pattern_re = glob_to_regex( lc $pattern );
	$pattern_re =~ s/\?-xism/?i-xsm/;
	$verbose && print "pattern_re '$pattern_re'<br>\n";

	$comment ||= 'acknowledged';

	read_statusfile( $pattern_re );
	ack_hosts( $comment, $preview, $ephemeral );
	ack_services( $comment, $preview, $ephemeral );

	}
    }





sub read_statusfile { 
    my( $pattern ) = @_;
    my( $in_host, $in_service, $host, $service, $current_state, $acked, 
	$state_type );

    open( fH, $statusfile ) || die "can't read-open $statusfile: $!\n";

    while ( <fH> ) { 
	if ( m/^service(status)?\s*{/ ) {    # } make vi happy
	    $in_service = 1;
	    $host = undef;
	    $service = undef;
	    $current_state = 0;
	    $acked = 0;
	    $state_type = 0;
	    }
	elsif ( m/^host(status)?\s*{/ ) {    # } make vi happy
	    $in_host = 1;
	    $host = undef;
	    $current_state = 0;
	    $acked = 0;
	    $state_type = 0;
	    }
	elsif ( m/^\s*host_name=(.*)/ ) { 
	    $host = $1;
	    }
	elsif ( m/^\s*service_description=(.*)/ ) { 
	    $service = $1;
	    }
	elsif ( m/^\s*current_state=(\d+)/ ) { 
	    $current_state = $1;
	    }
	elsif ( m/^\s*problem_has_been_acknowledged=(\d+)/ ) { 
	    $acked = $1;
	    }
	elsif ( m/^\s*state_type=(\d+)/ ) { 
	    $state_type = $1;
	    }
	# { make vi happy 
	elsif ( m/^\s*}\s*$/ ) { 
	    if ( $in_service ) {   
		$verbose && print "host $host, service $service, ", 
		    "current_state $current_state, state_type $state_type, ",
		    "acked $acked\n";
		if ( ( $host =~ m/$pattern/i || $service =~ m/$pattern/i
			    || "$host/$service" =~ m/$pattern/i )
			&& $current_state != 0  # not ok
			&& $state_type == 1	# hard
			&& $acked == 0 ) {	# not acked
		    $verbose && print "found unacked hard problem $host/$service\n";
		    $services_by_host{ $host }{ $service } = 1;
		    #$services_by_service{ $service }{ $host } = 1;
		}
		$in_service = 0;
		$host = undef;
		$service = undef;
		$current_state = 0;
		$acked = 0;
		$state_type = 0;
		}
	    elsif ( $in_host ) {   
		$verbose && print "host $host, ", 
		    "current_state $current_state, state_type $state_type, ",
		    "acked $acked\n";
		if ( $host =~ m/$pattern/i
			&& $current_state != 0  # not ok
			&& $state_type == 1     # hard
			&& $acked == 0 ) {	# not acked
		    $verbose && print "found unacked hard problem $host\n";
		    $hosts{ $host } = 1;
		    }
		$in_host = 0;
		$host = undef;
		$current_state = 0;
		$acked = 0;
		$state_type = 0;
		}
	    }
	}
    close fH;
    }



sub ack_hosts { 
    my( $comment, $preview, $ephemeral ) = @_;
    my( $now, $host, $nagios_cmd );

    print "<p>host problems:<br>\n";

    if ( scalar( keys %hosts ) < 1 ) { 
	print "no hosts to ack<br>\n";
	return;
	}

    # append results to nagios command file
    if ( ! $preview ) { 
	if ( ! open( cH, ">$cmdfile" ) ) { 
	    print "error: can't write-open cmdfile $cmdfile: $!\n"; 
	    return;
	    }
	}
    $now = time;
    foreach $host ( sort keys %hosts ) { 
	print "$host\n";

	#[<timestamp>] ACKNOWLEDGE_HOST_PROBLEM;<host>;<sticky ack>;<send notification>;<persistant_comment>;<author>;<comment>
	$nagios_cmd = sprintf "[%lu] ACKNOWLEDGE_HOST_PROBLEM;%s;%d;%d;%d;%s;%s\n",
	    $now, $host, 1, 0, ( ( $ephemeral ) ? 0 : 1 ), $author, $comment;

	$verbose && print "nagios_cmd = $nagios_cmd";
	if ( ! $preview ) { 
	    print cH $nagios_cmd;
	    }
	}
    close cH;
    }




sub ack_services { 
    my( $comment, $preview, $ephemeral ) = @_;
    my( $now, $host, $service, $nagios_cmd );

    print "<p>service problems:<br>\n";

    if ( scalar( keys %services_by_host ) < 1 ) { 
	print "no services to ack<br>\n";
	return;
	}

    # append results to nagios command file
    if ( ! $preview ) { 
	if ( ! open( cH, ">$cmdfile" ) ) { 
	    print "error: can't write-open cmdfile $cmdfile: $!\n"; 
	    return;
	    }
	}
    $now = time;
    foreach $host ( sort keys %services_by_host ) { 
	foreach $service ( sort keys %{$services_by_host{ $host }} ) { 

	    print "$host/$service<br>\n";

	    #[<timestamp>] ACKNOWLEDGE_SVC_PROBLEM;<host>;<service>;<sticky ack>;<send notification>;<persistant_comment>;<author>;<comment>
	    $nagios_cmd = sprintf "[%lu] ACKNOWLEDGE_SVC_PROBLEM;%s;%s;%d;%d;%d;%s;%s\n",
		$now, $host, $service, 1, 0, ( ( $ephemeral ) ? 0 : 1 ), $author, $comment;

	    $verbose && print "nagios_cmd = $nagios_cmd";
	    if ( ! $preview ) { 
		print cH $nagios_cmd;
		}
	    }
	}
    close cH;
    }


