Code

Fix for regex input of '|', being output causing problems with Nagios' parsing of
[nagiosplug.git] / contrib / check_ica_program_neigbourhood.pl
1 #!/usr/bin/perl -w
3 # $Id: check_ica_program_neigbourhood.pl 1097 2005-01-25 09:05:53Z stanleyhopcroft $
5 # Revision 1.1  2005/01/25 09:05:53  stanleyhopcroft
6 # New plugin to check Citrix Metaframe XP "Program Neighbourhood"
7 #
8 # Revision 1.1  2005-01-25 16:50:30+11  anwsmh
9 # Initial revision
10 #
12 use strict ;
14 use Getopt::Long;
16 use utils qw($TIMEOUT %ERRORS &print_revision &support);
17 use LWP 5.65 ;
18 use XML::Parser ;
20 my $PROGNAME = 'check_program_neigbourhood' ;
21 my ($debug, $xml_debug, $pn_server, $pub_apps, $app_servers, $server_farm, $usage) ;
23 Getopt::Long::Configure('bundling', 'no_ignore_case') ;
24 GetOptions
25         ("V|version"     => \&version,
26         "A|published_app:s"        => \$pub_apps,
27         "h|help"         => \&help,
28         'usage|?'        => \&usage,
29         "F|server_farm=s"  => \$server_farm,
30         "P|pn_server=s"  => \$pn_server,
31         "S|app_server=s" => \$app_servers,
32         "v|verbose"        => \$debug,
33         "x|xml_debug"    => \$xml_debug,
34 ) ;
36 $pn_server              || do  {
37         print "Name or IP Address of _one_ Program Neighbourhood server is required.\n" ;
38         &print_usage ;
39         exit $ERRORS{UNKNOWN} ;
40 } ;
42 $pub_apps               ||= 'Word 2003' ;
43 $pub_apps =~ s/["']//g ;
44 my @pub_apps = split /,\s*/, $pub_apps ;
46 my @app_servers = split /,\s*/, $app_servers ;
48 @app_servers            || do  {
49         print "IP Address of _each_ Application server in the Metaframe Citrix XP server farm is required.\n" ;
50         &print_usage ;
51         exit $ERRORS{UNKNOWN} ;
52 } ;
54 my @non_ip_addresses = grep ! /\d+\.\d+\.\d+\.\d+/, @app_servers ;
56 scalar(@non_ip_addresses) && do { 
57         print qq(Application servers must be specified by IP Address (not name): "@non_ip_addresses".\n) ;
58         &print_usage ;
59         exit $ERRORS{UNKNOWN} ;
60 } ;
62 $server_farm            || do {
63         print "Name of Citrix Metaframe XP server farm is required.\n" ;
64         &print_usage ;
65         exit $ERRORS{UNKNOWN} ;
66 } ;
68 my %xml_tag = () ;
69 my @tag_stack = () ;
71 my $xml_p = new XML::Parser(Handlers => {Start => \&handle_start,
72                                          End   => sub { pop @tag_stack },
73                                          Char  => \&handle_char}) ;
75 # values required by Metaframe XP that don't appear to matter too much
77 my $client_host         = 'Nagios server (http://www.Nagios.ORG)' ;
78 my $user_name           = 'nagios' ;
79 my $domain                      = 'Nagios_Uber_Alles' ;
81 # end values  required by Metaframe XP
83 my $nilpotent_req       = <<'EOR' ;
84 <?xml version="1.0" encoding="ISO-8859-1"?>
85 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd"><NFuseProtocol version="1.1">
86   <RequestProtocolInfo>
87     <ServerAddress addresstype="dns-port" />
88   </RequestProtocolInfo>
89 </NFuseProtocol>
90 EOR
92 my $server_farm_req     = <<'EOR' ;
93 <?xml version="1.0" encoding="ISO-8859-1"?>
94 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
95 <NFuseProtocol version="1.1">
96   <RequestServerFarmData>
97     <Nil />
98   </RequestServerFarmData>
99 </NFuseProtocol>
100 EOR
102 my $spec_server_farm_req = <<EOR ;
103 <?xml version="1.0" encoding="ISO-8859-1"?>
104 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
105 <NFuseProtocol version="1.1">
106   <RequestAddress>
107     <Name>
108       <UnspecifiedName>$server_farm*</UnspecifiedName>
109     </Name>
110     <ClientName>$client_host</ClientName>
111     <ClientAddress addresstype="dns-port" />
112     <ServerAddress addresstype="dns-port" />
113     <Flags />
114     <Credentials>
115       <UserName>$user_name</UserName>
116       <Domain>$domain</Domain>
117     </Credentials>
118   </RequestAddress>
119 </NFuseProtocol>
120 EOR
122 my $app_req             = <<EOR ;
123 <?xml version="1.0" encoding="ISO-8859-1"?>
124 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
125 <NFuseProtocol version="1.1">
126   <RequestAddress>
127     <Name>
128       <UnspecifiedName>PUBLISHED_APP_ENCODED</UnspecifiedName>
129     </Name>
130     <ClientName>Nagios_Service_Check</ClientName>
131     <ClientAddress addresstype="dns-port"/>
132     <ServerAddress addresstype="dns-port" />
133     <Flags />
134     <Credentials>
135       <UserName>$PROGNAME</UserName>
136       <Domain>$domain</Domain>
137     </Credentials>
138   </RequestAddress>
139 </NFuseProtocol>
140 EOR
142 my $ua = LWP::UserAgent->new ;
143 my $req = HTTP::Request->new('POST', "http://$pn_server/scripts/WPnBr.dll") ;
144    $req->content_type('text/xml') ;
146 my $svr ;
148 my @pubapp_encoded = map { my $x = $_ ; $x =~ s/(\W)/'&#' . ord($1) . ';'/eg; $x } @pub_apps ;
150 my $error_tag_cr = sub { ! exists($xml_tag{ErrorId}) } ;
152 my @app_reqs = (
153         # { Content => url,                             Ok => ok_condition,                             Seq => \d+ }
155         { Content => $nilpotent_req,    Ok => $error_tag_cr,                    Seq => 0 }, 
156         { Content => $server_farm_req,  Ok => sub {
157                                                         ! exists($xml_tag{ErrorId})                     &&
158                                                         exists( $xml_tag{ServerFarmName})       &&
159                                                         defined($xml_tag{ServerFarmName})       &&
160                                                         $xml_tag{ServerFarmName} eq  $server_farm
161                                                                         },                                                              Seq => 2 },
162         { Content => $nilpotent_req,    Ok => $error_tag_cr,                    Seq => 4 },
163         { Content => $spec_server_farm_req,     Ok => sub {
164                                                         ! exists($xml_tag{ErrorId})                     &&
165                                                         exists( $xml_tag{ServerAddress})        &&
166                                                         defined($xml_tag{ServerAddress})        &&
167                                                         $xml_tag{ServerAddress} =~ /\d+\.\d+\.\d+\.\d+:\d+/
168                                                                         },                                                              Seq => 6 },
169         { Content => $nilpotent_req,    Ok => $error_tag_cr,                    Seq => 8 },
170         { Content => $app_req,                  Ok => sub {
171                                                         ! exists($xml_tag{ErrorId})                     &&
172                                                         exists( $xml_tag{ServerAddress})        &&
173                                                         defined($xml_tag{ServerAddress})        &&
174                                                         (($svr) = split(/:/, $xml_tag{ServerAddress})) &&
175                                                         defined($svr)                                           &&
176                                                         scalar(grep $_ eq $svr, @app_servers)
177                                                                         },                                                              Seq => 10 }
178 ) ;
180 my $app_location ;
182 foreach my $pub_app (@pub_apps) {
184         my $pubapp_enc = shift @pubapp_encoded ;
185         my $app_req_tmp = $app_reqs[5]{Content} ;
186         $app_reqs[5]{Content} =~ s/PUBLISHED_APP_ENCODED/$pubapp_enc/ ;
188         foreach (@app_reqs) {
190                 $req->content($_->{Content}) ;
192                 $debug                  && print STDERR "App: $pub_app Seq: $_->{Seq}\n", $req->as_string, "\n" ;
194                 my $resp = $ua->request($req) ;
196                 $debug                  && print STDERR "App: $pub_app Seq: ", $_->{Seq} + 1, "\n", $resp->as_string, "\n" ;
198                 $resp->is_error && do {
199                         my $err = $resp->as_string ;
200                         $err =~ s/\n//g ;
201                         &outahere(qq(Failed. HTTP error finding $pub_app at seq $_->{Seq}: "$err")) ;
202         } ;
203                 my $xml = $resp->content ;
205                 my $xml_disp ;
206                    ($xml_disp = $xml) =~ s/\n//g ;
207                    $xml_disp =~ s/ \s+/ /g ;
209                 &outahere($resp->as_string)
210                 unless $xml ;
212                 my ($xml_ok, $whine) = &valid_xml($xml_p, $xml) ;
214                 $xml_ok                 || &outahere(qq(Failed. Bad XML finding $pub_app at eq $_->{Seq} in "$xml_disp".)) ;
216                 &{$_->{Ok}}             || &outahere(qq(Failed. \"\&\$_->{Ok}\" false finding $pub_app at seq $_->{Seq} in "$xml_disp".)) ;
218                                                         # Ugly but alternative is $_->{Ok}->().
219                                                         # eval $_->{Ok} where $_->{Ok} is an
220                                                         # expression returning a bool is possible. but
221                                                         # sub { } prevent recompilation.
223         }
225         $app_reqs[5]{Content} = $app_req_tmp ;
227         $app_location .= qq("$pub_app" => $svr, ) ;
231 substr($app_location, -2, 2) = '' ; 
232 print qq(Ok. Citrix XML service located all published apps $app_location.\n) ;
233 exit $ERRORS{'OK'} ;
235 sub outahere {
236         print "Citrix XML service $_[0]\n" ;
237         exit $ERRORS{CRITICAL} ;
240 sub valid_xml {
241         my ($p, $input) = @_ ;
243         %xml_tag   = () ;
244         @tag_stack = () ;
246         eval {
247         $p->parse($input)
248         } ;
250         return (0, qq(XML::Parser->parse failed: Bad XML in "$input".!))
251                 if $@ ;
253         if ( $xml_debug ) {
254         print STDERR pack('A4 A30 A40', ' ', $_, qq(-> "$xml_tag{$_}")), "\n"
255                 foreach (keys %xml_tag)
256         }
258         return (1, 'valid xml')
263 sub handle_start {
264         push @tag_stack, $_[1] ;
266         $xml_debug              && print STDERR pack('A8 A30 A40', ' ', 'handle_start - tag', " -> '$_[1]'"), "\n" ;
267         $xml_debug              && print STDERR pack('A8 A30 A60', ' ', 'handle_start - @tag_stack', " -> (@tag_stack)"), "\n" ;
270 sub handle_char {
271         my $text = $_[1] ;
273         !($text =~ /\S/  || $text =~ /^[ \t]$/)       && return ;
275         $text =~ s/\n//g ;
277         my $tag = $tag_stack[-1] ;
279         $xml_debug              && print STDERR pack('A8 A30 A30', ' ', 'handle_char - tag', " -> '$tag'"), "\n" ;
280         $xml_debug              && print STDERR pack('A8 A30 A40', ' ', 'handle_char - text', " -> '$text'"), "\n" ;
282         $xml_tag{$tag} .= $text ;
287 sub print_help() {
289 #          1        2         3         4         5         6         7         8
290 #12345678901234567890123456789012345678901234567890123456789012345678901234567890
292         print_revision($PROGNAME,'$Revision: 1097 $ ');
294 my $help = <<EOHELP ;
295 Copyright (c) 2004 Karl DeBisschop/S Hopcroft
297 $PROGNAME -P <pn_server> -S <svr1,svr2,..> -A <app1,app2,..>
298           -F <Farm> [-v -x -h -V]
300 Check the Citrix Metaframe XP service by completing an HTTP dialogue with a Program
301 Neigbourhood server (pn_server) that returns an ICA server in the named Server farm
302 hosting the specified applications (an ICA server in a farm which runs some MS app).
303 EOHELP
305         print $help ;
306         print "\n";
307         print "\n";
308         print_usage();
309         print "\n";
310         support();
313 sub print_usage () {
315 #          1        2         3         4         5         6         7         8
316 #12345678901234567890123456789012345678901234567890123456789012345678901234567890
318 my $usage = <<EOUSAGE ;
319 $PROGNAME
320 [-P | --pn_server] The name or address of the Citrix Metaframe XP
321                    Program Neigbourhood server (required).
322 [-A | --pub_apps] The name or names of an application published by the
323                   server farm (default 'Word 2003').
324 [-F | --server_farm] The name of a Citrix Metaframe XP server farm. (required)
325 [-S | --app_servers] The _IP addresses_ of _all_ of the Farms ICA servers expected
326                      to host the published application.
327                      Enter as a comma separated string eg 'Srv1, Svr2, ..,Srvn'.
328                      Since the PN servers round-robin the app servers to the clients,
329                      _all_ the server farm addresses must be specified or the check
330                      will fail (required).
331 [-v | --verbose]
332 [-h | --help]
333 [-x | --xml_debug]
334 [-V | --version]
335 EOUSAGE
337         print $usage ;
341 sub usage {
342         &print_usage ;
343         exit $ERRORS{'OK'} ;
346 sub version () {
347         print_revision($PROGNAME,'$Revision: 1097 $ ');
348         exit $ERRORS{'OK'};
351 sub help () {
352         print_help();
353         exit $ERRORS{'OK'};
356 =begin comment
358 This is the set of requests and responses transmitted between a Citrix Metaframe XP Program Neigbourhood (PN) client and a PN server.
360 This dialogue was captured by and reconstructed from tcpdump.
362 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, ...
363 do not appear to do anything.
365 req 0
366 POST /scripts/WPnBr.dll HTTP/1.1
367 Content-type: text/xml
368 Host: 10.1.2.2:80
369 Content-Length: 220
370 Connection: Keep-Alive
373 <?xml version="1.0" encoding="ISO-8859-1"?>
374 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
375 <NFuseProtocol version="1.1"><RequestProtocolInfo><ServerAddress addresstype="dns-port" /></RequestProtocolInfo></NFuseProtocol>
377 HTTP/1.1 100 Continue
378 Server: Citrix Web PN Server
379 Date: Thu, 30 Sep 2004 00:12:40 GMT
382 resp 1
383 HTTP/1.1 200 OK
384 Server: Citrix Web PN Server
385 Date: Thu, 30 Sep 2004 00:12:40 GMT
386 Content-type: text/xml
387 Content-length: 253
390 <?xml version="1.0" encoding="ISO-8859-1" ?>
391 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
392 <NFuseProtocol version="1.1">
393     <ResponseProtocolInfo>
394       <ServerAddress addresstype="no-change"></ServerAddress>
395     </ResponseProtocolInfo>
396 </NFuseProtocol>
398 req 2
399 POST /scripts/WPnBr.dll HTTP/1.1
400 Content-type: text/xml
401 Host: 10.1.2.2:80
402 Content-Length: 191
403 Connection: Keep-Alive
406 <?xml version="1.0" encoding="ISO-8859-1"?>
407 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
408 <NFuseProtocol version="1.1"><RequestServerFarmData><Nil /></RequestServerFarmData></NFuseProtocol>
410 HTTP/1.1 100 Continue
411 Server: Citrix Web PN Server
412 Date: Thu, 30 Sep 2004 00:12:40 GMT
415 resp 3
416 HTTP/1.1 200 OK
417 Server: Citrix Web PN Server
418 Date: Thu, 30 Sep 2004 00:12:40 GMT
419 Content-type: text/xml
420 Content-length: 293
423 <?xml version="1.0" encoding="ISO-8859-1" ?>
424 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
425 <NFuseProtocol version="1.1">
426     <ResponseServerFarmData>
427       <ServerFarmData>
428         <ServerFarmName>FOOFARM01</ServerFarmName>
429       </ServerFarmData>
430     </ResponseServerFarmData>
431 </NFuseProtocol>
433 req 4
434 POST /scripts/WPnBr.dll HTTP/1.1
435 Content-type: text/xml
436 Host: 10.1.2.2:80
437 Content-Length: 220
438 Connection: Keep-Alive
441 <?xml version="1.0" encoding="ISO-8859-1"?>
442 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
443 <NFuseProtocol version="1.1"><RequestProtocolInfo><ServerAddress addresstype="dns-port" /></RequestProtocolInfo></NFuseProtocol>
445 HTTP/1.1 100 Continue
446 Server: Citrix Web PN Server
447 Date: Thu, 30 Sep 2004 00:12:55 GMT
450 resp 5
451 HTTP/1.1 200 OK
452 Server: Citrix Web PN Server
453 Date: Thu, 30 Sep 2004 00:12:55 GMT
454 Content-type: text/xml
455 Content-length: 253
458 <?xml version="1.0" encoding="ISO-8859-1" ?>
459 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
460 <NFuseProtocol version="1.1">
461     <ResponseProtocolInfo>
462       <ServerAddress addresstype="no-change"></ServerAddress>
463     </ResponseProtocolInfo>
464 </NFuseProtocol>
466 req 6
467 POST /scripts/WPnBr.dll HTTP/1.1
468 Content-type: text/xml
469 Host: 10.1.2.2:80
470 Content-Length: 442
471 Connection: Keep-Alive
474 <?xml version="1.0" encoding="ISO-8859-1"?>
475 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
476 <NFuseProtocol version="1.1">
477 <RequestAddress><Name>i
478   <UnspecifiedName>FOOFARM01*</UnspecifiedName>
479   </Name><ClientName>WS09535</ClientName>
480   <ClientAddress addresstype="dns-port" />
481   <ServerAddress addresstype="dns-port" />
482   <Flags />
483   <Credentials>
484     <UserName>foo-user</UserName>
485     <Domain>some-domain</Domain>
486   </Credentials>
487 </RequestAddress></NFuseProtocol>
489 HTTP/1.1 100 Continue
490 Server: Citrix Web PN Server
491 Date: Thu, 30 Sep 2004 00:12:56 GMT
494 resp 7
495 HTTP/1.1 200 OK
496 Server: Citrix Web PN Server
497 Date: Thu, 30 Sep 2004 00:12:56 GMT
498 Content-type: text/xml
499 Content-length: 507
502 <?xml version="1.0" encoding="ISO-8859-1" ?>
503 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
504 <NFuseProtocol version="1.1">
505     <ResponseAddress>
506       <ServerAddress addresstype="dot-port">10.1.2.2:1494</ServerAddress>
507       <ServerType>win32</ServerType>
508       <ConnectionType>tcp</ConnectionType>
509       <ClientType>ica30</ClientType>
510       <TicketTag>10.1.2.2</TicketTag>
511       <SSLRelayAddress addresstype="dns-port">ica_svr01.some.domain:443</SSLRelayAddress>
512     </ResponseAddress>
513 </NFuseProtocol>
515 req 8
516 POST /scripts/WPnBr.dll HTTP/1.1
517 Content-type: text/xml
518 Host: 10.1.2.2:80
519 Content-Length: 220
520 Connection: Keep-Alive
523 <?xml version="1.0" encoding="ISO-8859-1"?>
524 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
525 <NFuseProtocol version="1.1"><RequestProtocolInfo><ServerAddress addresstype="dns-port" /></RequestProtocolInfo></NFuseProtocol>
527 HTTP/1.1 100 Continue
528 Server: Citrix Web PN Server
529 Date: Thu, 30 Sep 2004 00:13:29 GMT
532 resp 9
533 HTTP/1.1 200 OK
534 Server: Citrix Web PN Server
535 Date: Thu, 30 Sep 2004 00:13:29 GMT
536 Content-type: text/xml
537 Content-length: 253
540 <?xml version="1.0" encoding="ISO-8859-1" ?>
541 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
542 <NFuseProtocol version="1.1">
543     <ResponseProtocolInfo>
544       <ServerAddress addresstype="no-change"></ServerAddress>
545     </ResponseProtocolInfo>
546 </NFuseProtocol>
548 req 10
549 POST /scripts/WPnBr.dll HTTP/1.1
550 Content-type: text/xml
551 Host: 10.1.2.2:80
552 Content-Length: 446
553 Connection: Keep-Alive
556 <?xml version="1.0" encoding="ISO-8859-1"?>
557 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
558 <NFuseProtocol version="1.1">
559 <RequestAddress>i
560   <Name>
561     <UnspecifiedName>EXCEL#32;2003</UnspecifiedName>
562   </Name>
563   <ClientName>WS09535</ClientName>
564   <ClientAddress addresstype="dns-port" />
565   <ServerAddress addresstype="dns-port" />
566   <Flags />
567     <Credentials><UserName>foo-user</UserName>
568       <Domain>some-domain</Domain>
569     </Credentials>
570 </RequestAddress>i
571 </NFuseProtocol>
573 HTTP/1.1 100 Continue
574 Server: Citrix Web PN Server
575 Date: Thu, 30 Sep 2004 00:13:29 GMT
578 resp 11
579 HTTP/1.1 200 OK
580 Server: Citrix Web PN Server
581 Date: Thu, 30 Sep 2004 00:13:29 GMT
582 Content-type: text/xml
583 Content-length: 509
586 <?xml version="1.0" encoding="ISO-8859-1" ?>
587 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
588 <NFuseProtocol version="1.1">
589     <ResponseAddress>
590       <ServerAddress addresstype="dot-port">10.1.2.14:1494</ServerAddress>
591       <ServerType>win32</ServerType>
592       <ConnectionType>tcp</ConnectionType>
593       <ClientType>ica30</ClientType>
594       <TicketTag>10.1.2.14</TicketTag>
595       <SSLRelayAddress addresstype="dns-port">ica_svr02.some.domain:443</SSLRelayAddress>
596     </ResponseAddress>
597 </NFuseProtocol>
599 ** One sees this XML on an error (there may well be other error XML also, but I haven't seen it) **
601 <?xml version="1.0" encoding="ISO-8859-1" ?>
602 <!DOCTYPE NFuseProtocol SYSTEM "NFuse.dtd">
603 <NFuseProtocol version="1.1">
604     <ResponseAddress>
605       <ErrorId>unspecified</ErrorId>
606       <BrowserError>0x0000000E</BrowserError>
607     </ResponseAddress>
608 </NFuseProtocol>
611 =end comment
613 =cut
616 # You never know when you may be embedded ...