Code

New plugin to check Citrix Metaframe XP "Program Neighbourhood"
[nagiosplug.git] / contrib / check_ica_program_neigbourhood.pl
1 #!/usr/bin/perl -w
3 # $Id$
5 # $Log$
6 # Revision 1.1  2005/01/25 09:05:53  stanleyhopcroft
7 # New plugin to check Citrix Metaframe XP "Program Neighbourhood"
8 #
9 # Revision 1.1  2005-01-25 16:50:30+11  anwsmh
10 # Initial revision
11 #
13 use strict ;
15 use Getopt::Long;
17 use utils qw($TIMEOUT %ERRORS &print_revision &support);
18 use LWP 5.65 ;
19 use XML::Parser ;
21 my $PROGNAME = 'check_program_neigbourhood' ;
22 my ($debug, $xml_debug, $pn_server, $pub_apps, $app_servers, $server_farm, $usage) ;
24 Getopt::Long::Configure('bundling', 'no_ignore_case') ;
25 GetOptions
26         ("V|version"     => \&version,
27         "A|published_app:s"        => \$pub_apps,
28         "h|help"         => \&help,
29         'usage|?'        => \&usage,
30         "F|server_farm=s"  => \$server_farm,
31         "P|pn_server=s"  => \$pn_server,
32         "S|app_server=s" => \$app_servers,
33         "v|verbose"        => \$debug,
34         "x|xml_debug"    => \$xml_debug,
35 ) ;
37 $pn_server              || do  {
38         print "Name or IP Address of _one_ Program Neighbourhood server is required.\n" ;
39         &print_usage ;
40         exit $ERRORS{UNKNOWN} ;
41 } ;
43 $pub_apps               ||= 'Word 2003' ;
44 $pub_apps =~ s/["']//g ;
45 my @pub_apps = split /,\s*/, $pub_apps ;
47 my @app_servers = split /,\s*/, $app_servers ;
49 @app_servers            || do  {
50         print "IP Address of _each_ Application server in the Metaframe Citrix XP server farm is required.\n" ;
51         &print_usage ;
52         exit $ERRORS{UNKNOWN} ;
53 } ;
55 my @non_ip_addresses = grep ! /\d+\.\d+\.\d+\.\d+/, @app_servers ;
57 scalar(@non_ip_addresses) && do { 
58         print qq(Application servers must be specified by IP Address (not name): "@non_ip_addresses".\n) ;
59         &print_usage ;
60         exit $ERRORS{UNKNOWN} ;
61 } ;
63 $server_farm            || do {
64         print "Name of Citrix Metaframe XP server farm is required.\n" ;
65         &print_usage ;
66         exit $ERRORS{UNKNOWN} ;
67 } ;
69 my %xml_tag = () ;
70 my @tag_stack = () ;
72 my $xml_p = new XML::Parser(Handlers => {Start => \&handle_start,
73                                          End   => sub { pop @tag_stack },
74                                          Char  => \&handle_char}) ;
76 # values required by Metaframe XP that don't appear to matter too much
78 my $client_host         = 'Nagios server (http://www.Nagios.ORG)' ;
79 my $user_name           = 'nagios' ;
80 my $domain                      = 'Nagios_Uber_Alles' ;
82 # end values  required by Metaframe XP
84 my $nilpotent_req       = <<'EOR' ;
85 <?xml version="1.0" encoding="ISO-8859-1"?>
86 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd"><NFuseProtocol version="1.1">
87   <RequestProtocolInfo>
88     <ServerAddress addresstype="dns-port" />
89   </RequestProtocolInfo>
90 </NFuseProtocol>
91 EOR
93 my $server_farm_req     = <<'EOR' ;
94 <?xml version="1.0" encoding="ISO-8859-1"?>
95 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
96 <NFuseProtocol version="1.1">
97   <RequestServerFarmData>
98     <Nil />
99   </RequestServerFarmData>
100 </NFuseProtocol>
101 EOR
103 my $spec_server_farm_req = <<EOR ;
104 <?xml version="1.0" encoding="ISO-8859-1"?>
105 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
106 <NFuseProtocol version="1.1">
107   <RequestAddress>
108     <Name>
109       <UnspecifiedName>$server_farm*</UnspecifiedName>
110     </Name>
111     <ClientName>$client_host</ClientName>
112     <ClientAddress addresstype="dns-port" />
113     <ServerAddress addresstype="dns-port" />
114     <Flags />
115     <Credentials>
116       <UserName>$user_name</UserName>
117       <Domain>$domain</Domain>
118     </Credentials>
119   </RequestAddress>
120 </NFuseProtocol>
121 EOR
123 my $app_req             = <<EOR ;
124 <?xml version="1.0" encoding="ISO-8859-1"?>
125 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
126 <NFuseProtocol version="1.1">
127   <RequestAddress>
128     <Name>
129       <UnspecifiedName>PUBLISHED_APP_ENCODED</UnspecifiedName>
130     </Name>
131     <ClientName>Nagios_Service_Check</ClientName>
132     <ClientAddress addresstype="dns-port"/>
133     <ServerAddress addresstype="dns-port" />
134     <Flags />
135     <Credentials>
136       <UserName>$PROGNAME</UserName>
137       <Domain>$domain</Domain>
138     </Credentials>
139   </RequestAddress>
140 </NFuseProtocol>
141 EOR
143 my $ua = LWP::UserAgent->new ;
144 my $req = HTTP::Request->new('POST', "http://$pn_server/scripts/WPnBr.dll") ;
145    $req->content_type('text/xml') ;
147 my $svr ;
149 my @pubapp_encoded = map { my $x = $_ ; $x =~ s/(\W)/'&#' . ord($1) . ';'/eg; $x } @pub_apps ;
151 my $error_tag_cr = sub { ! exists($xml_tag{ErrorId}) } ;
153 my @app_reqs = (
154         # { Content => url,                             Ok => ok_condition,                             Seq => \d+ }
156         { Content => $nilpotent_req,    Ok => $error_tag_cr,                    Seq => 0 }, 
157         { Content => $server_farm_req,  Ok => sub {
158                                                         ! exists($xml_tag{ErrorId})                     &&
159                                                         exists( $xml_tag{ServerFarmName})       &&
160                                                         defined($xml_tag{ServerFarmName})       &&
161                                                         $xml_tag{ServerFarmName} eq  $server_farm
162                                                                         },                                                              Seq => 2 },
163         { Content => $nilpotent_req,    Ok => $error_tag_cr,                    Seq => 4 },
164         { Content => $spec_server_farm_req,     Ok => sub {
165                                                         ! exists($xml_tag{ErrorId})                     &&
166                                                         exists( $xml_tag{ServerAddress})        &&
167                                                         defined($xml_tag{ServerAddress})        &&
168                                                         $xml_tag{ServerAddress} =~ /\d+\.\d+\.\d+\.\d+:\d+/
169                                                                         },                                                              Seq => 6 },
170         { Content => $nilpotent_req,    Ok => $error_tag_cr,                    Seq => 8 },
171         { Content => $app_req,                  Ok => sub {
172                                                         ! exists($xml_tag{ErrorId})                     &&
173                                                         exists( $xml_tag{ServerAddress})        &&
174                                                         defined($xml_tag{ServerAddress})        &&
175                                                         (($svr) = split(/:/, $xml_tag{ServerAddress})) &&
176                                                         defined($svr)                                           &&
177                                                         scalar(grep $_ eq $svr, @app_servers)
178                                                                         },                                                              Seq => 10 }
179 ) ;
181 my $app_location ;
183 foreach my $pub_app (@pub_apps) {
185         my $pubapp_enc = shift @pubapp_encoded ;
186         my $app_req_tmp = $app_reqs[5]{Content} ;
187         $app_reqs[5]{Content} =~ s/PUBLISHED_APP_ENCODED/$pubapp_enc/ ;
189         foreach (@app_reqs) {
191                 $req->content($_->{Content}) ;
193                 $debug                  && print STDERR "App: $pub_app Seq: $_->{Seq}\n", $req->as_string, "\n" ;
195                 my $resp = $ua->request($req) ;
197                 $debug                  && print STDERR "App: $pub_app Seq: ", $_->{Seq} + 1, "\n", $resp->as_string, "\n" ;
199                 $resp->is_error && do {
200                         my $err = $resp->as_string ;
201                         $err =~ s/\n//g ;
202                         &outahere(qq(Failed. HTTP error finding $pub_app at seq $_->{Seq}: "$err")) ;
203         } ;
204                 my $xml = $resp->content ;
206                 my $xml_disp ;
207                    ($xml_disp = $xml) =~ s/\n//g ;
208                    $xml_disp =~ s/ \s+/ /g ;
210                 &outahere($resp->as_string)
211                 unless $xml ;
213                 my ($xml_ok, $whine) = &valid_xml($xml_p, $xml) ;
215                 $xml_ok                 || &outahere(qq(Failed. Bad XML finding $pub_app at eq $_->{Seq} in "$xml_disp".)) ;
217                 &{$_->{Ok}}             || &outahere(qq(Failed. \"\&\$_->{Ok}\" false finding $pub_app at seq $_->{Seq} in "$xml_disp".)) ;
219                                                         # Ugly but alternative is $_->{Ok}->().
220                                                         # eval $_->{Ok} where $_->{Ok} is an
221                                                         # expression returning a bool is possible. but
222                                                         # sub { } prevent recompilation.
224         }
226         $app_reqs[5]{Content} = $app_req_tmp ;
228         $app_location .= qq("$pub_app" => $svr, ) ;
232 substr($app_location, -2, 2) = '' ; 
233 print qq(Ok. Citrix XML service located all published apps $app_location.\n) ;
234 exit $ERRORS{'OK'} ;
236 sub outahere {
237         print "Citrix XML service $_[0]\n" ;
238         exit $ERRORS{CRITICAL} ;
241 sub valid_xml {
242         my ($p, $input) = @_ ;
244         %xml_tag   = () ;
245         @tag_stack = () ;
247         eval {
248         $p->parse($input)
249         } ;
251         return (0, qq(XML::Parser->parse failed: Bad XML in "$input".!))
252                 if $@ ;
254         if ( $xml_debug ) {
255         print STDERR pack('A4 A30 A40', ' ', $_, qq(-> "$xml_tag{$_}")), "\n"
256                 foreach (keys %xml_tag)
257         }
259         return (1, 'valid xml')
264 sub handle_start {
265         push @tag_stack, $_[1] ;
267         $xml_debug              && print STDERR pack('A8 A30 A40', ' ', 'handle_start - tag', " -> '$_[1]'"), "\n" ;
268         $xml_debug              && print STDERR pack('A8 A30 A60', ' ', 'handle_start - @tag_stack', " -> (@tag_stack)"), "\n" ;
271 sub handle_char {
272         my $text = $_[1] ;
274         !($text =~ /\S/  || $text =~ /^[ \t]$/)       && return ;
276         $text =~ s/\n//g ;
278         my $tag = $tag_stack[-1] ;
280         $xml_debug              && print STDERR pack('A8 A30 A30', ' ', 'handle_char - tag', " -> '$tag'"), "\n" ;
281         $xml_debug              && print STDERR pack('A8 A30 A40', ' ', 'handle_char - text', " -> '$text'"), "\n" ;
283         $xml_tag{$tag} .= $text ;
288 sub print_help() {
290 #          1        2         3         4         5         6         7         8
291 #12345678901234567890123456789012345678901234567890123456789012345678901234567890
293         print_revision($PROGNAME,'$Revision$ ');
295 my $help = <<EOHELP ;
296 Copyright (c) 2004 Karl DeBisschop/S Hopcroft
298 $PROGNAME -P <pn_server> -S <svr1,svr2,..> -A <app1,app2,..>
299           -F <Farm> [-v -x -h -V]
301 Check the Citrix Metaframe XP service by completing an HTTP dialogue with a Program
302 Neigbourhood server (pn_server) that returns an ICA server in the named Server farm
303 hosting the specified applications (an ICA server in a farm which runs some MS app).
304 EOHELP
306         print $help ;
307         print "\n";
308         print "\n";
309         print_usage();
310         print "\n";
311         support();
314 sub print_usage () {
316 #          1        2         3         4         5         6         7         8
317 #12345678901234567890123456789012345678901234567890123456789012345678901234567890
319 my $usage = <<EOUSAGE ;
320 $PROGNAME
321 [-P | --pn_server] The name or address of the Citrix Metaframe XP
322                    Program Neigbourhood server (required).
323 [-A | --pub_apps] The name or names of an application published by the
324                   server farm (default 'Word 2003').
325 [-F | --server_farm] The name of a Citrix Metaframe XP server farm. (required)
326 [-S | --app_servers] The _IP addresses_ of _all_ of the Farms ICA servers expected
327                      to host the published application.
328                      Enter as a comma separated string eg 'Srv1, Svr2, ..,Srvn'.
329                      Since the PN servers round-robin the app servers to the clients,
330                      _all_ the server farm addresses must be specified or the check
331                      will fail (required).
332 [-v | --verbose]
333 [-h | --help]
334 [-x | --xml_debug]
335 [-V | --version]
336 EOUSAGE
338         print $usage ;
342 sub usage {
343         &print_usage ;
344         exit $ERRORS{'OK'} ;
347 sub version () {
348         print_revision($PROGNAME,'$Revision$ ');
349         exit $ERRORS{'OK'};
352 sub help () {
353         print_help();
354         exit $ERRORS{'OK'};
357 =begin comment
359 This is the set of requests and responses transmitted between a Citrix Metaframe XP Program Neigbourhood (PN) client and a PN server.
361 This dialogue was captured by and reconstructed from tcpdump.
363 Citrix are not well known for documenting their protocols although the DTD may be informative. Note that the pair(s) 0 and 1, 4 and 5, ...
364 do not appear to do anything.
366 req 0
367 POST /scripts/WPnBr.dll HTTP/1.1
368 Content-type: text/xml
369 Host: 10.1.2.2:80
370 Content-Length: 220
371 Connection: Keep-Alive
374 <?xml version="1.0" encoding="ISO-8859-1"?>
375 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
376 <NFuseProtocol version="1.1"><RequestProtocolInfo><ServerAddress addresstype="dns-port" /></RequestProtocolInfo></NFuseProtocol>
378 HTTP/1.1 100 Continue
379 Server: Citrix Web PN Server
380 Date: Thu, 30 Sep 2004 00:12:40 GMT
383 resp 1
384 HTTP/1.1 200 OK
385 Server: Citrix Web PN Server
386 Date: Thu, 30 Sep 2004 00:12:40 GMT
387 Content-type: text/xml
388 Content-length: 253
391 <?xml version="1.0" encoding="ISO-8859-1" ?>
392 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
393 <NFuseProtocol version="1.1">
394     <ResponseProtocolInfo>
395       <ServerAddress addresstype="no-change"></ServerAddress>
396     </ResponseProtocolInfo>
397 </NFuseProtocol>
399 req 2
400 POST /scripts/WPnBr.dll HTTP/1.1
401 Content-type: text/xml
402 Host: 10.1.2.2:80
403 Content-Length: 191
404 Connection: Keep-Alive
407 <?xml version="1.0" encoding="ISO-8859-1"?>
408 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
409 <NFuseProtocol version="1.1"><RequestServerFarmData><Nil /></RequestServerFarmData></NFuseProtocol>
411 HTTP/1.1 100 Continue
412 Server: Citrix Web PN Server
413 Date: Thu, 30 Sep 2004 00:12:40 GMT
416 resp 3
417 HTTP/1.1 200 OK
418 Server: Citrix Web PN Server
419 Date: Thu, 30 Sep 2004 00:12:40 GMT
420 Content-type: text/xml
421 Content-length: 293
424 <?xml version="1.0" encoding="ISO-8859-1" ?>
425 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
426 <NFuseProtocol version="1.1">
427     <ResponseServerFarmData>
428       <ServerFarmData>
429         <ServerFarmName>FOOFARM01</ServerFarmName>
430       </ServerFarmData>
431     </ResponseServerFarmData>
432 </NFuseProtocol>
434 req 4
435 POST /scripts/WPnBr.dll HTTP/1.1
436 Content-type: text/xml
437 Host: 10.1.2.2:80
438 Content-Length: 220
439 Connection: Keep-Alive
442 <?xml version="1.0" encoding="ISO-8859-1"?>
443 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
444 <NFuseProtocol version="1.1"><RequestProtocolInfo><ServerAddress addresstype="dns-port" /></RequestProtocolInfo></NFuseProtocol>
446 HTTP/1.1 100 Continue
447 Server: Citrix Web PN Server
448 Date: Thu, 30 Sep 2004 00:12:55 GMT
451 resp 5
452 HTTP/1.1 200 OK
453 Server: Citrix Web PN Server
454 Date: Thu, 30 Sep 2004 00:12:55 GMT
455 Content-type: text/xml
456 Content-length: 253
459 <?xml version="1.0" encoding="ISO-8859-1" ?>
460 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
461 <NFuseProtocol version="1.1">
462     <ResponseProtocolInfo>
463       <ServerAddress addresstype="no-change"></ServerAddress>
464     </ResponseProtocolInfo>
465 </NFuseProtocol>
467 req 6
468 POST /scripts/WPnBr.dll HTTP/1.1
469 Content-type: text/xml
470 Host: 10.1.2.2:80
471 Content-Length: 442
472 Connection: Keep-Alive
475 <?xml version="1.0" encoding="ISO-8859-1"?>
476 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
477 <NFuseProtocol version="1.1">
478 <RequestAddress><Name>i
479   <UnspecifiedName>FOOFARM01*</UnspecifiedName>
480   </Name><ClientName>WS09535</ClientName>
481   <ClientAddress addresstype="dns-port" />
482   <ServerAddress addresstype="dns-port" />
483   <Flags />
484   <Credentials>
485     <UserName>foo-user</UserName>
486     <Domain>some-domain</Domain>
487   </Credentials>
488 </RequestAddress></NFuseProtocol>
490 HTTP/1.1 100 Continue
491 Server: Citrix Web PN Server
492 Date: Thu, 30 Sep 2004 00:12:56 GMT
495 resp 7
496 HTTP/1.1 200 OK
497 Server: Citrix Web PN Server
498 Date: Thu, 30 Sep 2004 00:12:56 GMT
499 Content-type: text/xml
500 Content-length: 507
503 <?xml version="1.0" encoding="ISO-8859-1" ?>
504 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
505 <NFuseProtocol version="1.1">
506     <ResponseAddress>
507       <ServerAddress addresstype="dot-port">10.1.2.2:1494</ServerAddress>
508       <ServerType>win32</ServerType>
509       <ConnectionType>tcp</ConnectionType>
510       <ClientType>ica30</ClientType>
511       <TicketTag>10.1.2.2</TicketTag>
512       <SSLRelayAddress addresstype="dns-port">ica_svr01.some.domain:443</SSLRelayAddress>
513     </ResponseAddress>
514 </NFuseProtocol>
516 req 8
517 POST /scripts/WPnBr.dll HTTP/1.1
518 Content-type: text/xml
519 Host: 10.1.2.2:80
520 Content-Length: 220
521 Connection: Keep-Alive
524 <?xml version="1.0" encoding="ISO-8859-1"?>
525 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
526 <NFuseProtocol version="1.1"><RequestProtocolInfo><ServerAddress addresstype="dns-port" /></RequestProtocolInfo></NFuseProtocol>
528 HTTP/1.1 100 Continue
529 Server: Citrix Web PN Server
530 Date: Thu, 30 Sep 2004 00:13:29 GMT
533 resp 9
534 HTTP/1.1 200 OK
535 Server: Citrix Web PN Server
536 Date: Thu, 30 Sep 2004 00:13:29 GMT
537 Content-type: text/xml
538 Content-length: 253
541 <?xml version="1.0" encoding="ISO-8859-1" ?>
542 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
543 <NFuseProtocol version="1.1">
544     <ResponseProtocolInfo>
545       <ServerAddress addresstype="no-change"></ServerAddress>
546     </ResponseProtocolInfo>
547 </NFuseProtocol>
549 req 10
550 POST /scripts/WPnBr.dll HTTP/1.1
551 Content-type: text/xml
552 Host: 10.1.2.2:80
553 Content-Length: 446
554 Connection: Keep-Alive
557 <?xml version="1.0" encoding="ISO-8859-1"?>
558 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
559 <NFuseProtocol version="1.1">
560 <RequestAddress>i
561   <Name>
562     <UnspecifiedName>EXCEL#32;2003</UnspecifiedName>
563   </Name>
564   <ClientName>WS09535</ClientName>
565   <ClientAddress addresstype="dns-port" />
566   <ServerAddress addresstype="dns-port" />
567   <Flags />
568     <Credentials><UserName>foo-user</UserName>
569       <Domain>some-domain</Domain>
570     </Credentials>
571 </RequestAddress>i
572 </NFuseProtocol>
574 HTTP/1.1 100 Continue
575 Server: Citrix Web PN Server
576 Date: Thu, 30 Sep 2004 00:13:29 GMT
579 resp 11
580 HTTP/1.1 200 OK
581 Server: Citrix Web PN Server
582 Date: Thu, 30 Sep 2004 00:13:29 GMT
583 Content-type: text/xml
584 Content-length: 509
587 <?xml version="1.0" encoding="ISO-8859-1" ?>
588 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
589 <NFuseProtocol version="1.1">
590     <ResponseAddress>
591       <ServerAddress addresstype="dot-port">10.1.2.14:1494</ServerAddress>
592       <ServerType>win32</ServerType>
593       <ConnectionType>tcp</ConnectionType>
594       <ClientType>ica30</ClientType>
595       <TicketTag>10.1.2.14</TicketTag>
596       <SSLRelayAddress addresstype="dns-port">ica_svr02.some.domain:443</SSLRelayAddress>
597     </ResponseAddress>
598 </NFuseProtocol>
600 ** One sees this XML on an error (there may well be other error XML also, but I haven't seen it) **
602 <?xml version="1.0" encoding="ISO-8859-1" ?>
603 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
604 <NFuseProtocol version="1.1">
605     <ResponseAddress>
606       <ErrorId>unspecified</ErrorId>
607       <BrowserError>0x0000000E</BrowserError>
608     </ResponseAddress>
609 </NFuseProtocol>
612 =end comment
614 =cut
617 # You never know when you may be embedded ...