#!/usr/local/bin/perl -T
#
# rm_matching_comments.cgi
#
# by Doke Scott, doke at udel dot edu, 2010.7.19
#
# $Header: /opt/home/doke/work/nagios/RCS/rm_matching_comments.cgi,v 1.5 2013/12/20 21:42:13 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 $commentsfile $cmdfile $verbose $help $cgi %hostcomments
    %servicecomments %host_states %service_states );

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

$verbose = 0;
$help = 0;

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

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

if ( $help == 1 ) { 
    help();
    }
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 removes nagios host and service comments that match the
host/service pattern, comment pattern, author pattern, time frame, and problem state.

<h4>host/service pattern</h4>
The host/service 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'.  
If you don't care, then specify '*'.

<h4>comment pattern</h4>
The comment pattern is compared with each comment.  If you don't care, then specify '*'.

<h4>author pattern</h4>
The author pattern is compared with each comment's author.  If you don't care, then specify '*'.

<h4>before</h4>
If entered, the comments must have been created before this date and time.  

<h4>problem state</h4>
Normally, this program will not remove comments from a host or service that
is currently in an alarm state.  Checking this box will let it delete
comments even when the item is in a warning or critical state.

<h4>preview</h4>
The "preview" button will show you which comments the
patterns would match, without actually doing anything.  It's a very good
idea to do this first.

<h4>really remove</h4>
The "really remove" button actually tells nagios to actually remove the comments.
It can take several minutes for nagios to get around to actually removing
the comments, especially if you delete at lot at once.

<p>
After clicking this, it may take up to 3 minutes for the
comments to disappear in nagios.  


<h4>combining parameters</h4>
All patterns and conditions must match (logical AND).



<h4>pattern syntax</h4>
<p> 
The patterns use standard unix glob wildcard syntax, except that they ignore 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> anything 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>





};
    }




sub main_form { 
    my( $progurl, $hspat, $hspat_re, $cpat, $cpat_re, $apat, $apat_re, $rm_problem_comments, $before,
	$rm_problem_comments_checked, $preview, $really_rm );

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

    $hspat = $cgi->param( 'hspat' );
    $cpat = $cgi->param( 'cpat' );
    $apat = $cgi->param( 'apat' );
    $before = $cgi->param( 'before' );
    $rm_problem_comments = $cgi->param( 'rm_problem_comments' );

    $rm_problem_comments_checked = ( $rm_problem_comments ) ? 'checked' : '';

    $hspat ||= '*'; 
    $cpat ||= '*'; 
    $apat ||= '*'; 

    print qq{
<p>
<form method=POST>
<table>
<tr><td></td><td width=100></td><td align=right> <a href="$progurl?help=1"><i>help</i></a></td></tr>
<tr><td>host/service pattern: &nbsp; </td><td> <input name=hspat type=text size=40 value="$hspat"></td></tr>
<tr><td>comment pattern: &nbsp; </td><td> <input name=cpat type=text size=40 value="$cpat"></td></tr>
<tr><td>author pattern: &nbsp; </td><td colspan=2> <input name=apat type=text size=32 value="$apat"></td></tr>
<tr><td>before: &nbsp; </td><td colspan=2> <input name=before type=text size=32 value="$before"> &nbsp; <i>only remove comments made before this date and time</i></td></tr>
<tr><td>problem: &nbsp; </td><td colspan=2> <input name=rm_problem_comments type=checkbox value=1 $rm_problem_comments_checked> &nbsp; <i>also remove comments for hosts and services currently in a problem state</i></td></tr>
<tr><td></td><td> <input name=preview type=submit value=preview> </td></tr>
<tr><td></td><td></td><td align=left> <input name="really remove" type=submit value="really remove"> </td></tr>
</table>
</form>
};



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

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

	if ( ! defined $hspat || length( $hspat ) == 0 ) { 
	    print "you must enter a host/service pattern (it can be '*' to match anything)\n";
	    return;
	    }

	if ( ! defined $cpat || length( $cpat ) == 0 ) { 
	    print "you must enter a comment pattern (it can be '*' to match anything)\n";
	    return;
	    }

	if ( ! defined $apat || length( $apat ) == 0 ) { 
	    print "you must enter an author pattern (it can be '*' to match anything)\n";
	    return;
	    }

	if ( ! defined $before || length( $before ) == 0 ) { 
	    $before = time() + 3600;
	    }
	else { 
	    $before = str2time( $before );
	    if ( ! defined $before ) { 
		print "can't parse 'before' time\n";
		return;
		}
	    }
	$verbose && print "before '$before'<br>\n";

	if ( $really_rm ) { 
	    print "<b>real mode:</b> Actually telling nagios to remove the comments.  ",
		"It can take several minutes for nagios to get around to actually removing ",
		"the comments, especially if you delete at lot at once.\n";
	    }
	else { 
	    print "preview mode: Not actually removing\n";
	    }
	print "\n<p>\n";

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

	$cpat_re = glob_to_regex( lc $cpat );
	$cpat_re =~ s/\?-xism/?i-xsm/;
	$verbose && print "cpat_re '$cpat_re'<br>\n";

	$apat_re = glob_to_regex( lc $apat );
	$apat_re =~ s/\?-xism/?i-xsm/;
	$verbose && print "apat_re '$apat_re'<br>\n";

	read_statusfile( $hspat_re, $cpat_re, $apat_re, $before, $rm_problem_comments );
	rm_host_comments( $really_rm, $rm_problem_comments );
	rm_service_comments( $really_rm, $rm_problem_comments );
	}
    }





