#! /usr/bin/perl # nagios: -epn # anders@fupp.net # Alternative HTTP checker for Nagios (tired of check_http) # # Aims: # # - show full URLs and any relevant error message, and text strings checked for # - log request and response, error output etc. to retrievable web page and # send the url along with the # alert, avoiding the need to simulate and # recreate errors # # Warning: # # - this code may not be the most advanced, but does the trick for me # - be careful with multiline searches, script runs slow when combined # with text searches of variable length (.* etc.). I use a fixed length # (for example I do exact match for number of spaces), which runs fast # enough # # 2012-11-23: First version. # 2013-02-04: Add support for SSL, authentication and sending a header. # Add command arguments to error page. # 2013-02-05: Change arguments to be somewhat check_http compatible # 2013-09-12: Actually time out, to give a better timeout message than what # Nagios gives. # 2013-10-29: Add SSL SNI support. # 2014-01-08: Add support for chunked encoding. # 2015-03-10: Add pre-check support, to check if server is supposed to be # healthy. use IO::Socket; use IO::Socket::SSL; use Net::SSLeay; use Getopt::Std; use Time::HiRes qw/ time /; use Text::Glob qw( match_glob ); use Digest::MD5 qw(md5); use MIME::Base64; use List::Util qw[min max]; use LWP::UserAgent; use HTTP::Status qw(is_server_error); # configuration: # where to put error reports: my $datadir = "/usr/local/www/h"; # url to serve them on: my $errorurl = "http://h.xy.no/"; # end, configuration my $ua = LWP::UserAgent->new; $ua->timeout(3); @args = @ARGV; getopts('I:k:u:c:w:r:s:mdt:p:H:a:SxA:P:'); my @CHARS = ('a'..'z', 'A'..'Z', '0'..'9'); $can_client_sni = Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x01000000; if ($opt_x) { $ssl_verify_mode = SSL_VERIFY_PEER; } else { $ssl_verify_mode = SSL_VERIFY_NONE; } sub shorten_url { my $url= shift; my @output; foreach my $int (unpack('N4', md5($url))) { my $out; for (my $j = 0; $j < 6; $j++) { my $val = 0x0000003D & $int; $out .= $CHARS[$val]; $int = $int >> 5; } push @output, $out; } return \@output; } sub usage { print "Usage: check_http_alt [-I ] [-H ] -u \n"; print "[-c ] [-w ] [-r ]\n"; print "[-s ] [-m (multi line)] [-t ] [-p ]\n"; print "[-k ]\n"; print "[-S (ssl)] [-x (check peer SSL certificate)] [-A (alternative user agent)]\n"; print "[-P (return OK if this does not return 200 OK)]\n"; exit(1); } sub timeout { my $ret; my $rettxt; my $timeout = shift; if ($opt_c && ($timeout > $opt_c)) { $ret = 2; $rettxt = ", beyond critical timeout $opt_c"; } elsif ($opt_w && ($timeout > $opt_w)) { $ret = 1; $rettxt = ", beyond warning timeout $opt_w"; } else { $ret = 1; } doexit($ret,"Could not check $urltxt, general timeout after $timeout seconds$rettxt"); } if ($opt_u =~ /^https:\/\//) { $opt_S = 1; $proto = "https"; } elsif ($opt_S) { $proto = "https"; } else { $proto = "http"; } if ($opt_u =~ /^(http|https):\/\//) { # Remove protocol, assume http/https $opt_u =~ s@^${1}:\/\/@@; # Find hostname $hosttmp = $opt_u; $hosttmp =~ s@\/.*@@; if ($hosttmp ne "") { $opt_H = $hosttmp; } else { usage; } $opt_u =~ s@^$opt_H@@; if ($opt_u eq "") { $opt_u = "/"; } } if ((!$opt_I) && $opt_H) { # Specify IP/host either in -I or -H $opt_I = $opt_H; } usage unless ($opt_u); if (!$opt_s) { $opt_s = "200"; } if ($opt_t && $opt_t =~ /^\d+$/) { $timeout = $opt_t; } else { if ($opt_w =~ /^\d+$/ && $opt_c =~ /^\d+$/) { $timeout = max($opt_w, $opt_c)+1; } elsif ($opt_w =~ /^\d+$/) { $timeout = $opt_w+1; } elsif ($opt_c =~ /^\d+$/) { $timeout = $opt_c+1; } else { $timeout = 10; } } if ($opt_P) { my $response = $ua->get($opt_P); my $rc = $response->code; if ($rc ne "200" && (!is_server_error($rc))) { print "Ignoring check of $proto://$opt_H$opt_u on $opt_I, pre-check of $opt_P indicates we should as it does not return 200 OK and is not a server error.\n"; exit(0); } } $gentimeout = $timeout + 1; $SIG{ALRM} = sub { timeout($gentimeout); }; alarm($gentimeout); if ($opt_H) { $hoststr = "Host: $opt_H\r\n"; $sslhost = $opt_H; } else { $hoststr = ""; $sslhost = $opt_I; } if (!($can_client_sni)) { $sslhost = ""; } if (!$opt_p) { if ($opt_S) { $opt_p = 443; } else { $opt_p = 80; } } if ($opt_d) { print "hoststr=$hoststr"; } if ($opt_A) { $useragent = $opt_A; } else { $useragent = "check_http/v1.4.15 (nagios-plugins 1.4.15)"; } sub doexit { my $ret = shift; my $txt = shift; my $shortref; my $short; my $shorturl; my $end; my $timespent; $end = time; $timespent = $end-$start; $txt .= sprintf(". %.3fs, %d bytes.", $timespent, length($data)); if ($ret != 0) { $shortref = shorten_url($opt_I . $opt_H . $opt_u . $start); $short = $$shortref[0]; open(REPORT, ">$datadir/$short"); print REPORT "Request sent at " . localtime($start) . ":\n"; print REPORT "I (check_http_alt) was run with arguments: @args\n\n"; print REPORT $getstr; print REPORT "Response headers:\n$headers\n\n"; print REPORT "Response body:\n$data\n\n"; print REPORT "Plugin response: $txt"; close(REPORT); $txt .= " $errorurl$short"; } print "$txt\n"; exit($ret); } $urltxt = "URL $proto://$opt_H$opt_u on $opt_I"; $start = time; if ($opt_S) { $sock = new IO::Socket::SSL ( PeerAddr => $opt_I, SSL_hostname => $sslhost, SSL_verify_mode => $ssl_verify_mode, Proto => 'tcp', PeerPort => $opt_p, Timeout => $timeout ); doexit(2,"Could not check $urltxt, connection error: $SSL_ERROR $@") unless ($sock); } else { $sock = new IO::Socket::INET ( PeerAddr => $opt_I, Proto => 'tcp', PeerPort => $opt_p, Timeout => $timeout ); doexit(2,"Could not check $urltxt, connection error: $@") unless ($sock); } $getstr = "GET $opt_u HTTP/1.1\r\n" . $hoststr . "User-Agent: $useragent\r\n"; if ($opt_k) { $getstr .= "$opt_k\r\n"; } if ($opt_a) { $getstr .= "Authorization: Basic " . encode_base64($opt_a, "") . "\r\n"; } $getstr .= "Connection: close\r\n\r\n"; print $sock $getstr; $headers = ""; $data = ""; while (<$sock>) { last if (/^\s*$/); $headers .= $_; } if ($headers=~ /^transfer-encoding:[ \t]*chunked\b/im) { while ($chunksize= hex(<$sock>)) { $left = $chunksize; while ($left && ($got= read($sock, $buf, $left))) { $data .= $buf; $left -= $got; } doexit(2,"Could not read chunked response body.") unless defined ($got); $_ = <$sock>; # Clear \r\n } } else { while (<$sock>) { $data .= $_; } } close($sock); $httpcode = (split /\n/, $headers)[0]; $httpcode =~ s@^HTTP/[\d\.]+ (\d+).*@$1@g; if ($opt_c && $timespent > $opt_c) { doexit(2,"Slow $urltxt"); } if ($opt_w && $timespent > $opt_w) { doexit(1,"Slow $urltxt"); } doexit(3, "Unknown status code $urltxt") if ($httpcode eq ""); doexit(2,"Wrong status code $urltxt, got $httpcode expected $opt_s") if ($httpcode !~ /^$opt_s$/); if ($opt_r) { if ($opt_m) { if ($data =~ /$opt_r/s) { doexit(0,"Looked for multi $opt_r in $urltxt, fine"); } else { doexit(2,"Looked for $opt_r in $urltxt as multiline text did not find it"); } } else { if ($data =~ /$opt_r/) { doexit(0,"Looked for $opt_r in $urltxt, fine"); } else { doexit(2,"Looked for $opt_r in $urltxt did not find it"); } } } doexit(0,"Checked $urltxt, fine (response code $httpcode)");