From b7c0754632ece2e05cbb5d3d7af6a925d2dfba7b Mon Sep 17 00:00:00 2001 From: Stanley Hopcroft Date: Tue, 1 Feb 2005 04:20:04 +0000 Subject: [PATCH] New or revised plugin in /contrib git-svn-id: https://nagiosplug.svn.sourceforge.net/svnroot/nagiosplug/nagiosplug/trunk@1117 f882894a-f735-0410-b71e-b25c423dba1c --- contrib/check_asterisk.pl | 259 +++ contrib/check_email_loop.pl | 46 +- contrib/check_http-with-client-certificate.c | 1567 ++++++++++++++++++ contrib/tarballs/check_traffic-0.91b.tar.gz | Bin 0 -> 12198 bytes 4 files changed, 1858 insertions(+), 14 deletions(-) create mode 100644 contrib/check_asterisk.pl create mode 100644 contrib/check_http-with-client-certificate.c create mode 100644 contrib/tarballs/check_traffic-0.91b.tar.gz diff --git a/contrib/check_asterisk.pl b/contrib/check_asterisk.pl new file mode 100644 index 0000000..179d367 --- /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 -u -p [-cwv]\n"; + print "Syntax: $0 -m iax -h [-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 733406d..965f851 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 0000000..157d028 --- /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 -I [-p ] [-u ]\n\ + [-w ] [-c ] [-t ]\n\ + [-S] [-C ] [-a ] [-A ]\n\ + [-Z ] [-e ] [-E ]\n\ + [-s ] [-r ] [-R ]\n\ + [-f (ok|warn|critical|follow)] [-g (ok|warn|critical)]\n" + +#define HELP_TXT_LONGOPTIONS "\ + -H, --hostname=\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=
\n\ + IP address or hostname for TCP connect (use IP to avoid DNS lookup)\n\ + -p, --port=\n\ + Port number (default: %d)\n\ + -u, --url-path=\n\ + URL to request from host (default: %s)\n\ + -S, --ssl\n\ + Use SSL (default port: %d)\n\ + -C, --server-certificate-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=\n\ + Colon separated username and password for basic authentication\n\ + -A, --client-certificate=\n\ + File containing X509 client certificate and key\n\ + -K, --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=\n\ + File containing certificates of trusted CAs\n\ + The server certificate will be checked against these CAs\n\ + -e, --http-expect=\n\ + String to expect in HTTP response line (Default: %s)\n\ + -E, --http-expect-only=\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=\n\ + String to expect in content\n\ + -r, --content-ereg=\n\ + Regular expression to expect in content\n\ + -R, --content-eregi=\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=\n\ + Seconds before connection times out (default: %d)\n\ + -c, --critical=\n\ + Response time to result in critical status (seconds)\n\ + -w, --warning=\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 +#include +#include +#include +#include +#include +#include +#endif + +#ifdef HAVE_OPENSSL_SSL_H +#include +#include +#include +#include +#include +#include +#include +#endif + +#define HTTPS_PORT 443 +#endif + +#ifdef HAVE_REGEX_H +#include +#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 */ + 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 new file mode 100644 index 0000000000000000000000000000000000000000..0d8ab7981741c8a1375828e7d6853a01faf10efe GIT binary patch literal 12198 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 o`y=CX^f~$*eU3gypQF#w=je0vIr literal 0 HcmV?d00001 -- 2.30.2