#!/usr/bin/perl -w
# $Id$
# $Log$
# Revision 1.1  2005/01/25 09:05:53  stanleyhopcroft
# New plugin to check Citrix Metaframe XP "Program Neighbourhood"
#
# Revision 1.1  2005-01-25 16:50:30+11  anwsmh
# Initial revision
#
use strict ;
use Getopt::Long;
use utils qw($TIMEOUT %ERRORS &print_revision &support);
use LWP 5.65 ;
use XML::Parser ;
my $PROGNAME = 'check_program_neigbourhood' ;
my ($debug, $xml_debug, $pn_server, $pub_apps, $app_servers, $server_farm, $usage) ;
Getopt::Long::Configure('bundling', 'no_ignore_case') ;
GetOptions
        ("V|version"     => \&version,
        "A|published_app:s"        => \$pub_apps,
        "h|help"         => \&help,
        'usage|?'        => \&usage,
        "F|server_farm=s"  => \$server_farm,
        "P|pn_server=s"  => \$pn_server,
        "S|app_server=s" => \$app_servers,
        "v|verbose"        => \$debug,
        "x|xml_debug"    => \$xml_debug,
) ;
$pn_server		|| do  {
	print "Name or IP Address of _one_ Program Neighbourhood server is required.\n" ;
	&print_usage ;
	exit $ERRORS{UNKNOWN} ;
} ;
$pub_apps		||= 'Word 2003' ;
$pub_apps =~ s/["']//g ;
my @pub_apps = split /,\s*/, $pub_apps ;
my @app_servers = split /,\s*/, $app_servers ;
@app_servers		|| do  {
	print "IP Address of _each_ Application server in the Metaframe Citrix XP server farm is required.\n" ;
	&print_usage ;
	exit $ERRORS{UNKNOWN} ;
} ;
my @non_ip_addresses = grep ! /\d+\.\d+\.\d+\.\d+/, @app_servers ;
scalar(@non_ip_addresses) && do { 
	print qq(Application servers must be specified by IP Address (not name): "@non_ip_addresses".\n) ;
	&print_usage ;
	exit $ERRORS{UNKNOWN} ;
} ;
$server_farm		|| do {
	print "Name of Citrix Metaframe XP server farm is required.\n" ;
	&print_usage ;
	exit $ERRORS{UNKNOWN} ;
} ;
my %xml_tag = () ;
my @tag_stack = () ;
my $xml_p = new XML::Parser(Handlers => {Start => \&handle_start,
					 End   => sub { pop @tag_stack },
					 Char  => \&handle_char}) ;
# values required by Metaframe XP that don't appear to matter too much
my $client_host		= 'Nagios server (http://www.Nagios.ORG)' ;
my $user_name		= 'nagios' ;
my $domain			= 'Nagios_Uber_Alles' ;
# end values  required by Metaframe XP
my $nilpotent_req	= <<'EOR' ;
  
    
  
EOR
my $server_farm_req	= <<'EOR' ;
  
    
  
EOR
my $spec_server_farm_req = <
  
    
      $server_farm*
    
    $client_host
    
    
    
    
      $user_name
      $domain
    
  
EOR
my $app_req		= <
  
    
      PUBLISHED_APP_ENCODED
    
    Nagios_Service_Check
    
    
    
    
      $PROGNAME
      $domain
    
  
EOR
my $ua = LWP::UserAgent->new ;
my $req = HTTP::Request->new('POST', "http://$pn_server/scripts/WPnBr.dll") ;
   $req->content_type('text/xml') ;
my $svr ;
my @pubapp_encoded = map { my $x = $_ ; $x =~ s/(\W)/'' . ord($1) . ';'/eg; $x } @pub_apps ;
my $error_tag_cr = sub { ! exists($xml_tag{ErrorId}) } ;
my @app_reqs = (
	# { Content => url,				Ok => ok_condition,				Seq => \d+ }
	{ Content => $nilpotent_req,	Ok => $error_tag_cr,			Seq => 0 }, 
	{ Content => $server_farm_req,	Ok => sub {
							! exists($xml_tag{ErrorId})			&&
							exists( $xml_tag{ServerFarmName})	&&
							defined($xml_tag{ServerFarmName})	&&
							$xml_tag{ServerFarmName} eq  $server_farm
									},								Seq => 2 },
	{ Content => $nilpotent_req,	Ok => $error_tag_cr,			Seq => 4 },
	{ Content => $spec_server_farm_req,	Ok => sub {
							! exists($xml_tag{ErrorId})			&&
							exists( $xml_tag{ServerAddress})	&&
							defined($xml_tag{ServerAddress})	&&
							$xml_tag{ServerAddress} =~ /\d+\.\d+\.\d+\.\d+:\d+/
									},								Seq => 6 },
	{ Content => $nilpotent_req,	Ok => $error_tag_cr,			Seq => 8 },
	{ Content => $app_req,			Ok => sub {
							! exists($xml_tag{ErrorId})			&&
							exists( $xml_tag{ServerAddress})	&&
							defined($xml_tag{ServerAddress})	&&
							(($svr) = split(/:/, $xml_tag{ServerAddress})) &&
							defined($svr)						&&
							scalar(grep $_ eq $svr, @app_servers)
									},								Seq => 10 }
) ;
my $app_location ;
foreach my $pub_app (@pub_apps) {
	my $pubapp_enc = shift @pubapp_encoded ;
	my $app_req_tmp = $app_reqs[5]{Content} ;
	$app_reqs[5]{Content} =~ s/PUBLISHED_APP_ENCODED/$pubapp_enc/ ;
	foreach (@app_reqs) {
		$req->content($_->{Content}) ;
		$debug			&& print STDERR "App: $pub_app Seq: $_->{Seq}\n", $req->as_string, "\n" ;
		my $resp = $ua->request($req) ;
		$debug			&& print STDERR "App: $pub_app Seq: ", $_->{Seq} + 1, "\n", $resp->as_string, "\n" ;
		$resp->is_error	&& do {
			my $err = $resp->as_string ;
			$err =~ s/\n//g ;
			&outahere(qq(Failed. HTTP error finding $pub_app at seq $_->{Seq}: "$err")) ;
        } ;
		my $xml = $resp->content ;
		my $xml_disp ;
		   ($xml_disp = $xml) =~ s/\n//g ;
		   $xml_disp =~ s/ \s+/ /g ;
		&outahere($resp->as_string)
		unless $xml ;
		my ($xml_ok, $whine) = &valid_xml($xml_p, $xml) ;
		$xml_ok			|| &outahere(qq(Failed. Bad XML finding $pub_app at eq $_->{Seq} in "$xml_disp".)) ;
		&{$_->{Ok}}		|| &outahere(qq(Failed. \"\&\$_->{Ok}\" false finding $pub_app at seq $_->{Seq} in "$xml_disp".)) ;
							# Ugly but alternative is $_->{Ok}->().
							# eval $_->{Ok} where $_->{Ok} is an
							# expression returning a bool is possible. but
							# sub { } prevent recompilation.
	}
	$app_reqs[5]{Content} = $app_req_tmp ;
	$app_location .= qq("$pub_app" => $svr, ) ;
}
substr($app_location, -2, 2) = '' ; 
print qq(Ok. Citrix XML service located all published apps $app_location.\n) ;
exit $ERRORS{'OK'} ;
sub outahere {
	print "Citrix XML service $_[0]\n" ;
	exit $ERRORS{CRITICAL} ;
}
sub valid_xml {
	my ($p, $input) = @_ ;
	%xml_tag   = () ;
	@tag_stack = () ;
	eval {
	$p->parse($input)
	} ;
	return (0, qq(XML::Parser->parse failed: Bad XML in "$input".!))
		if $@ ;
	if ( $xml_debug ) {
	print STDERR pack('A4 A30 A40', ' ', $_, qq(-> "$xml_tag{$_}")), "\n"
		foreach (keys %xml_tag)
	}
	return (1, 'valid xml')
}
sub handle_start {
	push @tag_stack, $_[1] ;
	$xml_debug		&& print STDERR pack('A8 A30 A40', ' ', 'handle_start - tag', " -> '$_[1]'"), "\n" ;
	$xml_debug 		&& print STDERR pack('A8 A30 A60', ' ', 'handle_start - @tag_stack', " -> (@tag_stack)"), "\n" ;
}
sub handle_char {
	my $text = $_[1] ;
	!($text =~ /\S/  || $text =~ /^[ \t]$/)       && return ;
	$text =~ s/\n//g ;
	my $tag = $tag_stack[-1] ;
	$xml_debug 		&& print STDERR pack('A8 A30 A30', ' ', 'handle_char - tag', " -> '$tag'"), "\n" ;
	$xml_debug 		&& print STDERR pack('A8 A30 A40', ' ', 'handle_char - text', " -> '$text'"), "\n" ;
	$xml_tag{$tag} .= $text ;
}
sub print_help() {
#          1        2         3         4         5         6         7         8
#12345678901234567890123456789012345678901234567890123456789012345678901234567890
	print_revision($PROGNAME,'$Revision$ ');
my $help = < -S  -A 
          -F  [-v -x -h -V]
Check the Citrix Metaframe XP service by completing an HTTP dialogue with a Program
Neigbourhood server (pn_server) that returns an ICA server in the named Server farm
hosting the specified applications (an ICA server in a farm which runs some MS app).
EOHELP
	print $help ;
	print "\n";
	print "\n";
	print_usage();
	print "\n";
	support();
}
sub print_usage () {
#          1        2         3         4         5         6         7         8
#12345678901234567890123456789012345678901234567890123456789012345678901234567890
my $usage = <
HTTP/1.1 100 Continue
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:40 GMT
resp 1
HTTP/1.1 200 OK
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:40 GMT
Content-type: text/xml
Content-length: 253
    
      
    
req 2
POST /scripts/WPnBr.dll HTTP/1.1
Content-type: text/xml
Host: 10.1.2.2:80
Content-Length: 191
Connection: Keep-Alive
HTTP/1.1 100 Continue
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:40 GMT
resp 3
HTTP/1.1 200 OK
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:40 GMT
Content-type: text/xml
Content-length: 293
    
      
        FOOFARM01
      
    
req 4
POST /scripts/WPnBr.dll HTTP/1.1
Content-type: text/xml
Host: 10.1.2.2:80
Content-Length: 220
Connection: Keep-Alive
HTTP/1.1 100 Continue
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:55 GMT
resp 5
HTTP/1.1 200 OK
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:55 GMT
Content-type: text/xml
Content-length: 253
    
      
    
req 6
POST /scripts/WPnBr.dll HTTP/1.1
Content-type: text/xml
Host: 10.1.2.2:80
Content-Length: 442
Connection: Keep-Alive
i
  FOOFARM01*
  WS09535
  
  
  
  
    foo-user
    some-domain
  
HTTP/1.1 100 Continue
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:56 GMT
resp 7
HTTP/1.1 200 OK
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:12:56 GMT
Content-type: text/xml
Content-length: 507
    
      10.1.2.2:1494
      win32
      tcp
      ica30
      10.1.2.2
      ica_svr01.some.domain:443
    
req 8
POST /scripts/WPnBr.dll HTTP/1.1
Content-type: text/xml
Host: 10.1.2.2:80
Content-Length: 220
Connection: Keep-Alive
HTTP/1.1 100 Continue
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:13:29 GMT
resp 9
HTTP/1.1 200 OK
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:13:29 GMT
Content-type: text/xml
Content-length: 253
    
      
    
req 10
POST /scripts/WPnBr.dll HTTP/1.1
Content-type: text/xml
Host: 10.1.2.2:80
Content-Length: 446
Connection: Keep-Alive
i
  
    EXCEL#32;2003
  
  WS09535
  
  
  
    foo-user
      some-domain
    
i
HTTP/1.1 100 Continue
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:13:29 GMT
resp 11
HTTP/1.1 200 OK
Server: Citrix Web PN Server
Date: Thu, 30 Sep 2004 00:13:29 GMT
Content-type: text/xml
Content-length: 509
    
      10.1.2.14:1494
      win32
      tcp
      ica30
      10.1.2.14
      ica_svr02.some.domain:443
    
** One sees this XML on an error (there may well be other error XML also, but I haven't seen it) **
    
      unspecified
      0x0000000E
    
=end comment
=cut
# You never know when you may be embedded ...