#!/usr/local/bin/perl -T
#
# downtime_matching.cgi
#
# by Doke Scott, doke at udel dot edu, 2010.12.13
#
# $Header: /opt/home/doke/work/nagios/RCS/downtime_matching.cgi,v 1.3 2011/08/12 09:52:51 doke Exp $
#


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

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 downtime matching" ),
    "<h3>nagios downtime 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 schedules immediate downtime for all nagios hosts and services that match a pattern.
During scheduled downtime, nagios will not send out alerts about this host or service.

<p>
<h4>pattern</h4>

<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>
<h4>start</h4>

<p>
The start field controls when the downtime should start.  The default is
"now".  You can also use any date format that perl Date::Parse accepts,
including:

<pre>
	2pm
	14:00
	07:00:00
	2011-08-11 07:00:00
	20110811 07:00:00
	20110811 070000
        2011:01:24T09:08:17.1823213           ISO-8601
        2011-01-24T09:08:17.1823213
        Wed, 16 Jun 11 07:29:35 CST           Comma and day name are optional
        Thu, 13 Oct 11 10:13:13 -0700
        Wed, 9 Nov 2011 09:50:32 -0500 (EST)  Text in ()'s will be ignored.
        21 dec 17:05                          Will be parsed in the current time zone
        21-dec 17:05
        21/dec 17:05
        21/dec/11 17:05
        2011 10:02:18 "GMT"
        16 Nov 11 22:28:20 PST
</pre>




<p>
<h4>duration</h4>

<p>
The duration field specifies how many hours the downtime should last.  You can use fractions, ie
0.25 means 1/4 hour or 15 minutes.  It defaults to 1 hour.



<p>
<h4>comment</h4>

<p>
The comment field describes why you're scheduling downtime, ie "building power outage".
If you don't say anything it will default to "scheduled downtime".



<p>
<h4>preview</h4>

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




<p>
<h4>really schedule downtime</h4>

<p>
The "really schedule downtime" button actually tells nagios to schedule the downtime.



<p>
<h4>effects</h4>

<p>
After scheduling downtime, it will about 2 minutes for the change to show
in nagios.  It may take longer, expecially if nagios is busy, or you
schedule downtime for a lot of things at once.

};
    }