sub read_statusfile { 
    my( $hspat, $cpat, $apat, $before, $rm_problem_comments ) = @_;
    my( $in_host, $in_service, $in_hostcomment, $in_servicecomment, $host, $service, 
	$current_state, $cid, $comment_data, $entry_time, $author );

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

    $in_host = 0;
    $in_service = 0;
    $in_hostcomment = 0;
    $in_servicecomment = 0;
    $host = undef;
    $cid = 0;
    $comment_data = undef;
    $entry_time = 0;
    $author = undef;

    while ( <fH> ) { 
	if ( m/^hoststatus\s*{/ ) {    # } make vi happy
	    $in_host = 1;
	    $in_service = 0;
	    $in_hostcomment = 0;
	    $in_servicecomment = 0;
	    $host = undef;
	    $current_state = 0;
	    }
	elsif ( m/^servicestatus\s*{/ ) {    # } make vi happy
	    $in_host = 0;
	    $in_service = 1;
	    $in_hostcomment = 0;
	    $in_servicecomment = 0;
	    $host = undef;
	    $service = undef;
	    $current_state = 0;
	    }
	elsif ( m/^hostcomment\s*{/ ) {    # } make vi happy
	    $in_host = 0;
	    $in_service = 0;
	    $in_hostcomment = 1;
	    $in_servicecomment = 0;
	    $host = undef;
	    $cid = 0;
	    $comment_data = undef;
	    $entry_time = 0;
	    $author = undef;
	    }
	elsif ( m/^servicecomment\s*{/ ) {    # } make vi happy
	    $in_host = 0;
	    $in_service = 0;
	    $in_hostcomment = 0;
	    $in_servicecomment = 1;
	    $host = undef;
	    $service = undef;
	    $cid = 0;
	    $comment_data = undef;
	    $entry_time = 0;
	    $author = undef;
	    }
	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*comment_id=(\d+)/ ) { 
	    $cid = $1;
	    }
	elsif ( m/^\s*comment_data=(.*)/ ) { 
	    $comment_data = $1;
	    }
	elsif ( m/^\s*entry_time=(\d+)/ ) { 
	    $entry_time = $1;
	    }
	elsif ( m/^\s*author=(.*)/ ) { 
	    $author = $1;
	    }

	# { make vi happy 
	elsif ( m/^\s*}\s*$/ ) { 
	    if ( $in_host ) {   
		$verbose && print "found host $host\n";
		$host_states{ $host } = $current_state;
		$in_host = 0;
		$host = undef;
		$current_state = 0;
		}
	    
	    elsif ( $in_service ) {   
		$verbose && print "found service $host/$service\n";
		$service_states{ $host }{ $service } = $current_state;
		$in_service = 0;
		$host = undef;
		$service = undef;
		$current_state = 0;
		}

	    elsif ( $in_hostcomment ) {   
		if ( $host =~ m/$hspat/io 
			&& $comment_data =~ m/$cpat/io
			&& $author =~ m/$apat/io
			&& $entry_time <= $before )  { 
		    $verbose && print "found host comment $host $author $comment_data\n";
		    $hostcomments{ $cid }{ 'host' } = $host;
		    $hostcomments{ $cid }{ 'comment_data' } = $comment_data;
		    $hostcomments{ $cid }{ 'entry_time' } = $entry_time;
		    $hostcomments{ $cid }{ 'author' } = $author;
		    }
		$in_hostcomment = 0;
		$host = undef;
		$cid = 0;
		$comment_data = undef;
		$author = undef;
		}

	    elsif ( $in_servicecomment ) {   
		if ( ( $host =~ m/$hspat/io || $service =~ m/$hspat/io
			    || "$host/$service" =~ m/$hspat/io )
			&& $comment_data =~ m/$cpat/io 
			&& $author =~ m/$apat/io 
			&& $entry_time <= $before )  { 
		    $verbose && print "found service comment $host/$service $author $comment_data\n";
		    $servicecomments{ $cid }{ 'host' } = $host;
		    $servicecomments{ $cid }{ 'service' } = $service;
		    $servicecomments{ $cid }{ 'comment_data' } = $comment_data;
		    $servicecomments{ $cid }{ 'entry_time' } = $entry_time;
		    $servicecomments{ $cid }{ 'author' } = $author;
		}
		$in_servicecomment = 0;
		$host = undef;
		$service = undef;
		$cid = 0;
		$comment_data = undef;
		$author = undef;
		}


	    }
	}
    close fH;
    }







sub rm_host_comments { 
    my( $really_rm, $rm_problem_comments ) = @_;
    my( $now, $host, $nagios_cmd, $n, $cid );

    print "<p>\n";
    $verbose && print "removing host comments<br>\n";

    $n = 0;
    foreach $cid ( sort keys %hostcomments ) { 
	next unless ( $cid > 0 );
	next unless ( defined $hostcomments{ $cid }{ 'host' } );
	$host = $hostcomments{ $cid }{ 'host' };
	next unless ( $host_states{ $host } == 0 
	    || $rm_problem_comments );
	$n++;
	}

    print "$n matching host comments<br>\n";
    return if ( $n == 0 );

    # append comment deletion commands to nagios command file
    if ( ! open( cH, ">$cmdfile" ) ) { 
	die "can't write-open cmdfile $cmdfile: $!<br>\n"; 
	}
    $now = time;
    print "<table>\n";
    print "<tr><td><b>host</b></td><td><b>entry time</b></td><td><b>author</b></td><td><b>comment</b></td></tr>\n";
    foreach $cid ( sort sort_hostcomments keys %hostcomments ) { 
	next unless ( $cid > 0 );
	next unless ( defined $hostcomments{ $cid }{ 'host' } );
	$host = $hostcomments{ $cid }{ 'host' };
	next unless ( $host_states{ $host } == 0 
	    || $rm_problem_comments );

	printf "<tr><td>%s &nbsp; </td><td>%s &nbsp; </td><td>%s &nbsp; </td><td>%s</td></tr>\n", 
	    $host, 
	    time2str( $hostcomments{ $cid }{ 'entry_time' } ), 
	    $hostcomments{ $cid }{ 'author' },
	    $hostcomments{ $cid }{ 'comment_data' };

	#[<timestamp>] DEL_HOST_COMMENT;<comment_id>
	$nagios_cmd = sprintf "[%lu] DEL_HOST_COMMENT;%lu\n",
	    $now, $cid;

	$verbose && print "nagios_cmd = $nagios_cmd";
	if ( $really_rm ) { 
	    print cH $nagios_cmd;
	    }
	}
    print "</table>\n";
    close cH;
    }


sub sort_hostcomments { 
    return $hostcomments{$a}{'host'} cmp $hostcomments{$b}{'host'};
    }



sub rm_service_comments { 
    my( $really_rm, $rm_problem_comments ) = @_;
    my( $now, $host, $service, $nagios_cmd, $n, $cid );

    print "<p>\n";
    $verbose && print "removing service comments<br>\n";

    $n = 0;
    foreach $cid ( sort keys %servicecomments ) { 
	next unless ( $cid > 0 ); 
	next unless ( defined $servicecomments{ $cid }{ 'host' } ); 
	next unless ( defined $servicecomments{ $cid }{ 'service' } ); 
	$host = $servicecomments{ $cid }{ 'host' }; 
	$service = $servicecomments{ $cid }{ 'service' }; 
	next unless ( $service_states{ $host }{ $service } == 0 
	    || $rm_problem_comments );
	$n++;
	}

    print "$n matching service comments<br>\n";
    return if ( $n == 0 );

    # append results to nagios command file
    if ( ! open( cH, ">$cmdfile" ) ) { 
	die "can't write-open cmdfile $cmdfile: $!<br>\n"; 
	}
    $now = time;
    print "<table>\n";
    print "<tr><td><b>host/service</b></td></td><td><b>entry time</b></td><td><b>author</b></td><td><b>comment</b></td></tr>\n";
    foreach $cid ( sort sort_servicecomments keys %servicecomments ) { 
	next unless ( $cid > 0 ); 
	next unless ( defined $servicecomments{ $cid }{ 'host' } ); 
	next unless ( defined $servicecomments{ $cid }{ 'service' } ); 
	$host = $servicecomments{ $cid }{ 'host' }; 
	$service = $servicecomments{ $cid }{ 'service' }; 
	next unless ( $service_states{ $host }{ $service } == 0 
	    || $rm_problem_comments );

	printf "<tr><td>%s/%s &nbsp; </td><td>%s &nbsp; </td><td>%s &nbsp; </td><td>%s</td></tr>\n", 
	    $host, $service, 
	    time2str( $servicecomments{ $cid }{ 'entry_time' } ), 
	    $servicecomments{ $cid }{ 'author' },
	    $servicecomments{ $cid }{ 'comment_data' };

	#[<timestamp>] DEL_SVC_COMMENT;<comment_id>
	$nagios_cmd = sprintf "[%lu] DEL_SVC_COMMENT;%lu\n",
	    $now, $cid;

	$verbose && print "nagios_cmd = $nagios_cmd";
	if ( $really_rm ) { 
	    print cH $nagios_cmd;
	    }
	
	}
    print "</table>\n";
    close cH;
    }


sub sort_servicecomments { 
    my( $r );
    $r = $servicecomments{$a}{'host'} cmp $servicecomments{$b}{'host'};
    return $r if ( $r != 0 );
    $r = $servicecomments{$a}{'service'} cmp $servicecomments{$b}{'service'};
    return $r;
    } 


sub time2str { 
    my( $time ) = @_; 
    return strftime( "%Y-%m-%d %H:%M:%S", localtime( $time ) );
    }
