#!/usr/bin/perl -w
#$Revision: 1.3 $
#$Author: trig_monkeypr0n $
# License: GPL
# Copyright (c) 2003 Jason Burnett (code@monkeypr0n.org)
# Nagios is a registered trademark of Ethan Galstad

use strict;
use Tk;
use Tk::Dialog;
use Tk::DialogBox;
use IO::Socket::INET;
use Config::IniFiles;
use Getopt::Long;
use Carp;
use Data::Dumper;
$Data::Dumper::Deepcopy = 1; $Data::Dumper::Indent = 1;
$Data::Dumper::Useqq = 1; $Data::Dumper::Quotekeys = 0;
sub d { warn Data::Dumper->Dump( [$_[0]], [$_[1]] ); }


use vars qw{ 
	@read $host2 $host $port $win_log $cmd %cfg
	$annoy $p $m $u $xmessage $festival $opt_c $osd_loaded
};

Getopt::Long::Configure('bundling');
GetOptions( qw(c=s) );

use Sys::Hostname;
use POSIX qw( strftime WNOHANG );

START:

if( $opt_c ){
	tie %cfg, 'Config::IniFiles', (
		-file => $opt_c,
		-reloadwarn => 1,
		-nocase => 1,
	);
}else{
	tie %cfg, 'Config::IniFiles', (
		-file => "popups.cfg",
		-reloadwarn => 1,
		-nocase => 1,
	);

}	
$host = $cfg{server}{host};
$port = $cfg{server}{port};
$annoy = $cfg{popups}{type};

my $VERSION = (qw($Revision: 1.3 $))[1];

#Define some variables I am using.
my $me = hostname;
my $os = $^O;
my $send_popup = \&{"send_$annoy"};
my $DADDY_PID = $$;

sub CLEANUP {
	warn ( "Caught Interrupt (^C), Aborting\n")
		if $$ == $DADDY_PID;
	exit(1);
}

sub RESTART {
	warn "got HUP'd\n\n";
	#exec( $0, @ARGV ) or croak "HUP: unable to restart: $!\n";
	goto START;
}

sub cancel {
	print "Login cancelled\n";
	exit(0);
}

sub reconnect {
	my $msg = shift;
	$send_popup->($msg);
	#exec( $0, @ARGV ) or croak "reconnect: unable to restart: $!\n";
	goto START;
}

#setup my syslog interface on linux
BEGIN {
    if ( $^O !~ /win/i ){
        require Sys::Syslog;
        import Sys::Syslog;
		eval {
			require X::Osd;
			import X::Osd;
		};
		$osd_loaded = $@ ? 0 : 1;
		eval <<'EOF' unless $osd_loaded;
sub XOSD_bottom {}
sub XOSD_right {}
EOF

#Signal handling
		$SIG{'QUIT'} = \&CLEANUP;
		$SIG{'INT'} = \&CLEANUP;
		$SIG{'HUP'} = \&RESTART;
    }
}

#log_linux if you couldnt guess
sub log_l {
	my $alert = shift;
	syslog ('info', "nagios: %s\n", $alert);
}

#log_windows...duh
sub log_w {
	$win_log = $cfg{windows}{win_log};
	my $alert = shift;
	my $time = strftime "%Y-%m-%d %H:%M:%S ", localtime;
	my @log = ();
	if ( -e $win_log ){
		open( LOG, "$win_log" ) or croak "open: Failed to open $win_log: $!\n";
		@log = <LOG>;
		close LOG;
	}
	open( LOG, ">$win_log" ) or croak "open: Failed to open $win_log: $!\n";
	shift @log if ( @log == 10 );
	push @log, $time.' - '.$alert."\n";
	print LOG join '', @log;
	close LOG;
}

sub send_x {
	my $alert = shift;
	$alert =~ s/\r|\n//g;
	$xmessage = $cfg{linux}{xmessage};
	$cmd = "$xmessage -center -buttons 'O.K.' -default 'O.K.' -timeout 60 -file -";
	my $pid = fork;
	if( !defined( $pid )) {
		die "fork: $!\n";
	}elsif( !$pid ) {
		open( PIPE, "| $cmd " ) or croak "pipe: failed to open: $!\n";
		local $SIG{ 'PIPE' } = sub { alarm 0; exit };
		print PIPE $alert;
		close PIPE;
		log_l( $alert );
		exit;
	}
   	while( waitpid( -1, WNOHANG ) > 0 ) { 
		#nothing 
	}
}

