root/lib/Resmon/Status.pm

Revision 8fd3d281c7aab293d6b443f8d3f7e001d0d2b4de, 15.8 kB (checked in by Mark Harrison <mark@omniti.com>, 6 years ago)

Don't crash when the client closes the connection unexpectedly (nessus fix)

git-svn-id: https://labs.omniti.com/resmon/trunk@191 8c0face9-b7db-6ec6-c4b3-d5f7145c7d55

  • Property mode set to 100644
Line 
1 package Resmon::Status;
2
3 use strict;
4 use POSIX qw/:sys_wait_h/;
5 use IO::Handle;
6 use IO::File;
7 use IO::Socket;
8 use Socket;
9 use Fcntl qw/:flock/;
10 use IPC::SysV qw /IPC_PRIVATE IPC_CREAT IPC_RMID ftok S_IRWXU S_IRWXG S_IRWXO/;
11 use Data::Dumper;
12
13 my $SEGSIZE = 1024*256;
14 my $KEEPALIVE_TIMEOUT = 5;
15 my $REQUEST_TIMEOUT = 60;
16 sub new {
17   my $class = shift;
18   my $file = shift;
19   return bless {
20     file => $file
21   }, $class;
22 }
23 sub get_shared_state {
24   my $self = shift;
25   my $blob;
26   my $len;
27   return unless(defined($self->{shared_state}));
28   # Lock shared segment
29   # Read in
30   shmread($self->{shared_state}, $len, 0, length(pack('i', 0)));
31   $len = unpack('i', $len);
32   shmread($self->{shared_state}, $blob, length(pack('i', 0)), $len);
33   # unlock
34   my $VAR1;
35   eval $blob;
36   die $@ if ($@);
37   $self->{store} = $VAR1;
38   return $self->{store};
39 }
40 sub store_shared_state {
41   my $self = shift;
42   return unless(defined($self->{shared_state}));
43   my $blob = Dumper($self->{store});
44
45   # Lock shared segment
46   # Write state and flush
47   shmwrite($self->{shared_state}, pack('i', length($blob)),
48            0, length(pack('i', 0))) || die "$!";
49   shmwrite($self->{shared_state}, $blob, length(pack('i', 0)),
50            length($blob)) || die "$!";
51   # unlock
52 }
53 sub xml_kv_dump {
54   my $info = shift;
55   my $indent = shift || 0;
56   my $rv = '';
57   while(my ($key, $value) = each %$info) {
58     $rv .= " " x $indent;
59     if(ref $value eq 'HASH') {
60       $rv .= "<$key>\n";
61       $rv .= xml_kv_dump($value, $indent + 2);
62       $rv .= " " x $indent;
63       $rv .= "</$key>\n";
64     }
65     else {
66       $value =~ s/&/&amp;/g;
67       $value =~ s/</&lt;/g;
68       $value =~ s/>/&gt;/g;
69       $value =~ s/'/&apos;/g;
70       $rv .= "<$key>$value</$key>\n";
71     }
72   }
73   return $rv;
74 }
75 sub xml_info {
76   my ($module, $service, $info) = @_;
77   my $rv = '';
78   $rv .= "  <ResmonResult module=\"$module\" service=\"$service\">\n";
79   $rv .= xml_kv_dump($info, 4);
80   $rv .= "  </ResmonResult>\n";
81   return $rv;
82 }
83 sub dump_generic {
84   my $self = shift;
85   my $dumper = shift;
86   my $rv = '';
87   while(my ($module, $services) = each %{$self->{store}}) {
88     while(my ($service, $info) = each %$services) {
89       $rv .= $dumper->($module,$service,$info);
90     }
91   }
92   return $rv;
93 }
94 sub dump_generic_module {
95   # Dumps a single module rather than all checks
96   my $self = shift;
97   my $dumper = shift;
98   my $module = shift;
99   my $rv = '';
100   my $services = $self->{store}->{$module};
101   while(my ($service, $info) = each %$services) {
102     $rv .= $dumper->($module,$service,$info);
103   }
104   return $rv;
105 }
106 sub dump_generic_state {
107   # Dumps only checks with a specific state
108   my $self = shift;
109   my $dumper = shift;
110   my $state = shift;
111   my $rv = '';
112   while(my ($module, $services) = each %{$self->{store}}) {
113     while(my ($service, $info) = each %$services) {
114       if ($info->{state} eq $state) {
115         $rv .= $dumper->($module,$service,$info);
116       }
117     }
118   }
119   return $rv;
120 }
121 sub dump_oldstyle {
122   my $self = shift;
123   my $response = $self->dump_generic(sub {
124     my($module,$service,$info) = @_;
125     return "$service($module) :: $info->{state}($info->{message})\n";
126   });
127   return $response;
128 }
129 sub dump_xml {
130   my $self = shift;
131   my $response = <<EOF
132 <?xml version="1.0" encoding="UTF-8"?>
133 <?xml-stylesheet type="text/xsl" href="/resmon.xsl"?>
134 <ResmonResults>
135 EOF
136   ;
137   $response .= $self->dump_generic(\&xml_info);
138   $response .= "</ResmonResults>\n";
139   return $response;
140 }
141 sub get_xsl() {
142   my $response = <<EOF
143 <?xml version="1.0" encoding="ISO-8859-1"?>
144 <xsl:stylesheet version="1.0"
145     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
146 <xsl:template match="ResmonResults">
147 <html>
148 <head>
149     <title>Resmon Results</title>
150     <link rel="stylesheet" type="text/css" href="/resmon.css" />
151 </head>
152 <body>
153     <ul class="navbar">
154         <li><a href="/">List all checks</a></li>
155         <li><a href="/BAD">List all checks that are BAD</a></li>
156         <li><a href="/WARNING">List all checks that are WARNING</a></li>
157         <li><a href="/OK">List all checks that are OK</a></li>
158     </ul>
159     <p>
160     Total checks:
161     <xsl:value-of select="count(ResmonResult)" />
162     </p>
163     <xsl:for-each select="ResmonResult">
164         <xsl:sort select="\@module" />
165         <xsl:sort select="\@service" />
166         <div class="item">
167                 <xsl:attribute name="class">
168                     item <xsl:value-of select="state" />
169                 </xsl:attribute>
170             <ul class="info">
171                 <li>Time taken for last check:
172                     <xsl:value-of select="last_runtime_seconds" /></li>
173                 <li>Last updated:
174                     <xsl:value-of select="last_update" /></li>
175             </ul>
176             <h1>
177                 <a>
178                     <xsl:attribute name="href">
179                         /<xsl:value-of select="\@module" />
180                     </xsl:attribute>
181                     <xsl:value-of select="\@module" />
182                 </a>
183                 -
184                 <a>
185                     <xsl:attribute name="href">
186                         /<xsl:value-of select="\@module"
187                             />/<xsl:value-of select="\@service" />
188                     </xsl:attribute>
189                     <xsl:value-of select="\@service" />
190                 </a>
191             </h1>
192             <h2>
193                 <xsl:value-of select="state"/>:
194                 <xsl:value-of select="message" />
195             </h2>
196             <a class="config" href="#">
197                 Hover to view configuration...
198                 <table>
199                     <tr>
200                         <th>Name</th>
201                         <th>Value</th>
202                     </tr>
203                     <xsl:for-each select="configuration/*">
204                         <xsl:sort select="name(.)" />
205                         <tr>
206                             <td><xsl:value-of select="name(.)" /></td>
207                             <td><xsl:value-of select="." /></td>
208                         </tr>
209                     </xsl:for-each>
210                 </table>
211             </a>
212         </div>
213     </xsl:for-each>
214 </body>
215 </html>
216 </xsl:template>
217 </xsl:stylesheet>
218 EOF
219   ;
220   return $response;
221 }
222 sub get_css() {
223   my $response=<<EOF
224 body {
225     font-family: Verdana, Arial, helvetica, sans-serif;
226 }
227 h1 {
228     margin: 0;
229     font-size: 120%;
230 }
231
232 h2 {
233     margin: 0;
234     font-sizE: 110%;
235 }
236
237 .item {
238     border: 1px solid black;
239     padding: 1em;
240     margin: 2em;
241     background-color: #eeeeee;
242 }
243
244 a.config {
245     color: black;
246 }
247
248 a.config:visited {
249     color: black;
250 }
251
252 a.config table {
253     display: none
254 }
255
256 a.config:hover table {
257     display: block;
258     position: fixed;
259     top: 1em;
260     right: 1em;
261     max-width: 95%;
262     overflow: hidden;
263 }
264
265 .info {
266     float: right;
267     font-size: 80%;
268     padding: 0;
269     margin: 0;
270 }
271
272 .OK {
273     background-color: #afa;
274 }
275
276 .WARNING {
277     background-color: #ffa;
278 }
279
280 .BAD {
281     background-color: #faa;
282 }
283
284 table {
285     border: 1px solid black;
286     background-color: #eeeeee;
287     border-collapse: collapse;
288     margin: 1em;
289     font-size: 80%;
290 }
291
292 th {
293     font-size: 100%;
294     font-weight: bold;
295     background-color: black;
296     color: white;
297 }
298
299 td {
300     padding-left: 1em;
301     padding-right: 1em;
302 }
303
304 a {
305     text-decoration: none;
306 }
307
308 ul.navbar {
309     list-style: none;
310     font-size: 80%;
311 }
312 ul.navbar li {
313     display: inline;
314     padding-left: 1em;
315     padding-right: 1em;
316     margin-right: -1px;
317     border-left: 1px solid black;
318     border-right: 1px solid black;
319 }
320 EOF
321   ;
322   return $response;
323 }
324 sub service {
325   my $self = shift;
326   my ($client, $req, $proto, $snip) = @_;
327   my $state = $self->get_shared_state();
328   if($req eq '/' or $req eq '/status') {
329     my $response .= $self->dump_xml();
330     $client->print(http_header(200, length($response), 'text/xml', $snip));
331     $client->print($response . "\r\n");
332     return;
333   } elsif($req eq '/status.txt') {
334     my $response = $self->dump_oldstyle();
335     $client->print(http_header(200, length($response), 'text/plain', $snip));
336     $client->print($response . "\r\n");
337     return;
338   } elsif($req eq '/resmon.xsl') {
339     my $response = $self->get_xsl();
340     $client->print(http_header(200, length($response), 'text/xml', $snip));
341     $client->print($response . "\r\n");
342     return;
343   } elsif($req eq '/resmon.css') {
344     my $response = $self->get_css();
345     $client->print(http_header(200, length($response), 'text/css', $snip));
346     $client->print($response . "\r\n");
347     return;
348   } elsif($req =~ /^\/([^\/]+)\/(.+)$/) {
349     if(exists($self->{store}->{$1}) &&
350         exists($self->{store}->{$1}->{$2})) {
351     my $info = $self->{store}->{$1}->{$2};
352     my $response = qq^<?xml version="1.0" encoding="UTF-8"?>\n^;
353     my $response .= qq^<?xml-stylesheet type="text/xsl" href="/resmon.xsl"?>^;
354     $response .= "<ResmonResults>\n".
355                     xml_info($1,$2,$info).
356                     "</ResmonResults>\n";
357     $client->print(http_header(200, length($response), 'text/xml', $snip));
358     $client->print( $response . "\r\n");
359     return;
360     }
361   } elsif($req =~ /^\/([^\/]+)$/) {
362     if ($1 eq "BAD" || $1 eq "OK" || $1 eq "WARNING") {
363       my $response = qq^<?xml version="1.0" encoding="UTF-8"?>\n^;
364       my $response .= qq^<?xml-stylesheet type="text/xsl" href="/resmon.xsl"?>^;
365       $response .= "<ResmonResults>\n".
366                       $self->dump_generic_state(\&xml_info,$1) .
367                       "</ResmonResults>\n";
368       $client->print(http_header(200, length($response), 'text/xml', $snip));
369       $client->print( $response . "\r\n");
370       return;
371     } elsif(exists($self->{store}->{$1})) {
372       my $response = qq^<?xml version="1.0" encoding="UTF-8"?>\n^;
373       my $response .= qq^<?xml-stylesheet type="text/xsl" href="/resmon.xsl"?>^;
374       $response .= "<ResmonResults>\n".
375                       $self->dump_generic_module(\&xml_info,$1) .
376                       "</ResmonResults>\n";
377       $client->print(http_header(200, length($response), 'text/xml', $snip));
378       $client->print( $response . "\r\n");
379       return;
380     }
381   }
382   die "Request not understood\n";
383 }
384 sub http_header {
385   my $code = shift;
386   my $len = shift;
387   my $type = shift || 'text/xml';
388   my $close_connection = shift || 1;
389   return qq^HTTP/1.0 $code OK
390 Server: resmon
391 ^ . (defined($len) ? "Content-length: $len\n" : "") .
392     (($close_connection || !$len) ? "Connection: close\n" : "") .
393 qq^Content-Type: $type; charset=utf-8
394
395 ^;
396 }
397 sub serve_http_on {
398   my $self = shift;
399   my $ip = shift;
400   my $port = shift;
401   $ip = INADDR_ANY if(!defined($ip) || $ip eq '' || $ip eq '*');
402   $port ||= 81;
403
404   my $handle = IO::Socket->new();
405   socket($handle, PF_INET, SOCK_STREAM, getprotobyname('tcp'))
406     || die "socket: $!";
407   setsockopt($handle, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
408     || die "setsockopt: $!";
409   bind($handle, sockaddr_in($port, $ip))
410     || die "bind: $!";
411   listen($handle,SOMAXCONN);
412
413   $self->{zindex} = 0;
414   if (-x "/usr/sbin/zoneadm") {
415     open(Z, "/usr/sbin/zoneadm list -p |");
416     my $firstline = <Z>;
417     close(Z);
418     ($self->{zindex}) = split /:/, $firstline, 2;
419   }
420   $self->{http_port} = $port;
421   $self->{http_ip} = $ip;
422   $self->{ftok_number} = $port * (1 + $self->{zindex});
423
424   $self->{child} = fork();
425   if($self->{child} == 0) {
426     eval {
427       $SIG{'HUP'} = 'IGNORE';
428       $SIG{'PIPE'} = 'IGNORE';
429       while(my $client = $handle->accept) {
430         my $req;
431         my $proto;
432         my $close_connection;
433         local $SIG{ALRM} = sub { die "timeout\n" };
434         eval {
435           alarm($KEEPALIVE_TIMEOUT);
436           while(<$client>) {
437             alarm($REQUEST_TIMEOUT);
438             eval {
439               s/\r\n/\n/g;
440               chomp;
441               if(!$req) {
442                 if(/^GET \s*(\S+)\s*?(?: HTTP\/(0\.9|1\.0|1\.1)\s*)?$/) {
443                   $req = $1;
444                   $proto = $2;
445                   # Protocol 1.1 and high are keep-alive by default
446                   $close_connection = ($proto <= 1.0)?1:0;
447                 }
448                 elsif(/./) {
449                   die "protocol deviations.\n";
450                 }
451               }
452               else {
453                 if(/^$/) {
454                   $self->service($client, $req, $proto, $close_connection);
455                   last if ($close_connection);
456                   alarm($KEEPALIVE_TIMEOUT);
457                   $req = undef;
458                   $proto = undef;
459                 }
460                 elsif(/^\S+\s*:\s*.{1,4096}$/) {
461                   # Valid request header... noop
462                   if(/^Connection: (\S+)/) {
463                     if(($proto <= 1.0 && lc($2) eq 'keep-alive') ||
464                        ($proto == 1.1 && lc($2) ne 'close')) {
465                       $close_connection = 0;
466                     }
467                   }
468                 }
469                 else {
470                   die "protocol deviations.\n";
471                 }
472               }
473             };
474             if($@) {
475               print $client http_header(500, 0, 'text/plain', 1);
476               print $client "$@\r\n";
477               last;
478             }
479           }
480           alarm(0);
481         };
482         alarm(0) if($@);
483         $client->close();
484       }
485     };
486     if($@) {
487       print STDERR "Error in listener: $@\n";
488     }
489     exit(0);
490   }
491   close($handle);
492   return;
493 }
494 sub open {
495   my $self = shift;
496   return 0 unless(ref $self);
497   return 1 if($self->{handle});  # Alread open
498   if($self->{file} eq '-' || !defined($self->{file})) {
499     $self->{handle_is_stdout} = 1;
500     $self->{handle} = IO::File->new_from_fd(fileno(STDOUT), "w");
501     return 1;
502   }
503   $self->{handle} = IO::File->new("> $self->{file}.swap");
504   die "open $self->{file}.swap failed: $!\n" unless($self->{handle});
505   $self->{swap_on_close} = 1; # move this to a non .swap version on close
506   chmod 0644, "$self->{file}.swap";
507
508   unless(defined($self->{shared_state})) {
509     $self->{shared_state} = shmget(IPC_PRIVATE, $SEGSIZE,
510                                    IPC_CREAT|S_IRWXU|S_IRWXG|S_IRWXO);
511     die "$0: $!" if($self->{shared_state} == -1);
512   }
513   return 1;
514 }
515 sub store {
516   my ($self, $type, $name, $info) = @_;
517   %{$self->{store}->{$type}->{$name}} = %$info;
518   $self->{store}->{$type}->{$name}->{last_update} = time;
519   $self->store_shared_state();
520   if($self->{handle}) {
521     $self->{handle}->print("$name($type) :: $info->{state}($info->{message})\n");
522   } else {
523     print "$name($type) :: $info->{state}($info->{message})\n";
524   }
525 }
526 sub purge {
527     # This removes status information for modules that are no longer loaded
528
529     # Generate list of current modules
530     my %loaded = ();
531     my ($self, $config) = @_;
532     while (my ($type, $mods) = each(%{$config->{Module}}) ) {
533         $loaded{$type} = ();
534         foreach (@$mods) {
535             $loaded{$type}{$_->{'object'}} = 1;
536         }
537     }
538
539     # Debugging
540     #while (my ($key, $value) = each(%loaded) ) {
541     #    print STDERR "$key: ";
542     #    while (my ($mod, $dummy) = each (%$value) ) {
543     #        print STDERR "$mod ";
544     #    }
545     #    print "\n";
546     #}
547
548     # Compare $self->{store} with list of loaded modules
549     while (my ($type, $value) = each (%{$self->{store}})) {
550         while (my ($name, $value2) = each (%$value)) {
551             if (!exists($loaded{$type}) || !exists($loaded{$type}{$name})) {
552                 #print STDERR "$type $name\n";
553                 delete $self->{store}->{$type}->{$name};
554                 if (scalar(keys %{$self->{store}->{$type}}) == 0) {
555                     #print STDERR "$type has no more objects, deleting\n";
556                     delete $self->{store}->{$type};
557                 }
558             }
559         }
560     }
561 }
562 sub close {
563   my $self = shift;
564   return if($self->{handle_is_stdout});
565   $self->{handle}->close() if($self->{handle});
566   $self->{handle} = undef;
567   if($self->{swap_on_close}) {
568     unlink("$self->{file}");
569     link("$self->{file}.swap", $self->{file});
570     unlink("$self->{file}.swap");
571     delete($self->{swap_on_close});
572   }
573 }
574 sub DESTROY {
575   my $self = shift;
576   my $child = $self->{child};
577   if($child) {
578     kill 15, $child;
579     sleep 1;
580     kill 9, $child if(kill 0, $child);
581     waitpid(-1,WNOHANG);
582   }
583   if(defined($self->{shared_state})) {
584     shmctl($self->{shared_state}, IPC_RMID, 0);
585   }
586 }
587 1;
Note: See TracBrowser for help on using the browser.