sub main_form {
    my( $progurl, $pattern, $pattern_re, $comment, $start, $start_esec,
	$duration, $end_esec, $preview, $really );

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

    $pattern = $cgi->param( 'pattern' );
    if ( ! defined $pattern ) {
	$pattern = '';
	}

    $start = $cgi->param( 'start' );
    if ( ! defined $start || ! $start ) {
	$start = 'now';
	}

    $duration = $cgi->param( 'duration' );
    if ( ! defined $duration || $duration <= 0.0 ) {
	$duration = '1.0';
	}

    $comment = $cgi->param( 'comment' );
    $comment ||= 'scheduled downtime';

    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>start: &nbsp; </td><td colspan=2> <input name=start type=text size=24 value="$start"></td></tr>
<tr><td>duration: &nbsp; </td><td colspan=2> <input name=duration type=text size=6 value="$duration"> hours </td></tr>
<tr><td>comment: &nbsp; </td><td colspan=2> <input name=comment type=text size=60 value="$comment"> </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 schedule downtime" type=submit value="really schedule downtime"> </td></tr>
</table>
</form>
};



    $preview = $cgi->param( 'preview' );
    $preview = ( defined $preview && $preview eq 'preview' ) ? 1 : 0;
    $really = $cgi->param( 'really schedule downtime' );
    $really = ( defined $really && $really =~ m/really/i ) ? 1 : 0;

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

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

	if ( ! defined $start || ! $start ) {
	    print "you must enter a start time\n";
	    return;
	    }
	elsif ( $start =~ m/^\s*now\s*$/i ) {
	    $start_esec = time;
	    $start = strftime( "%F %T", localtime( $start_esec ) );
	    }
	elsif ( ( $start_esec = str2time( $start ) ) != 0 ) {
	    $start = strftime( "%F %T", localtime( $start_esec ) );
	    }
	else {
	    print "you must enter a valid start time\n";
	    return;
	    }

	$end_esec = $start_esec + $duration * 3600.0;

	if ( ! defined $duration || $duration < 0 )  {
	    print "you must enter a valid duration\n";
	    return;
	    }

	if ( $really ) {
	    printf "<font color=red><b>really scheduling %0.1f hours of downtime, from %s to %s</b></font>\n<p>\n",
		$duration,
		scalar( localtime( $start_esec ) ),
		scalar( localtime( $end_esec ) );
	    }
	else {
	    print "preview mode, not actually scheduling downtime\n<p>\n";
	    printf "would have scheduled %0.1f hours of downtime, from %s to %s\n<p>\n",
		$duration,
		scalar( localtime( $start_esec ) ),
		scalar( localtime( $end_esec ) );
	    }

	$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 ||= 'scheduled downtime';

	read_statusfile( $pattern_re );
	downtime_hosts( $comment, $really, $start_esec, $end_esec );
	downtime_services( $comment, $really, $start_esec, $end_esec );

	}
    }





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

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

    while ( <fH> ) {
	if ( ! $in_service && ! $in_host ) {
	    if ( m/^service(status)?\s*{/ ) {    # } make vi happy
		$in_service = 1;
		$host = undef;
		$service = undef;
		}
	    elsif ( m/^host(status)?\s*{/ ) {    # } make vi happy
		$in_host = 1;
		$host = undef;
		}
	    }

	else {
	    if ( m/^\s+host_name=(.*)/ ) {
		$host = $1;
		}
	    elsif ( m/^\s+service_description=(.*)/ ) {
		$service = $1;
		}
	    # { make vi happy
	    elsif ( m/^\s*}\s*$/ ) {
		if ( $in_service ) {
		    if ( $host =~ m/$pattern/i || $service =~ m/$pattern/i
				|| "$host/$service" =~ m/$pattern/i ) {
			$verbose && print "found $host/$service\n";
			$services_by_host{ $host }{ $service } = 1;
		    }
		    $in_service = 0;
		    $host = undef;
		    $service = undef;
		    }
		elsif ( $in_host ) {
		    if ( $host =~ m/$pattern/i ) {
			$verbose && print "found $host\n";
			$hosts{ $host } = 1;
			}
		    $in_host = 0;
		    $host = undef;
		    }
		}
	    }

	}
    close fH;
    }



sub downtime_hosts {
    my( $comment, $really, $start_esec, $end_esec ) = @_;
    my( $now, $host, $nagios_cmd );

    print "<p>hosts:<br>\n";

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

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

	#snprintf(command_buffer,sizeof(command_buffer)-1,"[%lu] SCHEDULE_AND_PROPAGATE_TRIGGERED_HOST_DOWNTIME;%s;%lu;%lu;%d;%lu;%lu;%s;%s\n",current_time,host_name,start_time,end_time,(fixed==TRUE)?1:0,triggered_by,duration,comment_author,comment_data);
	$nagios_cmd = sprintf "[%lu] SCHEDULE_AND_PROPAGATE_TRIGGERED_HOST_DOWNTIME;%s;%u;%u;%d;%u;%u;%s;%s\n",
	    $now, $host, $start_esec, $end_esec, 1, 0, $end_esec - $start_esec, $author,
	    $comment;

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




sub downtime_services {
    my( $comment, $really, $start_esec, $end_esec ) = @_;
    my( $now, $host, $service, $nagios_cmd );

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

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

    # append results to nagios command file
    if ( $really ) {
	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";

	    #snprintf(command_buffer,sizeof(command_buffer)-1,"[%lu] SCHEDULE_SVC_DOWNTIME;%s;%s;%lu;%lu;%d;%lu;%lu;%s;%s\n",current_time,host_name,service_desc,start_time,end_time,(fixed==TRUE)?1:0,triggered_by,duration,comment_author,comment_data);
	    $nagios_cmd = sprintf "[%lu] SCHEDULE_SVC_DOWNTIME;%s;%s;%u;%u;%d;%u;%u;%s;%s\n",
		$now, $host, $service, $start_esec, $end_esec, 1, 0, $end_esec - $start_esec,
		$author, $comment;

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