sub send_osd{
	my @osd_disp = ();
	my %osd_pids = ();
	my %osd_dead_pids = ();
	my $alert = shift;
	$alert =~ s/\r|\n//g;
	my $font = '-bitstream-charter-bold-r-normal--*-140-*-*-*-*-*-*';
	my $pid;
   	while(( $pid = waitpid( -1, WNOHANG )) > 0 ) { 
		if( exists $osd_pids{$pid} ) {
			$osd_disp[ $osd_pids{$pid} ] = 0;
			delete $osd_pids{$pid};
		} else {
			$osd_dead_pids{$pid} = 1;
		}
	}
	for $pid ( keys %osd_dead_pids ) {
		if( exists $osd_pids{$pid} ) {
			$osd_disp[ $osd_pids{$pid} ] = 0;
			delete $osd_pids{$pid};
			delete $osd_dead_pids{$pid};
		}
	}
	my $osd_pos = -1;
	if( @osd_disp ) {
		while( ++$osd_pos < @osd_disp ) {
			unless( $osd_disp[ $osd_pos ] ) {
				$osd_disp[ $osd_pos ] = 1;
				last;
			}
		}
	} else {
		$osd_pos = 0;
	}
	my $pad = 10 + $osd_pos * 25;
	$pid = fork;
	if( !defined( $pid )) {
		die "fork: $!\n";
	}elsif( !$pid ) {
		if( $osd_loaded ) {
			my $osd = X::Osd->new(1);
			$osd->set_colour( 'red' );
			$osd->set_font( $font );
			$osd->set_timeout( 12 );
			$osd->set_pos( XOSD_bottom );
			$osd->set_align( XOSD_right );
			$osd->set_shadow_offset( 0 );
			$osd->set_outline_offset( 1 );
			$osd->set_vertical_offset( $pad );
			$osd->string(0, $alert);
			sleep 12;
			exit;
		}
		$cmd = "osd_cat -o $pad -O 1 -d 12 -f $font -p bottom -A right";
		open( PIPE, "| $cmd " ) or croak "pipe: failed to open: $!\n";
		local $SIG{ 'PIPE' } = sub { alarm 0; exit };
		print PIPE $alert;
		close PIPE;
		log_l( $alert );
		exit;
	}
	$osd_pids{$pid} = $osd_pos;
	$osd_disp[ $osd_pos ] = $pid;
   	while(( $pid = waitpid( -1, WNOHANG )) > 0 ) { 
		if( exists $osd_pids{$pid} ) {
			$osd_disp[ $osd_pids{$pid} ] = 0;
			delete $osd_pids{$pid};
		} else {
			$osd_dead_pids{$pid} = 1;
		}
	}
	for $pid ( keys %osd_dead_pids ) {
		if( exists $osd_pids{$pid} ) {
			$osd_disp[ $osd_pids{$pid} ] = 0;
			delete $osd_pids{$pid};
			delete $osd_dead_pids{$pid};
		}
	}
}

sub send_voice{
	my $alert = shift;
	$alert =~ s/\r|\n//g;
	$cmd = "$festival --tts ";
	my $pid = fork;
	if( !defined( $pid )) {
		die "fork: $!\n";
	}elsif( !$pid ) {
		open( PIPE, "| $cmd " ) or croak "pipe: failed to open: $!\n";
		local $SIG{ 'PIPE' } = sub { alarm 0; exit };
		print PIPE $alert;
		close PIPE;
		log_l( $alert );
		exit;
	}
   	while( waitpid( -1, WNOHANG ) > 0 ) { 
		# nothing 
	}
}
	
sub send_net {
	my $alert = shift;
	$alert =~ s/\r|\n//g;
	system("net send $me \"$alert\"");
	log_w( $alert );
}

######DO NOT USE THIS, IT DOES NOT WORK PROPERLY YET#######
sub send_tk {
	my $alert = shift;
	$alert =~ s/\r|\n//g;
	carp "send_tk called: $!\n";
	my $mw = MainWindow->new;
	$mw->title( "Nagios Popups" );
	my $d = $mw->Dialog(
		-text			=> "$alert",
		-bitmap			=> 'warning',
		-default_button	=> 'OK',
	);
	my $a = $d->Show();

}

#Create the popup window for user:pass.
$m = MainWindow->new;
$m->title("Username & Password for nagios-popups");
$m->Label(
	-justify	=>	'left',
	-text 		=>	"Name:")->pack(
		-side	=>	'left',
	);
$u = $m->Entry(
	-selectborderwidth	=> 10,
	-background			=> 'white')->pack(
		-side	=> 'left',
	);

$m->Label(
	-justify	=> 'left',
	-text		=> "Pass:")->pack(
		-side	=> 'left',
	);
$p = $m->Entry(
	-selectborderwidth	=> 10,
	-background			=> 'white',
	-show				=> "#")->pack(
		-side	=> 'left',
	);

$m->Button(
	-text		=> "Cancel",
	-takefocus	=> '1',
	-state		=> "active",
	-default	=> "active",
	-command	=> \&cancel)->pack(
		-side	=> 'right',
		-anchor	=> 'e'
	);
$m->Button(
	-text		=> "Log In",
	-takefocus	=> '1',
	-state		=>	'active',
	-default	=>	'active',
	-command => \&popups)->pack(
		-side => 'top',
		-anchor => 'n'
	);
$p->bind('<Return>',[\&popups]);
$p->bind('<Escape>',[\&cancel]);
$u->focus;
MainLoop;


sub popups {
    my $user = $u->get;
    my $pass = $p->get;
    $m -> destroy;
	$ENV{REMOTE_USER} = $user;

	my $sock = IO::Socket::INET->new(
		PeerAddr	=>	$host,
		PeerPort	=>	$port,
		Proto		=>	'tcp'
	) || croak "failed to connect to $host: $!\n";
	print $sock "$user:$pass\n" || croak("error sending user: $!");
	unless( $sock ) {
		&reconnect( "Auth failure, please try again." );
	}

	while (defined ($_ = $sock->getline)) {
		my $time = strftime "%Y-%m-%d %H:%M:%S ", localtime;
		print $time . "($host)" . ' - '. $_;
		if( $_ ){ 
			$send_popup->($_);
		}
	}
	reconnect( "Connection lost, restarting." );
}


__END__

=head1 NAME

Popups - Client for receiving popups generated by alerts on a Nagios server.

=head1 SYNOPSIS

./popups -c ~/.popups.cfg


-c [path to config file] 

If this option is not specified, the program will look for 
a file called popups.cfg in the current directory.

=head1 NOTES

Right now -c is the only option that the program accepts, there are more command
line options coming. 

Nagios is a registered trademark of Ethan Galstad

=head1 BUGS

*See http://sourceforge.net/projects/nagios-popups/

=head1 AUTHORS

 Jason Burnett -- code at monkeypr0n d0t org
 Casey Zacek -- perl at bogleg d0t org

=head1 GNU GENERAL PUBLIC LICENSE

 See the perlgpl (perldoc perlgpl) for the licensing.
