diff options
-rw-r--r-- | contrib/check_asterisk.pl | 259 | ||||
-rw-r--r-- | contrib/check_email_loop.pl | 46 | ||||
-rw-r--r-- | contrib/check_http-with-client-certificate.c | 1567 | ||||
-rw-r--r-- | contrib/tarballs/check_traffic-0.91b.tar.gz | bin | 0 -> 12198 bytes |
4 files changed, 1858 insertions, 14 deletions
diff --git a/contrib/check_asterisk.pl b/contrib/check_asterisk.pl new file mode 100644 index 00000000..179d3671 --- /dev/null +++ b/contrib/check_asterisk.pl @@ -0,0 +1,259 @@ +#!/usr/bin/perl -w + +use strict; +use IO::Socket; +use Getopt::Long; +$|=1; + +my ( + $host, $username, $password, $verbose, $help, $command, $mode, + $ipaddr, $respaddr, $sendto, $msg, $recvfrom, + $version, $response, $message, $line, + $sock, $port, $reply, + $warning, $critical, + %warnval, %critval, + %channels, + $runmode, + $key, + $s, +); +my $stop = 0; +my $mgr_port = 5038; +my $iax_port = 4569; +my $exitcode = 0; +my $cause = ""; + +my $iax_answer = 0; +my $iax_maxlen = 1024; +my $iax_timeout = 5; +my $iax_src_call = "8000"; #8000 most siginificant bit is IAX packet type full ... required for a poke etc... +my $iax_dst_call = "0000"; +my $iax_timestamp = "00000000"; +my $iax_outbound_seq = "00"; +my $iax_inbound_seq = "00"; +my $iax_type = "06"; #IAX_Control + +sub ok { + $s = shift; + $s =~ s/[\r\n]//g; + print "OK: $s\n"; + exit(0); +} + +sub warning { + $s = shift; + $s =~ s/[\r\n]//g; + print "WARNING: $s\n"; + exit(1); +} + +sub error { + $s = shift; + $s =~ s/[\r\n]//g; + print "ERROR: $s\n"; + exit(2); +} + +sub unknown { + $s = shift; + $s =~ s/[\r\n]//g; + print "UNKNOWN: $s\n"; + exit(3); +} + +sub syntax { + $s = shift; + unless ($s =~ m/Help:/) { + $s = "Error: (".$s.")" or $s = 'Unknown'; + } + print "$s\n" unless ($help); + print "Syntax: $0 -m mgr -h <host> -u <username> -p <password> [-cwv]\n"; + print "Syntax: $0 -m iax -h <host> [-v]\n"; + print "* --host -h Host\n"; + print "* --mode -m Mode - eithr 'mgr' or 'iax'\n"; + print " --username -u Username\n"; + print " --password -p Password\n"; + print " --port -P n Port (if not using $mgr_port for manager or $iax_port for IAX)\n"; + print " --warning xxx=n Return warning if > n channels of type xxx.\n"; + print " --critical xxx=n Return critical if > n channels of type xxx.\n"; + print " --verbose -v Verbose\n"; + print " --help -h This help\n"; + exit(3); +} + +Getopt::Long::Configure('bundling'); +GetOptions + ("p=s" => \$password, "password=s" => \$password, + "u=s" => \$username, "username=s" => \$username, + "h=s" => \$host, "host=s" => \$host, + "P=i" => \$port, "port=i" => \$port, + "H" => \$help, "help" => \$help, + "v" => \$verbose, "verbose" => \$verbose, + "m=s" => \$mode, "mode=s" => \$mode, + "critical=s" => \$critical, "warning=s" => \$warning); + +syntax("Help:") if ($help); +syntax("Missing host") unless (defined($host)); +syntax("Missing mode") unless (defined($mode)); +if ($mode =~ /^iax$/i) { + print "Running in IAX mode\n" if ($verbose); + $runmode = 1; +} elsif ($mode =~ /^mgr$/i) { + print "Running in Manager mode\n" if ($verbose); + $runmode = 2; +} else { + syntax("Unknown mode $mode") +} + +############################################################################## + +if ($runmode == 2) { + $port = $mgr_port; + syntax("Missing username") unless (defined($username)); + syntax("Missing password") unless (defined($password)); + if (defined($warning)) { + foreach $s (split(/,/, $warning)) { + syntax("Warning value given, $s, is invalid") + unless ($s =~ /^(\w+)=(\d+)$/); + $warnval{$1} = $2; + print "Clear to give WARNING after $2 connections on $1\n" if ($verbose); + } + } + if (defined($critical)) { + foreach $s (split(/,/, $critical)) { + syntax("Critical value given, $s, is invalid") + unless ($s =~ /^(\w+)=(\d+)$/); + $critval{$1} = $2; + print "Clear to give CRITICAL after $2 connections on $1\n" if ($verbose); + } + } + + print "Connecting to $host:$port\n" if ($verbose); + unless ($sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, Proto => 'tcp')) { + print("Could not connect to asterisk server ".$host.":".$port."\n"); + exit(2); + } + print "Connected to $host:$port\n" if ($verbose); + $version = <$sock>; + print $version if ($verbose); + + print $sock "Action: Login\r\nUsername: $username\r\nSecret: $password\r\nEvents: off\r\n\r\n"; + print "Action: Login\r\nUsername: $username\r\nSecret: $password\r\n\r\n" if ($verbose); + $response = <$sock>; + $message = <$sock>; + $s = <$sock>; + print $response.$message if ($verbose); + print $s if ($verbose); + + exit(1) unless ($response =~ m/^Response:\s+(.*)$/i); + exit(1) unless ($1 =~ m/Success/i); + + print $sock "Action: Status\r\n\r\n"; + print "Action: Status\r\n\r\n" if ($verbose); + + $response = <$sock>; + $message = <$sock>; + print $response.$message if ($verbose); + + &unknown("Unknown answer $response (wanted Response: something)") unless ($response =~ m/^Response:\s+(.*)$/i); + &unknown("$response didn't say Success") unless ($1 =~ m/Success/i); + &unknown("Unknown answer $response (wanted Message: something)") unless ($message =~ m/^Message:\s+(.*)$/i); + &unknown("didn't understand message $message") unless ($1 =~ m/Channel status will follow/i); + + $stop=0; + while (($stop == 0) && ($line = <$sock>)) { + print "$line" if ($verbose); + if ($line =~ m/Channel:\s+(\w+)\//) { + $channels{$1}++; + print "Found $1 channel\n" if ($verbose); + } + if ($line =~ m/Event:\s*StatusComplete/i) { + $stop++; + } + } + +# Log out + print $sock "Action: Logoff\r\n\r\n"; + + undef($s); + foreach $key (keys %channels) { + $s .= " " . $key . " (" . $channels{$key} . ")"; + } + + foreach $key (keys %critval) { + print "key = $key\n" if ($verbose); + if (defined($channels{$key}) && ($channels{$key} > $critval{$key})) { + $exitcode = 2; + $cause .= $channels{$key} . " $key channels detected. "; + } + } + + if ($exitcode < 2) { + foreach $key (keys %warnval) { + print "key = $key\n" if ($verbose); + if (defined($channels{$key}) && ($channels{$key} > $warnval{$key})) { + $exitcode = 1; + $cause .= $channels{$key} . " $key channels detected. "; + } + } + } + + if ($exitcode == 0) { + print "OK "; + } elsif ($exitcode == 1) { + print "WARNING "; + } elsif ($exitcode == 2) { + print "CRITICAL "; + } elsif ($exitcode > 2) { + print "UNKNOWN "; + } + if (defined($s)) { + $cause .= " Channels:$s"; + } else { + $cause .= " (idle)"; + } + + print $cause; + + print "\n" if ($verbose); + + exit($exitcode); +} elsif ($runmode == 1) { + $port = $iax_port; + + socket(PING, PF_INET, SOCK_DGRAM, getprotobyname("udp")); + + $msg = pack "H24", $iax_src_call . $iax_dst_call . $iax_timestamp . + $iax_outbound_seq . $iax_inbound_seq . $iax_type . $iax_type; + + $ipaddr = inet_aton($host); + $sendto = sockaddr_in($port,$ipaddr); + + send(PING, $msg, 0, $sendto) == length($msg) or die "cannot send to $host : $port : $!\n"; + + eval { + local $SIG{ALRM} = sub { die("alarm time out"); }; + alarm $iax_timeout; + + while (1) { + $recvfrom = recv(PING, $msg, $iax_maxlen, 0) or die "recv: $!"; + ($port, $ipaddr) = sockaddr_in($recvfrom); + $respaddr = inet_ntoa($ipaddr); + $iax_answer++; + # print "Response from $respaddr : $port\n"; + } + + }; + + if ($iax_answer) { + if ($iax_answer == 1) { + $reply = "reply"; + } else { + $reply = "replies"; + } + &ok("Got $iax_answer $reply"); + } else { + &error("Got no reply"); + } +} + diff --git a/contrib/check_email_loop.pl b/contrib/check_email_loop.pl index 733406da..965f8512 100644 --- a/contrib/check_email_loop.pl +++ b/contrib/check_email_loop.pl @@ -24,6 +24,11 @@ # back till now) or if a mails got lost (meaning a mail, that was # send later came back prior to another mail). # +# Michael Markstaller, mm@elabnet.de various changes/additions +# MM 021003: fixed some unquoted strings +# MM 021116: fixed/added pendwarn/lostwarn +# MM 030515: added deleting of orphaned check-emails +# changed to use "top" instead of get to minimize traffic (required changing match-string from "Subject: Email-ping [" to "Email-Ping [" use Net::POP3; use Net::SMTP; @@ -40,8 +45,8 @@ my %ERRORS = ('UNKNOWN' , '-1', 'CRITICAL', '2'); my $state = "UNKNOWN"; -my ($sender,$receiver, $pophost, $popuser, $poppasswd, $smtphost); -my ($poptimeout,$smtptimeout,$pinginterval)=(60,60,5); +my ($sender,$receiver, $pophost, $popuser, $poppasswd, $smtphost,$keeporphaned); +my ($poptimeout,$smtptimeout,$pinginterval,$maxmsg)=(60,60,5,50); my ($lostwarn, $lostcrit,$pendwarn, $pendcrit); # Internal Vars @@ -73,10 +78,12 @@ my $status = GetOptions( "smtptimeout=i",\$smtptimeout, "statfile=s",\$statfile, "interval=i",\$pinginterval, - "lostwarr=i",\$lostwarn, + "lostwarn=i",\$lostwarn, "lostcrit=i",\$lostcrit, "pendwarn=i",\$pendwarn, "pendcrit=i",\$pendcrit, + "maxmsg=i",\$maxmsg, + "keeporphaned=s",\$keeporphaned, ); usage() if ($status == 0 || ! ($pophost && $popuser && $poppasswd && $smtphost && $receiver && $sender )); @@ -127,10 +134,12 @@ $statinfo="$msgcount mails on POP3"; nsexit("POP3 login failed (user:$popuser)",'CRITICAL') if (!defined($msgcount)); +# Check if more than maxmsg mails in pop3-box +nsexit(">$maxmsg Mails ($msgcount Mails on POP3); Please delete !",'WARNING') if ($msgcount > $maxmsg); + # Count messages, that we are looking 4: while ($msgcount > 0) { - @msglines = @{$pop->get($msgcount)}; - + @msglines = @{$pop->top($msgcount,1)}; for (my $i=0; $i < scalar @messageids; $i++) { if (messagematchsid(\@msglines,$messageids[$i])) { $matchcount++; @@ -138,11 +147,18 @@ while ($msgcount > 0) { $newestid = $messageids[$i] if ($messageids[$i] > $newestid || !defined $newestid); $pop->delete($msgcount); # remove E-Mail from POP3 server splice @messageids, $i, 1;# remove id from List - last; # stop looking in list - } + last; # stop looking in list + } } - - $msgcount--; + # Delete orphaned Email-ping msg + my @msgsubject = grep /^Subject/, @msglines; + chomp @msgsubject; + # Scan Subject if email is an Email-Ping. In fact we match and delete also successfully retrieved messages here again. + if (!defined $keeporphaned && $msgsubject[0] =~ /E-Mail Ping \[/) { + $pop->delete($msgcount); # remove E-Mail from POP3 server + } + + $msgcount--; } $pop->quit(); # necessary for pop3 deletion! @@ -194,7 +210,7 @@ nsexit($statinfo); # ---------------------------------------------------------------------- sub usage { - print "check_email_loop 1.0 Nagios Plugin - Real check of a E-Mail system\n"; + print "check_email_loop 1.1 Nagios Plugin - Real check of a E-Mail system\n"; print "=" x 75,"\nERROR: Missing or wrong arguments!\n","=" x 75,"\n"; print "This script sends a mail with a specific id in the subject via an given\n"; print "smtp-server to a given email-adress. When the script is run again, it checks\n"; @@ -210,19 +226,21 @@ sub usage { print " -smtphost=text IP oder name of the SMTP host\n"; print " -smtptimeout=num Timeout in seconds for the SMTP-server\n"; print " -statfile=text File to save ids of messages ($statfile)\n"; -# print " -interval=num Time (in minutes) that must pass by before sending\n" -# print " another Ping-mail (gibe a new try);\n"; + print " -interval=num Time (in minutes) that must pass by before sending\n"; + print " another Ping-mail (gibe a new try);\n"; print " -lostwarn=num WARNING-state if more than num lost emails\n"; print " -lostcrit=num CRITICAL \n"; print " -pendwarn=num WARNING-state if more than num pending emails\n"; print " -pendcrit=num CRITICAL \n"; + print " -maxmsg=num WARNING if more than num emails on POP3 (default 50)\n"; + print " -keeporphaned Set this to NOT delete orphaned E-Mail Ping msg from POP3\n\n"; print " Options may abbreviated!\n"; print " LOST mails are mails, being sent before the last mail arrived back.\n"; print " PENDING mails are those, which are not. (supposed to be on the way)\n"; print "\nExample: \n"; print " $0 -poph=host -pa=pw -popu=popts -smtph=host -from=root\@me.com\n "; print " -to=remailer\@testxy.com -lostc=0 -pendc=2\n"; - print "\nCopyleft 19.10.2000, Benjamin Schmid\n"; + print "\nCopyleft 19.10.2000, Benjamin Schmid / 2003 Michael Markstaller, mm\@elabnet.de\n"; print "This script comes with ABSOLUTELY NO WARRANTY\n"; print "This programm is licensed under the terms of the "; print "GNU General Public License\n\n"; @@ -247,7 +265,7 @@ sub messagematchsid { # ID $id =~ s/^LI/ID/; # evtl. remove lost mail mark - @tmp = grep /Subject: E-Mail Ping \[/, @$mailref; + @tmp = grep /E-Mail Ping \[/, @$mailref; chomp @tmp; if (($tmp[0] =~ /$id/)) { $match = 1; } diff --git a/contrib/check_http-with-client-certificate.c b/contrib/check_http-with-client-certificate.c new file mode 100644 index 00000000..157d028a --- /dev/null +++ b/contrib/check_http-with-client-certificate.c @@ -0,0 +1,1567 @@ +/**************************************************************************** + * + * Program: HTTP plugin for Nagios + * License: GPL + * + * License Information: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + *****************************************************************************/ + +/**************************************************************************** + * + * check_http is derived from the original check_http provided by + * Ethan Galstad/Karl DeBisschop + * + * This provides some additional functionality including: + * - check server certificate against supplied hostname (Host: header) if any + * - check server certificate against local CA certificates (as browsers do) + * - authenticate with client certificate (and optional passphrase) + * - specify HTTP returncodes to return a status of WARNING or OK instead of + * CRITICAL (only global for 3xx or 4xx errors) + * - check only against HTTP status line and exit immediately if not matched + * + *****************************************************************************/ + +const char *progname = "check_http"; +#define REVISION "$Revision$" +#define CVSREVISION "1.24" +#define COPYRIGHT "2003" +#define AUTHORS "Fabian Pehla" +#define EMAIL "fabian@pehla.de" + +#include "config.h" +#include "common.h" +#include "netutils.h" +#include "utils.h" + + +#define HELP_TXT_SUMMARY "\ +This plugin tests the HTTP service on the specified host. It can test\n\ +normal (http) and secure (https) servers, follow redirects, search for\n\ +strings and regular expressions, check connection times, and report on\n\ +certificate expiration times.\n" + +#define HELP_TXT_OPTIONS "\ +-H <virtual host> -I <ip address> [-p <port>] [-u <uri>]\n\ + [-w <warn time>] [-c <critical time>] [-t <timeout>]\n\ + [-S] [-C <days>] [-a <basic auth>] [-A <certificate file>]\n\ + [-Z <ca certificate file>] [-e <expect>] [-E <expect only>]\n\ + [-s <string>] [-r <regex>] [-R <regex case insensitive>]\n\ + [-f (ok|warn|critical|follow)] [-g (ok|warn|critical)]\n" + +#define HELP_TXT_LONGOPTIONS "\ + -H, --hostname=<virtual host>\n\ + FQDN host name argument for use in HTTP Host:-Header (virtual host)\n\ + If used together wich the -S option, the server certificate will\n\ + be checked against this hostname\n\ + -I, --ip-address=<address>\n\ + IP address or hostname for TCP connect (use IP to avoid DNS lookup)\n\ + -p, --port=<port>\n\ + Port number (default: %d)\n\ + -u, --url-path=<uri>\n\ + URL to request from host (default: %s)\n\ + -S, --ssl\n\ + Use SSL (default port: %d)\n\ + -C, --server-certificate-days=<days>\n\ + Minimum number of days a server certificate must be valid\n\ + No other check can be combined with this option\n\ + -a, --basic-auth=<username:password>\n\ + Colon separated username and password for basic authentication\n\ + -A, --client-certificate=<certificate file>\n\ + File containing X509 client certificate and key\n\ + -K, --passphrase=<passphrase>\n\ + Passphrase for the client certificate key\n\ + This option can only be used in combination with the -A option\n\ + -Z, --ca-certificate=<certificate file>\n\ + File containing certificates of trusted CAs\n\ + The server certificate will be checked against these CAs\n\ + -e, --http-expect=<expect string>\n\ + String to expect in HTTP response line (Default: %s)\n\ + -E, --http-expect-only=<expect only string>\n\ + String to expect in HTTP response line\n\ + No other checks are made, this either matches the response\n\ + or exits immediately\n\ + -s, --content-string=<string>\n\ + String to expect in content\n\ + -r, --content-ereg=<regex>\n\ + Regular expression to expect in content\n\ + -R, --content-eregi=<regex case insensitive>\n\ + Case insensitive regular expression to expect in content\n\ + -f, --onredirect=(ok|warning|critical|follow)\n\ + Follow a redirect (3xx) or return with a user defined state\n\ + Default: OK\n\ + -g, --onerror=(ok|warning|critical)\n\ + Status to return on a client error (4xx)\n\ + -m, --min=INTEGER\n\ + Minimum page size required (bytes)\n\ + -t, --timeout=<timeout>\n\ + Seconds before connection times out (default: %d)\n\ + -c, --critical=<critical time>\n\ + Response time to result in critical status (seconds)\n\ + -w, --warning=<warn time>\n\ + Response time to result in warning status (seconds)\n\ + -V, --version\n\ + Print version information\n\ + -v, --verbose\n\ + Show details for command-line debugging (do not use with nagios server)\n\ + -h, --help\n\ + Print detailed help screen\n" + + + +#define HTTP_PORT 80 +#define DEFAULT_HTTP_URL_PATH "/" +#define DEFAULT_HTTP_EXPECT "HTTP/1." +#define DEFAULT_HTTP_METHOD "GET" +#define DEFAULT_HTTP_REDIRECT_STATE STATE_OK +#define DEFAULT_HTTP_CLIENT_ERROR_STATE STATE_WARNING + +#define HTTP_TEMPLATE_REQUEST "%s%s %s HTTP/1.0\r\n" +#define HTTP_TEMPLATE_HEADER_USERAGENT "%sUser-Agent: %s/%s (nagios-plugins %s)\r\n" +#define HTTP_TEMPLATE_HEADER_HOST "%sHost: %s\r\n" +#define HTTP_TEMPLATE_HEADER_AUTH "%sAuthorization: Basic %s\r\n" + +/* fill in printf with protocol_text(use_ssl), state_text(state), page->status, elapsed_time */ +#define RESULT_TEMPLATE_STATUS_RESPONSE_TIME "%s %s: %s - %7.3f seconds response time|time=%7.3f\n" +#define RESULT_TEMPLATE_RESPONSE_TIME "%s %s: %7.3f seconds response time|time=%7.3f\n" + +#ifdef HAVE_SSL + +#ifdef HAVE_SSL_H +#include <rsa.h> +#include <crypto.h> +#include <x509.h> +#include <pem.h> +#include <ssl.h> +#include <err.h> +#include <rand.h> +#endif + +#ifdef HAVE_OPENSSL_SSL_H +#include <openssl/rsa.h> +#include <openssl/crypto.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#endif + +#define HTTPS_PORT 443 +#endif + +#ifdef HAVE_REGEX_H +#include <regex.h> +#define REGEX_REGS 2 +#define MAX_REGEX_SIZE 256 +#endif + +#define chk_protocol(protocol) ( strstr( protocol, "https" ) ? TRUE : FALSE ); +#define protocol_std_port(use_ssl) ( use_ssl ? HTTPS_PORT : HTTP_PORT ); +#define protocol_text(use_ssl) ( use_ssl ? "HTTPS" : "HTTP" ) + +#define MAX_IPV4_HOSTLENGTH 64 +#define HTTP_HEADER_LOCATION_MATCH "%*[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]: " +#define HTTP_HEADER_PROTOCOL_MATCH "%[HTPShtps]://" +#define HTTP_HEADER_HOSTNAME_MATCH "%[a-zA-Z0-9.-]" +#define HTTP_HEADER_PORT_MATCH ":%[0-9]" +#define HTTP_HEADER_URL_PATH_MATCH "%[/a-zA-Z0-9._-=@,]" + +/* +************************************************************************ +* GLOBAL VARIABLE/POINTER DEFINITIONS * +************************************************************************ +*/ + +/* misc variables */ +int verbose = FALSE; + +/* time thresholds to determine exit code */ +int use_warning_interval = FALSE; +double warning_interval = 0; +int use_critical_interval = FALSE; +double critical_interval = 0; +double elapsed_time = 0; +struct timeval start_tv; + +/* variables concerning the server host */ +int use_server_hostname = FALSE; +char *server_hostname = ""; // hostname for use in HTTPs Host: header +char *server_host = ""; // hostname or ip address for tcp connect +int use_server_port = FALSE; +int server_port = HTTP_PORT; + +int use_basic_auth = FALSE; +char basic_auth[MAX_INPUT_BUFFER] = ""; + +/* variables concerning server responce */ +struct pageref { + char *content; + size_t size; + char *status; + char *header; + char *body; +}; + +/* variables concerning ssl connections */ +int use_ssl = FALSE; +#ifdef HAVE_SSL +int server_certificate_min_days_valid = 0; +int check_server_certificate = FALSE; +X509 *server_certificate; // structure containing server certificate +int use_client_certificate = FALSE; +char *client_certificate_file = NULL; +int use_client_certificate_passphrase = FALSE; +char *client_certificate_passphrase = NULL; +int use_ca_certificate = FALSE; +char *ca_certificate_file = NULL; + +BIO *bio_err = 0; // error write context +#endif + + +/* variables concerning check behaviour */ +char *http_method = DEFAULT_HTTP_METHOD; +char *http_url_path = ""; +int use_http_post_data = FALSE; +char *http_post_data = ""; +int use_min_content_length = FALSE; +int min_content_length = 0; +int use_http_expect_only = FALSE; +char http_expect[MAX_INPUT_BUFFER] = DEFAULT_HTTP_EXPECT; +int check_content_string = FALSE; +char content_string[MAX_INPUT_BUFFER] = ""; +int http_redirect_state = DEFAULT_HTTP_REDIRECT_STATE; +int http_client_error_state = DEFAULT_HTTP_CLIENT_ERROR_STATE; + +#ifdef HAVE_REGEX_H +regex_t regex_preg; +regmatch_t regex_pmatch[REGEX_REGS]; +int check_content_regex = FALSE; +char content_regex[MAX_REGEX_SIZE] = ""; +int regex_cflags = REG_NOSUB | REG_EXTENDED | REG_NEWLINE; +int regex_error = 0; +char regex_error_buffer[MAX_INPUT_BUFFER] = ""; +#endif + + + +/* +************************************************************************ +* FUNCTION PROTOTYPES * +************************************************************************ +*/ + +void print_usage( void ); +void print_help( void ); +int process_arguments (int, char **); +int http_request( int sock, struct pageref *page); + +int parse_http_response( struct pageref *page ); +int check_http_response( struct pageref *page ); +int check_http_content( struct pageref *page ); +int prepare_follow_redirect( struct pageref *page ); + +static char *base64 (char *bin, int len); + +#ifdef HAVE_SSL +int ssl_terminate( int state, char *string ); +static int passwd_cb( char *buf, int num, int rwflag, void *userdata ); +static void sigpipe_handle( int x ); +SSL_CTX * initialize_ssl_ctx( void ); +void destroy_ssl_ctx( SSL_CTX *ctx ); +int fetch_server_certificate( SSL *ssl ); +int check_server_certificate_chain( SSL *ssl ); +int check_server_certificate_hostname( void ); +int check_server_certificate_expires( void ); +int https_request( SSL_CTX *ctx, SSL *ssl, struct pageref *page ); +#endif + +/* +************************************************************************ +* IMPLEMENTATION * +************************************************************************ +*/ + +/* + * main() + * + * PSEUDOCODE OF HOW MAIN IS SUPPOSED TO WORK + * + * process command line arguments including sanity check + * initialize alarm signal handling + * if use_ssl + * build ssl context + * LOOP: + * make tcp connection + * if use_ssl + * make ssl connection + * if use_server_hostname + * check if certificate matches hostname + * if check_server_certificate + * check expiration date of server certificate + * return STATUS + * else + * request http page + * handle ssl rehandshake + * close ssl connection + * else + * request http page + * close tcp connection + * analyze http page + * if follow on redirect + * repeat LOOP + * end of LOOP + * destroy ssl context + */ +int +main (int argc, char **argv) +{ + int result = STATE_UNKNOWN; + int sock; + struct pageref page; +#ifdef HAVE_SSL + SSL_CTX *ctx; + SSL *ssl; + BIO *sbio; +#endif + + if ( process_arguments(argc, argv) == ERROR ) + usage( "check_http: could not parse arguments\n" ); + +#ifdef HAVE_SSL + /* build SSL context if required: + * a) either we use ssl from the beginning OR + * b) or we follor redirects wich may lead os to a ssl page + */ + if ( use_ssl || ( http_redirect_state == STATE_DEPENDENT ) ) + ctx=initialize_ssl_ctx(); +#endif + + /* Loop around 3xx onredirect=follow */ + do { + + /* + * initialize alarm signal handling, set socket timeout, start timer + * socket_timeout and socket_timeout_alarm_handler are defined in + * netutils.c + */ + (void) signal( SIGALRM, socket_timeout_alarm_handler ); + (void) alarm( socket_timeout ); + gettimeofday( &start_tv, NULL ); + + /* make a tcp connection */ + result = my_tcp_connect( server_host, server_port, &sock ); + + /* result of tcp connect */ + if ( result == STATE_OK ) + { +#ifdef HAVE_SSL + /* make a ssl connection */ + if ( use_ssl ) { + ssl=SSL_new( ctx ); + sbio=BIO_new_socket( sock, BIO_NOCLOSE ); + SSL_set_bio( ssl, sbio, sbio); + if ( SSL_connect( ssl ) <= 0 ) + ssl_terminate( STATE_CRITICAL, "check_http: SSL connect error" ); + + /* fetch server certificate */ + result = fetch_server_certificate( ssl ); + + /* verify server certificate against CAs */ + if ( ( result == STATE_OK ) && use_ca_certificate ) { + result = check_server_certificate_chain( ssl ); + } + + /* check if certificate matches hostname */ + if ( ( result == STATE_OK ) && use_server_hostname ) { + result = check_server_certificate_hostname(); + } + + if ( result == STATE_OK ) { + /* check server certificate expire date */ + if ( check_server_certificate ) { + result = check_server_certificate_expires(); + /* OR: perform http request */ + } else { + result = https_request( ctx, ssl, (struct pageref *) &page ); + } + } + SSL_shutdown( ssl ); + SSL_free( ssl ); + } else { +#endif + /* HTTP implementation */ + result = http_request( sock, (struct pageref *) &page ); +#ifdef HAVE_SSL + } +#endif + /* stop timer and calculate elapsed_time */ + elapsed_time = delta_time( start_tv ); + + /* close the tcp connection */ + close( sock ); + + /* reset the alarm */ + alarm( 0 ); + + /* analyze http page */ + /* TO DO */ + if ( result == STATE_OK ) + result = parse_http_response( (struct pageref *) &page ); + + if ( result == STATE_OK ) + result = check_http_response( (struct pageref *) &page ); + + switch ( result ) { + case STATE_OK: + /* weiter geht's */ + result = check_http_content( (struct pageref *) &page ); + break; + case STATE_DEPENDENT: + /* try to determine redirect parameters */ + result = prepare_follow_redirect( (struct pageref *) &page ); + break; + } + + } else { + /* some error occured while trying to make a tcp connect */ + exit( result ); + } + + } while ( result == STATE_DEPENDENT ); // end of onredirect loop + + /* destroy SSL context */ +#ifdef HAVE_SSL + if ( use_ssl || ( http_redirect_state == STATE_DEPENDENT ) ) + destroy_ssl_ctx( ctx ); +#endif + + /* if we ever get to this point, everything went fine */ + printf( RESULT_TEMPLATE_STATUS_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( result ), + page.status, + elapsed_time, + elapsed_time ); + + return result; +} + + +void +print_help( void ) +{ + print_revision( progname, REVISION ); + printf + ( "Copyright (c) %s %s <%s>\n\n%s\n", + COPYRIGHT, AUTHORS, EMAIL, HELP_TXT_SUMMARY ); + print_usage(); + printf( "NOTE: One or both of -H and -I must be specified\n" ); + printf( "\nOptions:\n" HELP_TXT_LONGOPTIONS "\n", + HTTP_PORT, DEFAULT_HTTP_URL_PATH, HTTPS_PORT, + DEFAULT_HTTP_EXPECT, DEFAULT_SOCKET_TIMEOUT ); +#ifdef HAVE_SSL + //printf( SSLDESCRIPTION ); +#endif +} + + +void +print_usage( void ) +{ + printf( "Usage:\n" " %s %s\n" +#ifdef HAVE_GETOPT_H + " %s (-h | --help) for detailed help\n" + " %s (-V | --version) for version information\n", +#else + " %s -h for detailed help\n" + " %s -V for version information\n", +#endif + progname, HELP_TXT_OPTIONS, progname, progname ); +} + + +/* +* process_arguments() +* +* process command line arguments either using getopt_long or getopt +* (parsing long argumants manually) +*/ +int +process_arguments( int argc, char **argv ) +{ + int c, i = 1; + extern char *optarg; + +#ifdef HAVE_GETOPT_H + int option_index = 0; + static struct option long_options[] = { + STD_LONG_OPTS, + {"file", required_argument, 0, 'F'}, + {"ip-address", required_argument, 0, 'I'}, + {"port", required_argument, 0, 'p'}, + {"url-path", required_argument, 0, 'u'}, + {"post-data", required_argument, 0, 'P'}, + {"ssl", no_argument, 0, 'S'}, + {"server-certificate-days", required_argument, 0, 'C'}, + {"basic-auth", required_argument, 0, 'a'}, + {"client-certificate", required_argument, 0, 'A'}, + {"passphrase", required_argument, 0, 'K'}, + {"ca-certificate", required_argument, 0, 'Z'}, + {"http-expect", required_argument, 0, 'e'}, + {"http-expect-only", required_argument, 0, 'E'}, + {"content-string", required_argument, 0, 's'}, + {"content-ereg-linespan", required_argument, 0, 'l'}, + {"content-ereg", required_argument, 0, 'r'}, + {"content-eregi", required_argument, 0, 'R'}, + {"onredirect", required_argument, 0, 'f'}, + {"onerror", required_argument, 0, 'g'}, + {"min", required_argument, 0, 'm'}, + {0, 0, 0, 0} + }; +#endif + + + /* convert commonly used arguments to their equivalent standard options */ + for (c = 1; c < argc; c++) { + if ( strcmp( "-to", argv[c]) == 0 ) + strcpy( argv[c], "-t" ); + if ( strcmp( "-hn", argv[c]) == 0 ) + strcpy( argv[c], "-H" ); + if ( strcmp( "-wt", argv[c]) == 0 ) + strcpy( argv[c], "-w" ); + if ( strcmp( "-ct", argv[c]) == 0 ) + strcpy( argv[c], "-c" ); + } + +#define OPTCHARS "Vvht:c:w:H:F:I:p:u:P:SC:a:A:K:Z:e:E:s:r:R:f:g:lm:" + + + while (1) { + +#ifdef HAVE_GETOPT_H + c = getopt_long( argc, argv, OPTCHARS, long_options, &option_index ); +#else + c = getopt( argc, argv, OPTCHARS ); +#endif + + if ( ( c == -1 ) || ( c == EOF ) ) { + break; + } + + switch (c) { + case '?': /* usage */ + usage2( "unknown argument", optarg ); + break; + + /* Standard options */ + case 'h': /* help */ + print_help(); + exit( STATE_OK ); + break; + case 'V': /* version */ + print_revision( progname, REVISION ); + exit( STATE_OK ); + break; + case 'v': /* verbose */ + verbose = TRUE; + break; + case 't': /* timeout period */ + if ( !is_intnonneg( optarg ) ) + usage2( "timeout interval must be a non-negative integer", optarg ); + /* socket_timeout is defined in netutils.h and defaults to + * DEFAULT_SOCKET_TIMEOUT from common.h + */ + socket_timeout = atoi( optarg ); + break; + case 'c': /* critical time threshold */ + if ( !is_nonnegative( optarg ) ) + usage2( "invalid critical threshold", optarg ); + critical_interval = strtod( optarg, NULL ); + use_critical_interval = TRUE; + break; + case 'w': /* warning time threshold */ + if ( !is_nonnegative( optarg ) ) + usage2( "invalid warning threshold", optarg ); + warning_interval = strtod( optarg, NULL ); + use_warning_interval = TRUE; + break; + case 'H': /* Host Name (virtual host) */ + /* this rejects FQDNs, so we leave it for now... + *if ( !is_hostname( optarg ) ) + * usage2( "invalid hostname", optarg ); + */ + asprintf( &server_hostname, "%s", optarg ); + use_server_hostname = TRUE; + break; + case 'F': /* File (dummy) */ + break; + /* End of standard options */ + + + case 'I': /* Server IP-address or Hostname */ + /* this rejects FQDNs, so we leave it for now... + *if ( !is_host( optarg ) ) + * usage2( "invalid ip address or hostname", optarg ) + */ + asprintf( &server_host, "%s", optarg ); + break; + case 'p': /* Server port */ + if ( !is_intnonneg( optarg ) ) + usage2( "invalid port number", optarg ); + server_port = atoi( optarg ); + use_server_port = TRUE; + break; + case 'S': /* use SSL */ +#ifdef HAVE_SSL + use_ssl = TRUE; + if ( use_server_port == FALSE ) + server_port = HTTPS_PORT; +#else + usage( "check_http: invalid option - SSL is not available\n" ); +#endif + break; + case 'C': /* Server certificate warning time threshold */ +#ifdef HAVE_SSL + if ( !is_intnonneg( optarg ) ) + usage2( "invalid certificate expiration period", optarg ); + server_certificate_min_days_valid = atoi( optarg ); + check_server_certificate = TRUE; +#else + usage( "check_http: invalid option - SSL is not available\n" ); +#endif + break; + case 'a': /* basic authorization info */ + strncpy( basic_auth, optarg, MAX_INPUT_BUFFER - 1 ); + basic_auth[MAX_INPUT_BUFFER - 1] = 0; + use_basic_auth = TRUE; + break; + case 'A': /* client certificate */ +#ifdef HAVE_SSL + asprintf( &client_certificate_file, "%s", optarg ); + use_client_certificate = TRUE; +#else + usage( "check_http: invalid option - SSL is not available\n" ); +#endif + break; + case 'K': /* client certificate passphrase */ +#ifdef HAVE_SSL + asprintf( &client_certificate_passphrase, "%s", optarg ); + use_client_certificate_passphrase = TRUE; +#else + usage( "check_http: invalid option - SSL is not available\n" ); +#endif + case 'Z': /* valid CA certificates */ +#ifdef HAVE_SSL + asprintf( &ca_certificate_file, "%s", optarg ); + use_ca_certificate = TRUE; +#else + usage( "check_http: invalid option - SSL is not available\n" ); +#endif + break; + case 'u': /* URL PATH */ + asprintf( &http_url_path, "%s", optarg ); + break; + case 'P': /* POST DATA */ + asprintf( &http_post_data, "%s", optarg ); + use_http_post_data = TRUE; + asprintf( &http_method, "%s", "POST" ); + break; + case 'e': /* expected string in first line of HTTP response */ + strncpy( http_expect , optarg, MAX_INPUT_BUFFER - 1 ); + http_expect[MAX_INPUT_BUFFER - 1] = 0; + break; + case 'E': /* expected string in first line of HTTP response and process no other check*/ + strncpy( http_expect , optarg, MAX_INPUT_BUFFER - 1 ); + http_expect[MAX_INPUT_BUFFER - 1] = 0; + use_http_expect_only = TRUE; + break; + case 's': /* expected (sub-)string in content */ + strncpy( content_string , optarg, MAX_INPUT_BUFFER - 1 ); + content_string[MAX_INPUT_BUFFER - 1] = 0; + check_content_string = TRUE; + break; + case 'l': /* regex linespan */ +#ifdef HAVE_REGEX_H + regex_cflags &= ~REG_NEWLINE; +#else + usage( "check_http: call for regex which was not a compiled option\n" ); +#endif + break; + case 'R': /* expected case insensitive regular expression in content */ +#ifdef HAVE_REGEX_H + regex_cflags |= REG_ICASE; +#else + usage( "check_http: call for regex which was not a compiled option\n" ); +#endif + case 'r': /* expected regular expression in content */ +#ifdef HAVE_REGEX_H + strncpy( content_regex , optarg, MAX_REGEX_SIZE - 1 ); + content_regex[MAX_REGEX_SIZE - 1] = 0; + check_content_regex = TRUE; + regex_error = regcomp( ®ex_preg, content_regex, regex_cflags ); + if ( regex_error != 0 ) { + regerror( regex_error, ®ex_preg, regex_error_buffer, MAX_INPUT_BUFFER ); + printf( "Could Not Compile Regular Expression: %s", regex_error_buffer ); + return ERROR; + } +#else + usage( "check_http: call for regex which was not a compiled option\n" ); +#endif + break; + case 'f': /* onredirect (3xx errors) */ + if ( !strcmp( optarg, "follow" ) ) + http_redirect_state = STATE_DEPENDENT; + if ( !strcmp( optarg, "unknown" ) ) + http_redirect_state = STATE_UNKNOWN; + if ( !strcmp( optarg, "ok" ) ) + http_redirect_state = STATE_OK; + if ( !strcmp( optarg, "warning" ) ) + http_redirect_state = STATE_WARNING; + if ( !strcmp( optarg, "critical" ) ) + http_redirect_state = STATE_CRITICAL; + break; + case 'g': /* onerror (4xx errors) */ + if ( !strcmp( optarg, "unknown" ) ) + http_client_error_state = STATE_UNKNOWN; + if ( !strcmp( optarg, "ok" ) ) + http_client_error_state = STATE_OK; + if ( !strcmp( optarg, "warning" ) ) + http_client_error_state = STATE_WARNING; + if ( !strcmp( optarg, "critical" ) ) + http_client_error_state = STATE_CRITICAL; + break; + case 'm': /* min */ + if ( !is_intnonneg( optarg ) ) + usage2( "invalid page size", optarg ); + min_content_length = atoi( optarg ); + use_min_content_length = TRUE; + break; + } // end switch + } // end while(1) + + c = optind; + + + /* Sanity checks on supplied command line arguments */ + + /* 1. if both host and hostname are not defined, try to + * fetch one more argument which is possibly supplied + * without an option + */ + if ( ( strcmp( server_host, "" ) ) && (c < argc) ) { + asprintf( &server_host, "%s", argv[c++] ); + } + + /* 2. check if another artument is supplied + */ + if ( ( strcmp( server_hostname, "" ) == 0 ) && (c < argc) ) { + asprintf( &server_hostname, "%s", argv[c++] ); + } + + /* 3. if host is still not defined, just copy hostname, + * which is then guaranteed to be defined by now + */ + if ( strcmp( server_host, "") == 0 ) { + if ( strcmp( server_hostname, "" ) == 0 ) { + usage ("check_http: you must specify a server address or host name\n"); + } else { + asprintf( &server_host, "%s", server_hostname ); + } + } + + /* 4. check if content checks for a string and a regex + * are requested for only one of both is possible at + * a time + */ + if ( check_content_string && check_content_regex ) + usage( "check_http: you can only check for string OR regex at a time\n" ); + + /* 5. check for options which require use_ssl */ + if ( check_server_certificate && !use_ssl ) + usage( "check_http: you must use -S to check server certificate\n" ); + if ( use_client_certificate && !use_ssl ) + usage( "check_http: you must use -S to authenticate with a client certificate\n" ); + if ( use_ca_certificate && !use_ssl ) + usage( "check_http: you must use -S to check server certificate against CA certificates\n" ); + + /* 6. check for passphrase without client certificate */ + if ( use_client_certificate_passphrase && !use_client_certificate ) + usage( "check_http: you must supply a client certificate to use a passphrase\n" ); + + + /* Finally set some default values if necessary */ + if ( strcmp( http_method, "" ) == 0 ) + asprintf( &http_method, "%s", DEFAULT_HTTP_METHOD ); + if ( strcmp( http_url_path, "" ) == 0 ) { + asprintf( &http_url_path, "%s", DEFAULT_HTTP_URL_PATH ); + } + + return TRUE; +} + + +int +http_request( int sock, struct pageref *page ) +{ + char *buffer = ""; + char recvbuff[MAX_INPUT_BUFFER] = ""; + int buffer_len = 0; + int content_len = 0; + size_t sendsize = 0; + size_t recvsize = 0; + char *content = ""; + size_t size = 0; + char *basic_auth_encoded = NULL; + + asprintf( &buffer, HTTP_TEMPLATE_REQUEST, buffer, http_method, http_url_path ); + + asprintf( &buffer, HTTP_TEMPLATE_HEADER_USERAGENT, buffer, progname, REVISION, PACKAGE_VERSION ); + + if ( use_server_hostname ) { + asprintf( &buffer, HTTP_TEMPLATE_HEADER_HOST, buffer, server_hostname ); + } + + if ( use_basic_auth ) { + basic_auth_encoded = base64( basic_auth, strlen( basic_auth ) ); + asprintf( &buffer, HTTP_TEMPLATE_HEADER_AUTH, buffer, basic_auth_encoded ); + } + + /* either send http POST data */ + if ( use_http_post_data ) { + /* based on code written by Chris Henesy <lurker@shadowtech.org> */ + asprintf( &buffer, "Content-Type: application/x-www-form-urlencoded\r\n" ); + asprintf( &buffer, "Content-Length: %i\r\n\r\n", buffer, content_len ); + asprintf( &buffer, "%s%s%s", buffer, http_post_data, "\r\n" ); + sendsize = send( sock, buffer, strlen( buffer ), 0 ); + if ( sendsize < strlen( buffer ) ) { + printf( "ERROR: Incomplete write\n" ); + return STATE_CRITICAL; + } + /* or just a newline */ + } else { + asprintf( &buffer, "%s%s", buffer, "\r\n" ); + sendsize = send( sock, buffer, strlen( buffer ) , 0 ); + if ( sendsize < strlen( buffer ) ) { + printf( "ERROR: Incomplete write\n" ); + return STATE_CRITICAL; + } + } + + + /* read server's response */ + + do { + recvsize = recv( sock, recvbuff, MAX_INPUT_BUFFER - 1, 0 ); + if ( recvsize > (size_t) 0 ) { + recvbuff[recvsize] = '\0'; + asprintf( &content, "%s%s", content, recvbuff ); + size += recvsize; + } + } while ( recvsize > (size_t) 0 ); + + asprintf( &page->content, "%s", content ); + page->size = size; + + /* return a CRITICAL status if we couldn't read any data */ + if ( size == (size_t) 0) + ssl_terminate( STATE_CRITICAL, "No data received" ); + + return STATE_OK; +} + + +int +parse_http_response( struct pageref *page ) +{ + char *content = ""; //local copy of struct member + char *status = ""; //local copy of struct member + char *header = ""; //local copy of struct member + size_t len = 0; //temporary used + char *pos = ""; //temporary used + + asprintf( &content, "%s", page->content ); + + /* find status line and null-terminate it */ + // copy content to status + status = content; + + // find end of status line and copy pointer to pos + content += (size_t) strcspn( content, "\r\n" ); + pos = content; + + // advance content pointer behind the newline of status line + content += (size_t) strspn( content, "\r\n" ); + + // null-terminate status line at pos + status[strcspn( status, "\r\n")] = 0; + strip( status ); + + // copy final status to struct member + page->status = status; + + + /* find header and null-terminate it */ + // copy remaining content to header + header = content; + + // loop until line containing only newline is found (end of header) + while ( strcspn( content, "\r\n" ) > 0 ) { + //find end of line and copy pointer to pos + content += (size_t) strcspn( content, "\r\n" ); + pos = content; + + if ( ( strspn( content, "\r" ) == 1 && strspn( content, "\r\n" ) >= 2 ) || + ( strspn( content, "\n" ) == 1 && strspn( content, "\r\n" ) >= 2 ) ) + content += (size_t) 2; + else + content += (size_t) 1; + } + // advance content pointer behind the newline + content += (size_t) strspn( content, "\r\n" ); + + // null-terminate header at pos + header[pos - header] = 0; + + // copy final header to struct member + page->header = header; + + + // copy remaining content to body + page->body = content; + + if ( verbose ) { + printf( "STATUS: %s\n", page->status ); + printf( "HEADER: \n%s\n", page->header ); + printf( "BODY: \n%s\n", page->body ); + } + + return STATE_OK; +} + + +int +check_http_response( struct pageref *page ) +{ + char *msg = ""; + + /* check response time befor anything else */ + if ( use_critical_interval && ( elapsed_time > critical_interval ) ) { + asprintf( &msg, RESULT_TEMPLATE_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( STATE_CRITICAL ), + elapsed_time, + elapsed_time ); + terminate( STATE_CRITICAL, msg ); + } + if ( use_warning_interval && ( elapsed_time > warning_interval ) ) { + asprintf( &msg, RESULT_TEMPLATE_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( STATE_WARNING ), + elapsed_time, + elapsed_time ); + terminate( STATE_WARNING, msg ); + } + + + /* make sure the status line matches the response we are looking for */ + if ( strstr( page->status, http_expect ) ) { + /* The result is only checked against the expected HTTP status line, + so exit immediately after this check */ + if ( use_http_expect_only ) { + if ( ( server_port == HTTP_PORT ) +#ifdef HAVE_SSL + || ( server_port == HTTPS_PORT ) ) +#else + ) +#endif + asprintf( &msg, "Expected HTTP response received from host\n" ); + else + asprintf( &msg, "Expected HTTP response received from host on port %d\n", server_port ); + terminate( STATE_OK, msg ); + } + } else { + if ( ( server_port == HTTP_PORT ) +#ifdef HAVE_SSL + || ( server_port == HTTPS_PORT ) ) +#else + ) +#endif + asprintf( &msg, "Invalid HTTP response received from host\n" ); + else + asprintf( &msg, "Invalid HTTP response received from host on port %d\n", server_port ); + terminate( STATE_CRITICAL, msg ); + } + + /* check the return code */ + /* server errors result in a critical state */ + if ( strstr( page->status, "500" ) || + strstr( page->status, "501" ) || + strstr( page->status, "502" ) || + strstr( page->status, "503" ) || + strstr( page->status, "504" ) || + strstr( page->status, "505" )) { + asprintf( &msg, RESULT_TEMPLATE_STATUS_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( http_client_error_state ), + page->status, + elapsed_time, + elapsed_time ); + terminate( STATE_CRITICAL, msg ); + } + + /* client errors result in a warning state */ + if ( strstr( page->status, "400" ) || + strstr( page->status, "401" ) || + strstr( page->status, "402" ) || + strstr( page->status, "403" ) || + strstr( page->status, "404" ) || + strstr( page->status, "405" ) || + strstr( page->status, "406" ) || + strstr( page->status, "407" ) || + strstr( page->status, "408" ) || + strstr( page->status, "409" ) || + strstr( page->status, "410" ) || + strstr( page->status, "411" ) || + strstr( page->status, "412" ) || + strstr( page->status, "413" ) || + strstr( page->status, "414" ) || + strstr( page->status, "415" ) || + strstr( page->status, "416" ) || + strstr( page->status, "417" ) ) { + asprintf( &msg, RESULT_TEMPLATE_STATUS_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( http_client_error_state ), + page->status, + elapsed_time, + elapsed_time ); + terminate( http_client_error_state, msg ); + } + + /* check redirected page if specified */ + if (strstr( page->status, "300" ) || + strstr( page->status, "301" ) || + strstr( page->status, "302" ) || + strstr( page->status, "303" ) || + strstr( page->status, "304" ) || + strstr( page->status, "305" ) || + strstr( page->status, "306" ) || + strstr( page->status, "307" ) ) { + if ( http_redirect_state == STATE_DEPENDENT ) { + /* returning STATE_DEPENDENT means follow redirect */ + return STATE_DEPENDENT; + } else { + asprintf( &msg, RESULT_TEMPLATE_STATUS_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( http_redirect_state ), + page->status, + elapsed_time, + elapsed_time ); + terminate( http_redirect_state, msg ); + } + } + + return STATE_OK; +} + +int +check_http_content( struct pageref *page ) +{ + char *msg = ""; + + /* check for string in content */ + if ( check_content_string ) { + if ( strstr( page->content, content_string ) ) { + asprintf( &msg, RESULT_TEMPLATE_STATUS_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( STATE_OK ), + page->status, + elapsed_time, + elapsed_time ); + terminate( STATE_OK, msg ); + } else { + asprintf( &msg, RESULT_TEMPLATE_STATUS_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( STATE_CRITICAL ), + page->status, + elapsed_time, + elapsed_time ); + terminate( STATE_CRITICAL, msg ); + } + } + +#ifdef HAVE_REGEX_H + /* check for regex in content */ + if ( check_content_regex ) { + regex_error = regexec( ®ex_preg, page->content, REGEX_REGS, regex_pmatch, 0); + if ( regex_error == 0 ) { + asprintf( &msg, RESULT_TEMPLATE_STATUS_RESPONSE_TIME, + protocol_text( use_ssl ), + state_text( STATE_OK ), + page->status, + elapsed_time, + elapsed_time ); + terminate( STATE_OK, msg ); + } else { + if ( regex_error == REG_NOMATCH ) { + asprintf( &msg, "%s, %s: regex pattern not found\n", + protocol_text( use_ssl) , + state_text( STATE_CRITICAL ) ); + terminate( STATE_CRITICAL, msg ); + } else { + regerror( regex_error, ®ex_preg, regex_error_buffer, MAX_INPUT_BUFFER); + asprintf( &msg, "%s %s: Regex execute Error: %s\n", + protocol_text( use_ssl) , + state_text( STATE_CRITICAL ), + regex_error_buffer ); + terminate( STATE_CRITICAL, msg ); + } + } + } +#endif + + return STATE_OK; +} + + +int +prepare_follow_redirect( struct pageref *page ) +{ + char *header = NULL; + char *msg = ""; + char protocol[6]; + char hostname[MAX_IPV4_HOSTLENGTH]; + char port[6]; + char *url_path = NULL; + char *orig_url_path = NULL; + char *orig_url_dirname = NULL; + size_t len = 0; + + asprintf( &header, "%s", page->header ); + + + /* restore some default values */ + use_http_post_data = FALSE; + asprintf( &http_method, "%s", DEFAULT_HTTP_METHOD ); + + /* copy url of original request, maybe we need it to compose + absolute url from relative Location: header */ + asprintf( &orig_url_path, "%s", http_url_path ); + + while ( strcspn( header, "\r\n" ) > (size_t) 0 ) { + url_path = realloc( url_path, (size_t) strcspn( header, "\r\n" ) ); + if ( url_path == NULL ) + terminate( STATE_UNKNOWN, "HTTP UNKNOWN: could not reallocate url_path" ); + + + /* Try to find a Location header combination of METHOD HOSTNAME PORT and PATH */ + /* 1. scan for Location: http[s]://hostname:port/path */ + if ( sscanf ( header, HTTP_HEADER_LOCATION_MATCH HTTP_HEADER_PROTOCOL_MATCH HTTP_HEADER_HOSTNAME_MATCH HTTP_HEADER_PORT_MATCH HTTP_HEADER_URL_PATH_MATCH, &protocol, &hostname, &port, url_path ) == 4 ) { + asprintf( &server_hostname, "%s", hostname ); + asprintf( &server_host, "%s", hostname ); + use_ssl = chk_protocol(protocol); + server_port = atoi( port ); + asprintf( &http_url_path, "%s", url_path ); + return STATE_DEPENDENT; + } + else if ( sscanf ( header, HTTP_HEADER_LOCATION_MATCH HTTP_HEADER_PROTOCOL_MATCH HTTP_HEADER_HOSTNAME_MATCH HTTP_HEADER_URL_PATH_MATCH, &protocol, &hostname, url_path ) == 3) { + asprintf( &server_hostname, "%s", hostname ); + asprintf( &server_host, "%s", hostname ); + use_ssl = chk_protocol(protocol); + server_port = protocol_std_port(use_ssl); + asprintf( &http_url_path, "%s", url_path ); + return STATE_DEPENDENT; + } + else if ( sscanf ( header, HTTP_HEADER_LOCATION_MATCH HTTP_HEADER_PROTOCOL_MATCH HTTP_HEADER_HOSTNAME_MATCH HTTP_HEADER_PORT_MATCH, &protocol, &hostname, &port ) == 3) { + asprintf( &server_hostname, "%s", hostname ); + asprintf( &server_host, "%s", hostname ); + use_ssl = chk_protocol(protocol); + server_port = atoi( port ); + asprintf( &http_url_path, "%s", DEFAULT_HTTP_URL_PATH ); + return STATE_DEPENDENT; + } + else if ( sscanf ( header, HTTP_HEADER_LOCATION_MATCH HTTP_HEADER_PROTOCOL_MATCH HTTP_HEADER_HOSTNAME_MATCH, protocol, hostname ) == 2 ) { + asprintf( &server_hostname, "%s", hostname ); + asprintf( &server_host, "%s", hostname ); + use_ssl = chk_protocol(protocol); + server_port = protocol_std_port(use_ssl); + asprintf( &http_url_path, "%s", DEFAULT_HTTP_URL_PATH ); + } + else if ( sscanf ( header, HTTP_HEADER_LOCATION_MATCH HTTP_HEADER_URL_PATH_MATCH, url_path ) == 1 ) { + /* check for relative url and prepend path if necessary */ + if ( ( url_path[0] != '/' ) && ( orig_url_dirname = strrchr( orig_url_path, '/' ) ) ) { + *orig_url_dirname = '\0'; + asprintf( &http_url_path, "%s%s", orig_url_path, url_path ); + } else { + asprintf( &http_url_path, "%s", url_path ); + } + return STATE_DEPENDENT; + } + header += (size_t) strcspn( header, "\r\n" ); + header += (size_t) strspn( header, "\r\n" ); + } /* end while (header) */ + + + /* default return value is STATE_DEPENDENT to continue looping in main() */ + asprintf( &msg, "% %: % - Could not find redirect Location", + protocol_text( use_ssl ), + state_text( STATE_UNKNOWN ), + page->status ); + terminate( STATE_UNKNOWN, msg ); +} + +#ifdef HAVE_SSL +int +https_request( SSL_CTX *ctx, SSL *ssl, struct pageref *page ) +{ + char *buffer = ""; + char recvbuff[MAX_INPUT_BUFFER] = ""; + int buffer_len = 0; + int content_len = 0; + size_t sendsize = 0; + size_t recvsize = 0; + char *content = ""; + size_t size = 0; + char *basic_auth_encoded = NULL; + + asprintf( &buffer, HTTP_TEMPLATE_REQUEST, buffer, http_method, http_url_path ); + + asprintf( &buffer, HTTP_TEMPLATE_HEADER_USERAGENT, buffer, progname, REVISION, PACKAGE_VERSION ); + + if ( use_server_hostname ) { + asprintf( &buffer, HTTP_TEMPLATE_HEADER_HOST, buffer, server_hostname ); + } + + if ( use_basic_auth ) { + basic_auth_encoded = base64( basic_auth, strlen( basic_auth ) ); + asprintf( &buffer, HTTP_TEMPLATE_HEADER_AUTH, buffer, basic_auth_encoded ); + } + + /* either send http POST data */ + if ( use_http_post_data ) { + asprintf( &buffer, "%sContent-Type: application/x-www-form-urlencoded\r\n", buffer ); + asprintf( &buffer, "%sContent-Length: %i\r\n\r\n", buffer, content_len ); + asprintf( &buffer, "%s%s%s", buffer, http_post_data, "\r\n" ); + sendsize = SSL_write( ssl, buffer, strlen( buffer ) ); + switch ( SSL_get_error( ssl, sendsize ) ) { + case SSL_ERROR_NONE: + if ( sendsize < strlen( buffer ) ) + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Incomplete write.\n" ); + break; + default: + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Write problem.\n" ); + break; + } + /* or just a newline */ + } else { + + asprintf( &buffer, "%s\r\n", buffer ); + sendsize = SSL_write( ssl, buffer, strlen( buffer ) ); + switch ( SSL_get_error( ssl, sendsize ) ) { + case SSL_ERROR_NONE: + if ( sendsize < strlen( buffer ) ) + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Incomplete write.\n" ); + break; + default: + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Write problem.\n" ); + break; + } + } + + + /* read server's response */ + + do { + recvsize = SSL_read( ssl, recvbuff, MAX_INPUT_BUFFER - 1 ); + + switch ( SSL_get_error( ssl, recvsize ) ) { + case SSL_ERROR_NONE: + if ( recvsize > (size_t) 0 ) { + recvbuff[recvsize] = '\0'; + asprintf( &content, "%s%s", content, recvbuff ); + size += recvsize; + } + break; + case SSL_ERROR_WANT_READ: + if ( use_client_certificate ) { + continue; + } else { + // workaround while we don't have anonymous client certificates: return OK + //ssl_terminate( STATE_WARNING, "HTTPS WARNING - Client Certificate required.\n" ); + ssl_terminate( STATE_OK, "HTTPS WARNING - Client Certificate required.\n" ); + } + break; + case SSL_ERROR_ZERO_RETURN: + break; + case SSL_ERROR_SYSCALL: + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Premature close.\n" ); + break; + default: + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Read problem.\n" ); + break; + } + } while ( recvsize > (size_t) 0 ); + + asprintf( &page->content, "%s", content ); + page->size = size; + + /* return a CRITICAL status if we couldn't read any data */ + if ( size == (size_t) 0) + ssl_terminate( STATE_CRITICAL, "No data received" ); + + return STATE_OK; +} +#endif + + +#ifdef HAVE_SSL +int +ssl_terminate(int state, char *string ) { + ERR_print_errors( bio_err ); + terminate( state, string ); +} +#endif + +#ifdef HAVE_SSL +static int +password_cb( char *buf, int num, int rwflag, void *userdata ) +{ + if ( num < strlen( client_certificate_passphrase ) + 1 ) + return( 0 ); + + strcpy( buf, client_certificate_passphrase ); + return( strlen( client_certificate_passphrase ) ); +} +#endif + +#ifdef HAVE_SSL +static void +sigpipe_handle( int x ) { +} +#endif + +#ifdef HAVE_SSL +SSL_CTX * +initialize_ssl_ctx( void ) +{ + SSL_METHOD *meth; + SSL_CTX *ctx; + + if ( !bio_err ) { + /* Global system initialization */ + SSL_library_init(); + SSL_load_error_strings(); + + /* An error write context */ + bio_err=BIO_new_fp( stderr, BIO_NOCLOSE ); + } + + /* set up as SIGPIPE handler */ + signal( SIGPIPE, sigpipe_handle ); + + /* create our context */ + meth=SSLv3_method(); + ctx=SSL_CTX_new( meth ); + + /* load client certificate and key */ + if ( use_client_certificate ) { + if ( !(SSL_CTX_use_certificate_chain_file( ctx, client_certificate_file )) ) + ssl_terminate( STATE_CRITICAL, "check_http: can't read client certificate file" ); + + /* set client certificate key passphrase */ + if ( use_client_certificate_passphrase ) { + SSL_CTX_set_default_passwd_cb( ctx, password_cb ); + } + + if ( !(SSL_CTX_use_PrivateKey_file( ctx, client_certificate_file, SSL_FILETYPE_PEM )) ) + ssl_terminate( STATE_CRITICAL, "check_http: can't read client certificate key file" ); + } + + /* load the CAs we trust */ + if ( use_ca_certificate ) { + if ( !(SSL_CTX_load_verify_locations( ctx, ca_certificate_file, 0 )) ) + ssl_terminate( STATE_CRITICAL, "check_http: can't read CA certificate file" ); + +#if (OPENSSL_VERSION_NUMBER < 0x00905100L) + SSL_CTX_set_verify_depth( ctx, 1 ); +#endif + } + + return ctx; +} +#endif + +#ifdef HAVE_SSL +void destroy_ssl_ctx( SSL_CTX *ctx ) +{ + SSL_CTX_free( ctx ); +} +#endif + +#ifdef HAVE_SSL +int +fetch_server_certificate( SSL *ssl ) +{ + server_certificate = SSL_get_peer_certificate( ssl ); + if ( server_certificate == NULL ) + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Cannot retrieve server certificate.\n" ); + + return STATE_OK; +} +#endif + + +#ifdef HAVE_SSL +int +check_server_certificate_chain( SSL *ssl ) +{ + if ( SSL_get_verify_result( ssl ) != X509_V_OK ) + ssl_terminate( STATE_CRITICAL, "SSL ERROR: Cannot verify server certificate chain.\n" ); + + return STATE_OK; +} +#endif + + +#ifdef HAVE_SSL +int +check_server_certificate_hostname( ) +{ + char server_CN[256]; + char *msg = NULL; + X509_NAME_get_text_by_NID( X509_get_subject_name( server_certificate ), NID_commonName, server_CN, 256 ); + if ( strcasecmp( server_CN, server_hostname ) ) { + asprintf( &msg, "SSL ERROR: Server Certificate does not match Hostname %s.\n", server_hostname ); + ssl_terminate( STATE_WARNING, msg ); + } + + return STATE_OK; +} +#endif + +#ifdef HAVE_SSL +int +check_server_certificate_expires( ) +{ + ASN1_STRING *tm; + int offset; + struct tm stamp; + int days_left; + char timestamp[17] = ""; + char *msg = NULL; + + /* Retrieve timestamp of certificate */ + tm = X509_get_notAfter( server_certificate ); + + /* Generate tm structure to process timestamp */ + if ( tm->type == V_ASN1_UTCTIME ) { + if ( tm->length < 10 ) { + ssl_terminate( STATE_CRITICAL, "ERROR: Wrong time format in certificate.\n" ); + } else { + stamp.tm_year = ( tm->data[0] - '0' ) * 10 + ( tm->data[1] - '0' ); + if ( stamp.tm_year < 50 ) + stamp.tm_year += 100; + offset = 0; + } + } else { + if ( tm->length < 12 ) { + ssl_terminate( STATE_CRITICAL, "ERROR: Wrong time format in certificate.\n" ); + } else { + stamp.tm_year = + ( tm->data[0] - '0' ) * 1000 + ( tm->data[1] - '0' ) * 100 + + ( tm->data[2] - '0' ) * 10 + ( tm->data[3] - '0' ); + stamp.tm_year -= 1900; + offset = 2; + } + } + stamp.tm_mon = + ( tm->data[2 + offset] - '0' ) * 10 + ( tm->data[3 + offset] - '0' ) - 1; + stamp.tm_mday = + ( tm->data[4 + offset] - '0' ) * 10 + ( tm->data[5 + offset] - '0' ); + stamp.tm_hour = + ( tm->data[6 + offset] - '0' ) * 10 + ( tm->data[7 + offset] - '0' ); + stamp.tm_min = + ( tm->data[8 + offset] - '0' ) * 10 + ( tm->data[9 + offset] - '0' ); + stamp.tm_sec = 0; + stamp.tm_isdst = -1; + + days_left = ( mktime( &stamp ) - time( NULL ) ) / 86400; + snprintf + ( timestamp, 17, "%02d.%02d.%04d %02d:%02d", + stamp.tm_mday, stamp.tm_mon +1, stamp.tm_year + 1900, + stamp.tm_hour, stamp.tm_min ); + + if ( ( days_left > 0 ) && ( days_left <= server_certificate_min_days_valid ) ) { + asprintf( &msg, "Certificate expires in %d day(s) (%s).\n", days_left, timestamp ); + ssl_terminate( STATE_WARNING, msg ); + } + if ( days_left < 0 ) { + asprintf( &msg, "Certificate expired on %s.\n", timestamp ); + ssl_terminate( STATE_CRITICAL, msg ); + } + + if (days_left == 0) { + asprintf( &msg, "Certificate expires today (%s).\n", timestamp ); + ssl_terminate( STATE_WARNING, msg ); + } + + asprintf( &msg, "Certificate will expire on %s.\n", timestamp ); + ssl_terminate( STATE_OK, msg ); +} +#endif + +/* written by lauri alanko */ +static char * +base64 (char *bin, int len) +{ + + char *buf = (char *) malloc ((len + 2) / 3 * 4 + 1); + int i = 0, j = 0; + + char BASE64_END = '='; + char base64_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + while (j < len - 2) { + buf[i++] = base64_table[bin[j] >> 2]; + buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)]; + buf[i++] = base64_table[((bin[j + 1] & 15) << 2) | (bin[j + 2] >> 6)]; + buf[i++] = base64_table[bin[j + 2] & 63]; + j += 3; + } + + switch (len - j) { + case 1: + buf[i++] = base64_table[bin[j] >> 2]; + buf[i++] = base64_table[(bin[j] & 3) << 4]; + buf[i++] = BASE64_END; + buf[i++] = BASE64_END; + break; + case 2: + buf[i++] = base64_table[bin[j] >> 2]; + buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)]; + buf[i++] = base64_table[(bin[j + 1] & 15) << 2]; + buf[i++] = BASE64_END; + break; + case 0: + break; + } + + buf[i] = '\0'; + return buf; +} + diff --git a/contrib/tarballs/check_traffic-0.91b.tar.gz b/contrib/tarballs/check_traffic-0.91b.tar.gz Binary files differnew file mode 100644 index 00000000..0d8ab798 --- /dev/null +++ b/contrib/tarballs/check_traffic-0.91b.tar.gz |