Code

Fix translations when extra-opts aren't enabled
[nagiosplug.git] / plugins / check_http.c
index e25e5db24715a01d352528ff39f32ebbed19a7bb..6c53f7ad495e0bb2d264de080b1e5ffc202babff 100644 (file)
@@ -1,40 +1,59 @@
-/******************************************************************************
+/*****************************************************************************
+* 
+* Nagios check_http plugin
+* 
+* License: GPL
+* Copyright (c) 1999-2008 Nagios Plugins Development Team
+* 
+* Description:
+* 
+* This file contains the check_http plugin
+* 
+* This plugin tests the HTTP service on the specified host. It can test
+* normal (http) and secure (https) servers, follow redirects, search for
+* strings and regular expressions, check connection times, and report on
+* certificate expiration times.
+* 
+* 
+* 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 3 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, see <http://www.gnu.org/licenses/>.
+* 
+* 
+*****************************************************************************/
 
 
- 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.
-
- $Id$
-******************************************************************************/
 /* splint -I. -I../../plugins -I../../lib/ -I/usr/kerberos/include/ ../../plugins/check_http.c */
 
 const char *progname = "check_http";
 /* splint -I. -I../../plugins -I../../lib/ -I/usr/kerberos/include/ ../../plugins/check_http.c */
 
 const char *progname = "check_http";
-const char *revision = "$Revision$";
-const char *copyright = "1999-2005";
+const char *copyright = "1999-2008";
 const char *email = "nagiosplug-devel@lists.sourceforge.net";
 
 #include "common.h"
 #include "netutils.h"
 #include "utils.h"
 const char *email = "nagiosplug-devel@lists.sourceforge.net";
 
 #include "common.h"
 #include "netutils.h"
 #include "utils.h"
+#include "base64.h"
+#include <ctype.h>
 
 #define INPUT_DELIMITER ";"
 
 #define INPUT_DELIMITER ";"
+#define STICKY_NONE 0
+#define STICKY_HOST 1
+#define STICKY_PORT 2
 
 #define HTTP_EXPECT "HTTP/1."
 enum {
   MAX_IPV4_HOSTLENGTH = 255,
   HTTP_PORT = 80,
 
 #define HTTP_EXPECT "HTTP/1."
 enum {
   MAX_IPV4_HOSTLENGTH = 255,
   HTTP_PORT = 80,
-  HTTPS_PORT = 443
+  HTTPS_PORT = 443,
+  MAX_PORT = 65535
 };
 
 #ifdef HAVE_SSL
 };
 
 #ifdef HAVE_SSL
@@ -51,26 +70,24 @@ X509 *server_cert;
 int no_body = FALSE;
 int maximum_age = -1;
 
 int no_body = FALSE;
 int maximum_age = -1;
 
-#ifdef HAVE_REGEX_H
 enum {
   REGS = 2,
   MAX_RE_SIZE = 256
 };
 enum {
   REGS = 2,
   MAX_RE_SIZE = 256
 };
-#include <regex.h>
+#include "regex.h"
 regex_t preg;
 regmatch_t pmatch[REGS];
 char regexp[MAX_RE_SIZE];
 char errbuf[MAX_INPUT_BUFFER];
 int cflags = REG_NOSUB | REG_EXTENDED | REG_NEWLINE;
 int errcode;
 regex_t preg;
 regmatch_t pmatch[REGS];
 char regexp[MAX_RE_SIZE];
 char errbuf[MAX_INPUT_BUFFER];
 int cflags = REG_NOSUB | REG_EXTENDED | REG_NEWLINE;
 int errcode;
-#endif
+int invert_regex = 0;
 
 struct timeval tv;
 
 #define HTTP_URL "/"
 #define CRLF "\r\n"
 
 
 struct timeval tv;
 
 #define HTTP_URL "/"
 #define CRLF "\r\n"
 
-char timestamp[17] = "";
 int specify_port = FALSE;
 int server_port = HTTP_PORT;
 char server_port_text[6] = "";
 int specify_port = FALSE;
 int server_port = HTTP_PORT;
 char server_port_text[6] = "";
@@ -88,11 +105,14 @@ int check_warning_time = FALSE;
 double critical_time = 0;
 int check_critical_time = FALSE;
 char user_auth[MAX_INPUT_BUFFER] = "";
 double critical_time = 0;
 int check_critical_time = FALSE;
 char user_auth[MAX_INPUT_BUFFER] = "";
+char proxy_auth[MAX_INPUT_BUFFER] = "";
 int display_html = FALSE;
 char **http_opt_headers;
 int http_opt_headers_count = 0;
 int onredirect = STATE_OK;
 int display_html = FALSE;
 char **http_opt_headers;
 int http_opt_headers_count = 0;
 int onredirect = STATE_OK;
+int followsticky = STICKY_NONE;
 int use_ssl = FALSE;
 int use_ssl = FALSE;
+int use_sni = FALSE;
 int verbose = FALSE;
 int sd;
 int min_page_len = 0;
 int verbose = FALSE;
 int sd;
 int min_page_len = 0;
@@ -105,7 +125,6 @@ char *http_content_type;
 char buffer[MAX_INPUT_BUFFER];
 
 int process_arguments (int, char **);
 char buffer[MAX_INPUT_BUFFER];
 
 int process_arguments (int, char **);
-static char *base64 (const char *bin, size_t len);
 int check_http (void);
 void redir (char *pos, char *status_line);
 int server_type_check(const char *type);
 int check_http (void);
 void redir (char *pos, char *status_line);
 int server_type_check(const char *type);
@@ -120,25 +139,25 @@ main (int argc, char **argv)
 {
   int result = STATE_UNKNOWN;
 
 {
   int result = STATE_UNKNOWN;
 
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
   /* Set default URL. Must be malloced for subsequent realloc if --onredirect=follow */
   server_url = strdup(HTTP_URL);
   server_url_length = strlen(server_url);
   /* Set default URL. Must be malloced for subsequent realloc if --onredirect=follow */
   server_url = strdup(HTTP_URL);
   server_url_length = strlen(server_url);
-  asprintf (&user_agent, "User-Agent: check_http/%s (nagios-plugins %s)",
-            clean_revstring (revision), VERSION);
+  asprintf (&user_agent, "User-Agent: check_http/v%s (nagios-plugins %s)",
+            NP_VERSION, VERSION);
+
+  /* Parse extra opts if any */
+  argv=np_extra_opts (&argc, argv, progname);
 
   if (process_arguments (argc, argv) == ERROR)
     usage4 (_("Could not parse arguments"));
 
 
   if (process_arguments (argc, argv) == ERROR)
     usage4 (_("Could not parse arguments"));
 
-  if (strstr (timestamp, ":")) {
-    if (strstr (server_url, "?"))
-      asprintf (&server_url, "%s&%s", server_url, timestamp);
-    else
-      asprintf (&server_url, "%s?%s", server_url, timestamp);
-  }
-
   if (display_html == TRUE)
   if (display_html == TRUE)
-    printf ("<A HREF=\"%s://%s:%d%s\" target=\"_blank\">", 
-      use_ssl ? "https" : "http", host_name,
+    printf ("<A HREF=\"%s://%s:%d%s\" target=\"_blank\">",
+      use_ssl ? "https" : "http", host_name ? host_name : server_address,
       server_port, server_url);
 
   /* initialize alarm signal handling, set socket timeout, start timer */
       server_port, server_url);
 
   /* initialize alarm signal handling, set socket timeout, start timer */
@@ -157,19 +176,29 @@ int
 process_arguments (int argc, char **argv)
 {
   int c = 1;
 process_arguments (int argc, char **argv)
 {
   int c = 1;
+  char *p;
+
+  enum {
+    INVERT_REGEX = CHAR_MAX + 1,
+    SNI_OPTION
+  };
 
   int option = 0;
   static struct option longopts[] = {
     STD_LONG_OPTS,
 
   int option = 0;
   static struct option longopts[] = {
     STD_LONG_OPTS,
-    {"file",required_argument,0,'F'},
     {"link", no_argument, 0, 'L'},
     {"nohtml", no_argument, 0, 'n'},
     {"ssl", no_argument, 0, 'S'},
     {"link", no_argument, 0, 'L'},
     {"nohtml", no_argument, 0, 'n'},
     {"ssl", no_argument, 0, 'S'},
-    {"verbose", no_argument, 0, 'v'},
+    {"sni", no_argument, 0, SNI_OPTION},
     {"post", required_argument, 0, 'P'},
     {"post", required_argument, 0, 'P'},
+    {"method", required_argument, 0, 'j'},
     {"IP-address", required_argument, 0, 'I'},
     {"url", required_argument, 0, 'u'},
     {"IP-address", required_argument, 0, 'I'},
     {"url", required_argument, 0, 'u'},
+    {"port", required_argument, 0, 'p'},
+    {"authorization", required_argument, 0, 'a'},
+    {"proxy_authorization", required_argument, 0, 'b'},
     {"string", required_argument, 0, 's'},
     {"string", required_argument, 0, 's'},
+    {"expect", required_argument, 0, 'e'},
     {"regex", required_argument, 0, 'r'},
     {"ereg", required_argument, 0, 'r'},
     {"eregi", required_argument, 0, 'R'},
     {"regex", required_argument, 0, 'r'},
     {"ereg", required_argument, 0, 'r'},
     {"eregi", required_argument, 0, 'R'},
@@ -182,6 +211,7 @@ process_arguments (int argc, char **argv)
     {"max-age", required_argument, 0, 'M'},
     {"content-type", required_argument, 0, 'T'},
     {"pagesize", required_argument, 0, 'm'},
     {"max-age", required_argument, 0, 'M'},
     {"content-type", required_argument, 0, 'T'},
     {"pagesize", required_argument, 0, 'm'},
+    {"invert-regex", no_argument, NULL, INVERT_REGEX},
     {"use-ipv4", no_argument, 0, '4'},
     {"use-ipv6", no_argument, 0, '6'},
     {0, 0, 0, 0}
     {"use-ipv4", no_argument, 0, '4'},
     {"use-ipv6", no_argument, 0, '6'},
     {0, 0, 0, 0}
@@ -204,20 +234,20 @@ process_arguments (int argc, char **argv)
   }
 
   while (1) {
   }
 
   while (1) {
-    c = getopt_long (argc, argv, "Vvh46t:c:w:A:k:H:P:T:I:a:e:p:s:R:r:u:f:C:nlLSm:M:N", longopts, &option);
+    c = getopt_long (argc, argv, "Vvh46t:c:w:A:k:H:P:j:T:I:a:b:e:p:s:R:r:u:f:C:nlLSm:M:N", longopts, &option);
     if (c == -1 || c == EOF)
       break;
 
     switch (c) {
     case '?': /* usage */
     if (c == -1 || c == EOF)
       break;
 
     switch (c) {
     case '?': /* usage */
-      usage2 (_("Unknown argument"), optarg);
+      usage5 ();
       break;
     case 'h': /* help */
       print_help ();
       exit (STATE_OK);
       break;
     case 'V': /* version */
       break;
     case 'h': /* help */
       print_help ();
       exit (STATE_OK);
       break;
     case 'V': /* version */
-      print_revision (progname, revision);
+      print_revision (progname, NP_VERSION);
       exit (STATE_OK);
       break;
     case 't': /* timeout period */
       exit (STATE_OK);
       break;
     case 't': /* timeout period */
@@ -251,7 +281,7 @@ process_arguments (int argc, char **argv)
       else
         http_opt_headers = realloc (http_opt_headers, sizeof (char *) * (++http_opt_headers_count));
       http_opt_headers[http_opt_headers_count - 1] = optarg;
       else
         http_opt_headers = realloc (http_opt_headers, sizeof (char *) * (++http_opt_headers_count));
       http_opt_headers[http_opt_headers_count - 1] = optarg;
-      //asprintf (&http_opt_headers, "%s", optarg);
+      /* asprintf (&http_opt_headers, "%s", optarg); */
       break;
     case 'L': /* show html link */
       display_html = TRUE;
       break;
     case 'L': /* show html link */
       display_html = TRUE;
@@ -277,25 +307,37 @@ process_arguments (int argc, char **argv)
       if (specify_port == FALSE)
         server_port = HTTPS_PORT;
       break;
       if (specify_port == FALSE)
         server_port = HTTPS_PORT;
       break;
+    case SNI_OPTION:
+      use_sni = TRUE;
+      break;
     case 'f': /* onredirect */
     case 'f': /* onredirect */
-      if (!strcmp (optarg, "follow"))
-        onredirect = STATE_DEPENDENT;
-      if (!strcmp (optarg, "unknown"))
+      if (!strcmp (optarg, "stickyport"))
+        onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST|STICKY_PORT;
+      else if (!strcmp (optarg, "sticky"))
+        onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST;
+      else if (!strcmp (optarg, "follow"))
+        onredirect = STATE_DEPENDENT, followsticky = STICKY_NONE;
+      else if (!strcmp (optarg, "unknown"))
         onredirect = STATE_UNKNOWN;
         onredirect = STATE_UNKNOWN;
-      if (!strcmp (optarg, "ok"))
+      else if (!strcmp (optarg, "ok"))
         onredirect = STATE_OK;
         onredirect = STATE_OK;
-      if (!strcmp (optarg, "warning"))
+      else if (!strcmp (optarg, "warning"))
         onredirect = STATE_WARNING;
         onredirect = STATE_WARNING;
-      if (!strcmp (optarg, "critical"))
+      else if (!strcmp (optarg, "critical"))
         onredirect = STATE_CRITICAL;
         onredirect = STATE_CRITICAL;
+      else usage2 (_("Invalid onredirect option"), optarg);
       if (verbose)
       if (verbose)
-        printf(_("option f:%d \n"), onredirect);  
+        printf(_("option f:%d \n"), onredirect);
       break;
     /* Note: H, I, and u must be malloc'd or will fail on redirects */
     case 'H': /* Host Name (virtual host) */
       host_name = strdup (optarg);
       break;
     /* Note: H, I, and u must be malloc'd or will fail on redirects */
     case 'H': /* Host Name (virtual host) */
       host_name = strdup (optarg);
-      if (strstr (optarg, ":"))
-        sscanf (optarg, "%*[^:]:%d", &server_port);
+      if (host_name[0] == '[') {
+        if ((p = strstr (host_name, "]:")) != NULL) /* [IPv6]:port */
+          server_port = atoi (p + 2);
+      } else if ((p = strchr (host_name, ':')) != NULL
+                 && strchr (++p, ':') == NULL) /* IPv4:port or host:port */
+          server_port = atoi (p);
       break;
     case 'I': /* Server IP-address */
       server_address = strdup (optarg);
       break;
     case 'I': /* Server IP-address */
       server_address = strdup (optarg);
@@ -316,10 +358,20 @@ process_arguments (int argc, char **argv)
       strncpy (user_auth, optarg, MAX_INPUT_BUFFER - 1);
       user_auth[MAX_INPUT_BUFFER - 1] = 0;
       break;
       strncpy (user_auth, optarg, MAX_INPUT_BUFFER - 1);
       user_auth[MAX_INPUT_BUFFER - 1] = 0;
       break;
-    case 'P': /* HTTP POST data in URL encoded format */
-      if (http_method || http_post_data) break;
-      http_method = strdup("POST");
-      http_post_data = strdup (optarg);
+    case 'b': /* proxy-authorization info */
+      strncpy (proxy_auth, optarg, MAX_INPUT_BUFFER - 1);
+      proxy_auth[MAX_INPUT_BUFFER - 1] = 0;
+      break;
+    case 'P': /* HTTP POST data in URL encoded format; ignored if settings already */
+      if (! http_post_data)
+        http_post_data = strdup (optarg);
+      if (! http_method)
+        http_method = strdup("POST");
+      break;
+    case 'j': /* Set HTTP method */
+      if (http_method)
+        free(http_method);
+      http_method = strdup (optarg);
       break;
     case 's': /* string or substring */
       strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1);
       break;
     case 's': /* string or substring */
       strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1);
@@ -333,13 +385,6 @@ process_arguments (int argc, char **argv)
     case 'T': /* Content-type */
       asprintf (&http_content_type, "%s", optarg);
       break;
     case 'T': /* Content-type */
       asprintf (&http_content_type, "%s", optarg);
       break;
-#ifndef HAVE_REGEX_H
-    case 'l': /* linespan */
-    case 'r': /* linespan */
-    case 'R': /* linespan */
-      usage4 (_("Call for regex which was not a compiled option"));
-      break;
-#else
     case 'l': /* linespan */
       cflags &= ~REG_NEWLINE;
       break;
     case 'l': /* linespan */
       cflags &= ~REG_NEWLINE;
       break;
@@ -355,7 +400,9 @@ process_arguments (int argc, char **argv)
         return ERROR;
       }
       break;
         return ERROR;
       }
       break;
-#endif
+    case INVERT_REGEX:
+      invert_regex = 1;
+      break;
     case '4':
       address_family = AF_INET;
       break;
     case '4':
       address_family = AF_INET;
       break;
@@ -387,7 +434,7 @@ process_arguments (int argc, char **argv)
           exit (STATE_WARNING);
         } else
           max_page_len = atoi(tmp);
           exit (STATE_WARNING);
         } else
           max_page_len = atoi(tmp);
-      } else 
+      } else
         min_page_len = atoi (optarg);
       break;
       }
         min_page_len = atoi (optarg);
       break;
       }
@@ -441,49 +488,6 @@ process_arguments (int argc, char **argv)
 
 
 
 
 
 
-/* written by lauri alanko */
-static char *
-base64 (const char *bin, size_t len)
-{
-
-  char *buf = (char *) malloc ((len + 2) / 3 * 4 + 1);
-  size_t i = 0, j = 0;
-
-  char BASE64_END = '=';
-  char base64_table[64];
-  strncpy (base64_table, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 64);
-
-  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;
-}
-
-
-
 /* Returns 1 if we're done processing the document body; 0 to keep going */
 static int
 document_headers_done (char *full_page)
 /* Returns 1 if we're done processing the document body; 0 to keep going */
 static int
 document_headers_done (char *full_page)
@@ -569,7 +573,7 @@ parse_time_string (const char *string)
     if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
       return 0;
 
     if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
       return 0;
 
-    /* 
+    /*
     This is actually wrong: we need to subtract the local timezone
     offset from GMT from this value.  But, that's ok in this usage,
     because we only comparing these two GMT dates against each other,
     This is actually wrong: we need to subtract the local timezone
     offset from GMT from this value.  But, that's ok in this usage,
     because we only comparing these two GMT dates against each other,
@@ -593,14 +597,33 @@ parse_time_string (const char *string)
   }
 }
 
   }
 }
 
+/* Checks if the server 'reply' is one of the expected 'statuscodes' */
+static int
+expected_statuscode (const char *reply, const char *statuscodes)
+{
+  char *expected, *code;
+  int result = 0;
+
+  if ((expected = strdup (statuscodes)) == NULL)
+    die (STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
+
+  for (code = strtok (expected, ","); code != NULL; code = strtok (NULL, ","))
+    if (strstr (reply, code) != NULL) {
+      result = 1;
+      break;
+    }
 
 
+  free (expected);
+  return result;
+}
 
 
-static void
-check_document_dates (const char *headers)
+static int
+check_document_dates (const char *headers, char **msg)
 {
   const char *s;
   char *server_date = 0;
   char *document_date = 0;
 {
   const char *s;
   char *server_date = 0;
   char *document_date = 0;
+  int date_result = STATE_OK;
 
   s = headers;
   while (*s) {
 
   s = headers;
   while (*s) {
@@ -618,7 +641,10 @@ check_document_dates (const char *headers)
     /* Skip to the end of the header, including continuation lines. */
     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
       s++;
     /* Skip to the end of the header, including continuation lines. */
     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
       s++;
-    s++;
+
+    /* Avoid stepping over end-of-string marker */
+    if (*s)
+      s++;
 
     /* Process this header. */
     if (value && value > field+2) {
 
     /* Process this header. */
     if (value && value > field+2) {
@@ -651,34 +677,38 @@ check_document_dates (const char *headers)
 
   /* Done parsing the body.  Now check the dates we (hopefully) parsed.  */
   if (!server_date || !*server_date) {
 
   /* Done parsing the body.  Now check the dates we (hopefully) parsed.  */
   if (!server_date || !*server_date) {
-    die (STATE_UNKNOWN, _("Server date unknown\n"));
+    asprintf (msg, _("%sServer date unknown, "), *msg);
+    date_result = max_state_alt(STATE_UNKNOWN, date_result);
   } else if (!document_date || !*document_date) {
   } else if (!document_date || !*document_date) {
-    die (STATE_CRITICAL, _("Document modification date unknown\n"));
+    asprintf (msg, _("%sDocument modification date unknown, "), *msg);
+    date_result = max_state_alt(STATE_CRITICAL, date_result);
   } else {
     time_t srv_data = parse_time_string (server_date);
     time_t doc_data = parse_time_string (document_date);
 
     if (srv_data <= 0) {
   } else {
     time_t srv_data = parse_time_string (server_date);
     time_t doc_data = parse_time_string (document_date);
 
     if (srv_data <= 0) {
-      die (STATE_CRITICAL, _("CRITICAL - Server date \"%100s\" unparsable"), server_date);
+      asprintf (msg, _("%sServer date \"%100s\" unparsable, "), *msg, server_date);
+      date_result = max_state_alt(STATE_CRITICAL, date_result);
     } else if (doc_data <= 0) {
     } else if (doc_data <= 0) {
-      die (STATE_CRITICAL, _("CRITICAL - Document date \"%100s\" unparsable"), document_date);
+      asprintf (msg, _("%sDocument date \"%100s\" unparsable, "), *msg, document_date);
+      date_result = max_state_alt(STATE_CRITICAL, date_result);
     } else if (doc_data > srv_data + 30) {
     } else if (doc_data > srv_data + 30) {
-      die (STATE_CRITICAL, _("CRITICAL - Document is %d seconds in the future\n"), (int)doc_data - (int)srv_data);
+      asprintf (msg, _("%sDocument is %d seconds in the future, "), *msg, (int)doc_data - (int)srv_data);
+      date_result = max_state_alt(STATE_CRITICAL, date_result);
     } else if (doc_data < srv_data - maximum_age) {
     } else if (doc_data < srv_data - maximum_age) {
-    int n = (srv_data - doc_data);
-    if (n > (60 * 60 * 24 * 2))
-      die (STATE_CRITICAL,
-        _("CRITICAL - Last modified %.1f days ago\n"),
-        ((float) n) / (60 * 60 * 24));
-  else
-    die (STATE_CRITICAL,
-        _("CRITICAL - Last modified %d:%02d:%02d ago\n"),
-        n / (60 * 60), (n / 60) % 60, n % 60);
+      int n = (srv_data - doc_data);
+      if (n > (60 * 60 * 24 * 2)) {
+        asprintf (msg, _("%sLast modified %.1f days ago, "), *msg, ((float) n) / (60 * 60 * 24));
+        date_result = max_state_alt(STATE_CRITICAL, date_result);
+      } else {
+        asprintf (msg, _("%sLast modified %d:%02d:%02d ago, "), *msg, n / (60 * 60), (n / 60) % 60, n % 60);
+        date_result = max_state_alt(STATE_CRITICAL, date_result);
+      }
     }
     }
-
     free (server_date);
     free (document_date);
   }
     free (server_date);
     free (document_date);
   }
+  return date_result;
 }
 
 int
 }
 
 int
@@ -703,7 +733,10 @@ get_content_length (const char *headers)
     /* Skip to the end of the header, including continuation lines. */
     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
       s++;
     /* Skip to the end of the header, including continuation lines. */
     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
       s++;
-    s++;
+
+    /* Avoid stepping over end-of-string marker */
+    if (*s)
+      s++;
 
     /* Process this header. */
     if (value && value > field+2) {
 
     /* Process this header. */
     if (value && value > field+2) {
@@ -731,6 +764,22 @@ get_content_length (const char *headers)
   return (content_length);
 }
 
   return (content_length);
 }
 
+char *
+prepend_slash (char *path)
+{
+  char *newpath;
+
+  if (path[0] == '/')
+    return path;
+
+  if ((newpath = malloc (strlen(path) + 2)) == NULL)
+    die (STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
+  newpath[0] = '/';
+  strcpy (newpath + 1, path);
+  free (path);
+  return newpath;
+}
+
 int
 check_http (void)
 {
 int
 check_http (void)
 {
@@ -744,33 +793,47 @@ check_http (void)
   int i = 0;
   size_t pagesize = 0;
   char *full_page;
   int i = 0;
   size_t pagesize = 0;
   char *full_page;
+  char *full_page_new;
   char *buf;
   char *pos;
   long microsec;
   double elapsed_time;
   int page_len = 0;
   char *buf;
   char *pos;
   long microsec;
   double elapsed_time;
   int page_len = 0;
-  int result = STATE_UNKNOWN;
+  int result = STATE_OK;
 
   /* try to connect to the host at the given port number */
   if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
 
   /* try to connect to the host at the given port number */
   if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
-    die (STATE_CRITICAL, _("Unable to open TCP socket\n"));
+    die (STATE_CRITICAL, _("HTTP CRITICAL - Unable to open TCP socket\n"));
 #ifdef HAVE_SSL
   if (use_ssl == TRUE) {
 #ifdef HAVE_SSL
   if (use_ssl == TRUE) {
-    np_net_ssl_init(sd);
+    np_net_ssl_init_with_hostname(sd, (use_sni ? host_name : NULL));
     if (check_cert == TRUE) {
       result = np_net_ssl_check_cert(days_till_exp);
       np_net_ssl_cleanup();
     if (check_cert == TRUE) {
       result = np_net_ssl_check_cert(days_till_exp);
       np_net_ssl_cleanup();
-      if(sd) close(sd);
+      if (sd) close(sd);
       return result;
     }
   }
 #endif /* HAVE_SSL */
 
       return result;
     }
   }
 #endif /* HAVE_SSL */
 
-  asprintf (&buf, "%s %s HTTP/1.0\r\n%s\r\n", http_method, server_url, user_agent);
+  asprintf (&buf, "%s %s %s\r\n%s\r\n", http_method, server_url, host_name ? "HTTP/1.1" : "HTTP/1.0", user_agent);
+
+  /* tell HTTP/1.1 servers not to keep the connection alive */
+  asprintf (&buf, "%sConnection: close\r\n", buf);
 
   /* optionally send the host header info */
 
   /* optionally send the host header info */
-  if (host_name)
-    asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
+  if (host_name) {
+    /*
+     * Specify the port only if we're using a non-default port (see RFC 2616,
+     * 14.23).  Some server applications/configurations cause trouble if the
+     * (default) port is explicitly specified in the "Host:" header line.
+     */
+    if ((use_ssl == FALSE && server_port == HTTP_PORT) ||
+        (use_ssl == TRUE && server_port == HTTPS_PORT))
+      asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
+    else
+      asprintf (&buf, "%sHost: %s:%d\r\n", buf, host_name, server_port);
+  }
 
   /* optionally send any other header tag */
   if (http_opt_headers_count) {
 
   /* optionally send any other header tag */
   if (http_opt_headers_count) {
@@ -778,23 +841,31 @@ check_http (void)
       for ((pos = strtok(http_opt_headers[i], INPUT_DELIMITER)); pos; (pos = strtok(NULL, INPUT_DELIMITER)))
         asprintf (&buf, "%s%s\r\n", buf, pos);
     }
       for ((pos = strtok(http_opt_headers[i], INPUT_DELIMITER)); pos; (pos = strtok(NULL, INPUT_DELIMITER)))
         asprintf (&buf, "%s%s\r\n", buf, pos);
     }
-    free(http_opt_headers);
+    /* This cannot be free'd here because a redirection will then try to access this and segfault */
+    /* Covered in a testcase in tests/check_http.t */
+    /* free(http_opt_headers); */
   }
 
   /* optionally send the authentication info */
   if (strlen(user_auth)) {
   }
 
   /* optionally send the authentication info */
   if (strlen(user_auth)) {
-    auth = base64 (user_auth, strlen (user_auth));
+    base64_encode_alloc (user_auth, strlen (user_auth), &auth);
     asprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
   }
 
     asprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
   }
 
-  /* either send http POST data */
+  /* optionally send the proxy authentication info */
+  if (strlen(proxy_auth)) {
+    base64_encode_alloc (proxy_auth, strlen (proxy_auth), &auth);
+    asprintf (&buf, "%sProxy-Authorization: Basic %s\r\n", buf, auth);
+  }
+
+  /* either send http POST data (any data, not only POST)*/
   if (http_post_data) {
     if (http_content_type) {
       asprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
     } else {
       asprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
     }
   if (http_post_data) {
     if (http_content_type) {
       asprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
     } else {
       asprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
     }
-    
+
     asprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
     asprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
   }
     asprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
     asprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
   }
@@ -810,7 +881,9 @@ check_http (void)
   full_page = strdup("");
   while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
     buffer[i] = '\0';
   full_page = strdup("");
   while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
     buffer[i] = '\0';
-    asprintf (&full_page, "%s%s", full_page, buffer);
+    asprintf (&full_page_new, "%s%s", full_page, buffer);
+    free (full_page);
+    full_page = full_page_new;
     pagesize += i;
 
                 if (no_body && document_headers_done (full_page)) {
     pagesize += i;
 
                 if (no_body && document_headers_done (full_page)) {
@@ -825,15 +898,15 @@ check_http (void)
     if (use_ssl) {
       sslerr=SSL_get_error(ssl, i);
       if ( sslerr == SSL_ERROR_SSL ) {
     if (use_ssl) {
       sslerr=SSL_get_error(ssl, i);
       if ( sslerr == SSL_ERROR_SSL ) {
-        die (STATE_WARNING, _("Client Certificate Required\n"));
+        die (STATE_WARNING, _("HTTP WARNING - Client Certificate Required\n"));
       } else {
       } else {
-        die (STATE_CRITICAL, _("Error on receive\n"));
+        die (STATE_CRITICAL, _("HTTP CRITICAL - Error on receive\n"));
       }
     }
     else {
     */
 #endif
       }
     }
     else {
     */
 #endif
-      die (STATE_CRITICAL, _("Error on receive\n"));
+      die (STATE_CRITICAL, _("HTTP CRITICAL - Error on receive\n"));
 #ifdef HAVE_SSL
       /* XXX
     }
 #ifdef HAVE_SSL
       /* XXX
     }
@@ -843,16 +916,17 @@ check_http (void)
 
   /* return a CRITICAL status if we couldn't read any data */
   if (pagesize == (size_t) 0)
 
   /* return a CRITICAL status if we couldn't read any data */
   if (pagesize == (size_t) 0)
-    die (STATE_CRITICAL, _("No data received %s\n"), timestamp);
+    die (STATE_CRITICAL, _("HTTP CRITICAL - No data received from host\n"));
 
   /* close the connection */
 #ifdef HAVE_SSL
   np_net_ssl_cleanup();
 #endif
 
   /* close the connection */
 #ifdef HAVE_SSL
   np_net_ssl_cleanup();
 #endif
-  if(sd) close(sd);
+  if (sd) close(sd);
 
 
-  /* reset the alarm */
-  alarm (0);
+  /* Save check time */
+  microsec = deltime (tv);
+  elapsed_time = (double)microsec / 1.0e6;
 
   /* leave full_page untouched so we can free it later */
   page = full_page;
 
   /* leave full_page untouched so we can free it later */
   page = full_page;
@@ -890,22 +964,23 @@ check_http (void)
                 (no_body ? "  [[ skipped ]]" : page));
 
   /* make sure the status line matches the response we are looking for */
                 (no_body ? "  [[ skipped ]]" : page));
 
   /* make sure the status line matches the response we are looking for */
-  if (!strstr (status_line, server_expect)) {
+  if (!expected_statuscode (status_line, server_expect)) {
     if (server_port == HTTP_PORT)
       asprintf (&msg,
     if (server_port == HTTP_PORT)
       asprintf (&msg,
-                _("Invalid HTTP response received from host\n"));
+                _("Invalid HTTP response received from host: %s\n"),
+                status_line);
     else
       asprintf (&msg,
     else
       asprintf (&msg,
-                _("Invalid HTTP response received from host on port %d\n"),
-                server_port);
-    die (STATE_CRITICAL, "%s", msg);
+                _("Invalid HTTP response received from host on port %d: %s\n"),
+                server_port, status_line);
+    die (STATE_CRITICAL, "HTTP CRITICAL - %s", msg);
   }
 
   }
 
-  /* Exit here if server_expect was set by user and not default */
+  /* Bypass normal status line check if server_expect was set by user and not default */
+  /* NOTE: After this if/else block msg *MUST* be an asprintf-allocated string */
   if ( server_expect_yn  )  {
     asprintf (&msg,
   if ( server_expect_yn  )  {
     asprintf (&msg,
-              _("HTTP OK: Status line output matched \"%s\"\n"),
-              server_expect);
+              _("Status line output matched \"%s\" - "), server_expect);
     if (verbose)
       printf ("%s\n",msg);
   }
     if (verbose)
       printf ("%s\n",msg);
   }
@@ -922,135 +997,124 @@ check_http (void)
 
     /* check the return code */
 
 
     /* check the return code */
 
-    if (http_status >= 600 || http_status < 100)
+    if (http_status >= 600 || http_status < 100) {
       die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
       die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
-
+    }
     /* server errors result in a critical state */
     /* server errors result in a critical state */
-    else if (http_status >= 500)
-      die (STATE_CRITICAL, _("HTTP CRITICAL: %s\n"), status_line);
-
+    else if (http_status >= 500) {
+      asprintf (&msg, _("%s - "), status_line);
+      result = STATE_CRITICAL;
+    }
     /* client errors result in a warning state */
     /* client errors result in a warning state */
-    else if (http_status >= 400)
-      die (STATE_WARNING, _("HTTP WARNING: %s\n"), status_line);
-
+    else if (http_status >= 400) {
+      asprintf (&msg, _("%s - "), status_line);
+      result = max_state_alt(STATE_WARNING, result);
+    }
     /* check redirected page if specified */
     else if (http_status >= 300) {
 
       if (onredirect == STATE_DEPENDENT)
         redir (header, status_line);
     /* check redirected page if specified */
     else if (http_status >= 300) {
 
       if (onredirect == STATE_DEPENDENT)
         redir (header, status_line);
-      else if (onredirect == STATE_UNKNOWN)
-        printf (_("UNKNOWN"));
-      else if (onredirect == STATE_OK)
-        printf (_("OK"));
-      else if (onredirect == STATE_WARNING)
-        printf (_("WARNING"));
-      else if (onredirect == STATE_CRITICAL)
-        printf (_("CRITICAL"));
-      microsec = deltime (tv);
-      elapsed_time = (double)microsec / 1.0e6;
-      die (onredirect,
-           _(" - %s - %.3f second response time %s%s|%s %s\n"),
-           status_line, elapsed_time, timestamp,
-           (display_html ? "</A>" : ""),
-           perfd_time (elapsed_time), perfd_size (pagesize));
+      else
+        result = max_state_alt(onredirect, result);
+      asprintf (&msg, _("%s - "), status_line);
     } /* end if (http_status >= 300) */
     } /* end if (http_status >= 300) */
+    else {
+      /* Print OK status anyway */
+      asprintf (&msg, _("%s - "), status_line);
+    }
 
   } /* end else (server_expect_yn)  */
 
   } /* end else (server_expect_yn)  */
-    
-        if (maximum_age >= 0) {
-          check_document_dates (header);
-        }
 
 
-  /* check elapsed time */
-  microsec = deltime (tv);
-  elapsed_time = (double)microsec / 1.0e6;
-  asprintf (&msg,
-            _("HTTP WARNING: %s - %.3f second response time %s%s|%s %s\n"),
-            status_line, elapsed_time, timestamp,
-            (display_html ? "</A>" : ""),
-            perfd_time (elapsed_time), perfd_size (pagesize));
-  if (check_critical_time == TRUE && elapsed_time > critical_time)
-    die (STATE_CRITICAL, "%s", msg);
-  if (check_warning_time == TRUE && elapsed_time > warning_time)
-    die (STATE_WARNING, "%s", msg);
+  /* reset the alarm - must be called *after* redir or we'll never die on redirects! */
+  alarm (0);
+
+  if (maximum_age >= 0) {
+    result = max_state_alt(check_document_dates(header, &msg), result);
+  }
 
   /* Page and Header content checks go here */
 
   /* Page and Header content checks go here */
-  /* these checks should be last */
 
   if (strlen (string_expect)) {
 
   if (strlen (string_expect)) {
-    if (strstr (page, string_expect)) {
-      printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
-              status_line, elapsed_time,
-              timestamp, (display_html ? "</A>" : ""),
-              perfd_time (elapsed_time), perfd_size (pagesize));
-      exit (STATE_OK);
-    }
-    else {
-      printf (_("CRITICAL - string not found%s|%s %s\n"),
-              (display_html ? "</A>" : ""),
-              perfd_time (elapsed_time), perfd_size (pagesize));
-      exit (STATE_CRITICAL);
+    if (!strstr (page, string_expect)) {
+      asprintf (&msg, _("%sstring not found, "), msg);
+      result = STATE_CRITICAL;
     }
   }
     }
   }
-#ifdef HAVE_REGEX_H
+
   if (strlen (regexp)) {
     errcode = regexec (&preg, page, REGS, pmatch, 0);
   if (strlen (regexp)) {
     errcode = regexec (&preg, page, REGS, pmatch, 0);
-    if (errcode == 0) {
-      printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
-              status_line, elapsed_time,
-              timestamp, (display_html ? "</A>" : ""),
-              perfd_time (elapsed_time), perfd_size (pagesize));
-      exit (STATE_OK);
+    if ((errcode == 0 && invert_regex == 0) || (errcode == REG_NOMATCH && invert_regex == 1)) {
+      /* OK - No-op to avoid changing the logic around it */
+      result = max_state_alt(STATE_OK, result);
+    }
+    else if ((errcode == REG_NOMATCH && invert_regex == 0) || (errcode == 0 && invert_regex == 1)) {
+      if (invert_regex == 0)
+        asprintf (&msg, _("%spattern not found, "), msg);
+      else
+        asprintf (&msg, _("%spattern found, "), msg);
+      result = STATE_CRITICAL;
     }
     else {
     }
     else {
-      if (errcode == REG_NOMATCH) {
-        printf (_("CRITICAL - pattern not found%s|%s %s\n"),
-                (display_html ? "</A>" : ""),
-                perfd_time (elapsed_time), perfd_size (pagesize));
-        exit (STATE_CRITICAL);
-      }
-      else {
-        regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
-        printf (_("CRITICAL - Execute Error: %s\n"), errbuf);
-        exit (STATE_CRITICAL);
-      }
+      /* FIXME: Shouldn't that be UNKNOWN? */
+      regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
+      asprintf (&msg, _("%sExecute Error: %s, "), msg, errbuf);
+      result = STATE_CRITICAL;
     }
   }
     }
   }
-#endif
 
   /* make sure the page is of an appropriate size */
   /* page_len = get_content_length(header); */
 
   /* make sure the page is of an appropriate size */
   /* page_len = get_content_length(header); */
+  /* FIXME: Will this work with -N ? IMHO we should use
+   * get_content_length(header) and always check if it's different than the
+   * returned pagesize
+   */
+  /* FIXME: IIRC pagesize returns headers - shouldn't we make
+   * it == get_content_length(header) ??
+   */
   page_len = pagesize;
   if ((max_page_len > 0) && (page_len > max_page_len)) {
   page_len = pagesize;
   if ((max_page_len > 0) && (page_len > max_page_len)) {
-    printf (_("HTTP WARNING: page size %d too large%s|%s\n"),
-      page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
-    exit (STATE_WARNING);
+    asprintf (&msg, _("%spage size %d too large, "), msg, page_len);
+    result = max_state_alt(STATE_WARNING, result);
   } else if ((min_page_len > 0) && (page_len < min_page_len)) {
   } else if ((min_page_len > 0) && (page_len < min_page_len)) {
-    printf (_("HTTP WARNING: page size %d too small%s|%s\n"),
-      page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
-    exit (STATE_WARNING);
+    asprintf (&msg, _("%spage size %d too small, "), msg, page_len);
+    result = max_state_alt(STATE_WARNING, result);
   }
   }
-  /* We only get here if all tests have been passed */
-  asprintf (&msg, _("HTTP OK %s - %d bytes in %.3f seconds %s%s|%s %s\n"),
-            status_line, page_len, elapsed_time,
-            timestamp, (display_html ? "</A>" : ""),
+
+  /* Cut-off trailing characters */
+  if(msg[strlen(msg)-2] == ',')
+    msg[strlen(msg)-2] = '\0';
+  else
+    msg[strlen(msg)-3] = '\0';
+
+  /* check elapsed time */
+  asprintf (&msg,
+            _("%s - %d bytes in %.3f second response time %s|%s %s"),
+            msg, page_len, elapsed_time,
+            (display_html ? "</A>" : ""),
             perfd_time (elapsed_time), perfd_size (page_len));
             perfd_time (elapsed_time), perfd_size (page_len));
-  die (STATE_OK, "%s", msg);
+
+  if (check_critical_time == TRUE && elapsed_time > critical_time)
+    result = STATE_CRITICAL;
+  if (check_warning_time == TRUE && elapsed_time > warning_time)
+    result =  max_state_alt(STATE_WARNING, result);
+
+  die (result, "HTTP %s: %s\n", state_text(result), msg);
+  /* die failed? */
   return STATE_UNKNOWN;
 }
 
 
 
 /* per RFC 2396 */
   return STATE_UNKNOWN;
 }
 
 
 
 /* per RFC 2396 */
-#define HDR_LOCATION "%*[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]: "
-#define URI_HTTP "%[HTPShtps]://"
-#define URI_HOST "%[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
-#define URI_PORT ":%[0123456789]"
+#define URI_HTTP "%5[HTPShtps]"
+#define URI_HOST "%255[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
+#define URI_PORT "%6d" /* MAX_PORT's width is 5 chars, 6 to detect overflow */
 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
-#define HD1 URI_HTTP URI_HOST URI_PORT URI_PATH
-#define HD2 URI_HTTP URI_HOST URI_PATH
-#define HD3 URI_HTTP URI_HOST URI_PORT
-#define HD4 URI_HTTP URI_HOST
+#define HD1 URI_HTTP "://" URI_HOST ":" URI_PORT "/" URI_PATH
+#define HD2 URI_HTTP "://" URI_HOST "/" URI_PATH
+#define HD3 URI_HTTP "://" URI_HOST ":" URI_PORT
+#define HD4 URI_HTTP "://" URI_HOST
 #define HD5 URI_PATH
 
 void
 #define HD5 URI_PATH
 
 void
@@ -1061,58 +1125,68 @@ redir (char *pos, char *status_line)
   char xx[2];
   char type[6];
   char *addr;
   char xx[2];
   char type[6];
   char *addr;
-  char port[6];
   char *url;
 
   addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
   if (addr == NULL)
   char *url;
 
   addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
   if (addr == NULL)
-    die (STATE_UNKNOWN, _("Could not allocate addr\n"));
-  
+    die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate addr\n"));
+
   url = malloc (strcspn (pos, "\r\n"));
   if (url == NULL)
   url = malloc (strcspn (pos, "\r\n"));
   if (url == NULL)
-    die (STATE_UNKNOWN, _("Could not allocate url\n"));
+    die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n"));
 
   while (pos) {
 
   while (pos) {
-
-    if (sscanf (pos, "%[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]:%n", xx, &i) < 1) {
-
+    sscanf (pos, "%1[Ll]%*1[Oo]%*1[Cc]%*1[Aa]%*1[Tt]%*1[Ii]%*1[Oo]%*1[Nn]:%n", xx, &i);
+    if (i == 0) {
       pos += (size_t) strcspn (pos, "\r\n");
       pos += (size_t) strspn (pos, "\r\n");
       pos += (size_t) strcspn (pos, "\r\n");
       pos += (size_t) strspn (pos, "\r\n");
-      if (strlen(pos) == 0) 
+      if (strlen(pos) == 0)
         die (STATE_UNKNOWN,
         die (STATE_UNKNOWN,
-             _("UNKNOWN - Could not find redirect location - %s%s\n"),
+             _("HTTP UNKNOWN - Could not find redirect location - %s%s\n"),
              status_line, (display_html ? "</A>" : ""));
       continue;
     }
 
     pos += i;
              status_line, (display_html ? "</A>" : ""));
       continue;
     }
 
     pos += i;
-    pos += strspn (pos, " \t\r\n");
+    pos += strspn (pos, " \t");
 
 
-    url = realloc (url, strcspn (pos, "\r\n"));
+    /*
+     * RFC 2616 (4.2):  ``Header fields can be extended over multiple lines by
+     * preceding each extra line with at least one SP or HT.''
+     */
+    for (; (i = strspn (pos, "\r\n")); pos += i) {
+      pos += i;
+      if (!(i = strspn (pos, " \t"))) {
+        die (STATE_UNKNOWN, _("HTTP UNKNOWN - Empty redirect location%s\n"),
+             display_html ? "</A>" : "");
+      }
+    }
+
+    url = realloc (url, strcspn (pos, "\r\n") + 1);
     if (url == NULL)
     if (url == NULL)
-      die (STATE_UNKNOWN, _("could not allocate url\n"));
+      die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n"));
 
     /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
 
     /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
-    if (sscanf (pos, HD1, type, addr, port, url) == 4) {
+    if (sscanf (pos, HD1, type, addr, &i, url) == 4) {
+      url = prepend_slash (url);
       use_ssl = server_type_check (type);
       use_ssl = server_type_check (type);
-      i = atoi (port);
     }
 
     /* URI_HTTP URI_HOST URI_PATH */
     }
 
     /* URI_HTTP URI_HOST URI_PATH */
-    else if (sscanf (pos, HD2, type, addr, url) == 3 ) { 
+    else if (sscanf (pos, HD2, type, addr, url) == 3 ) {
+      url = prepend_slash (url);
       use_ssl = server_type_check (type);
       i = server_port_check (use_ssl);
     }
 
     /* URI_HTTP URI_HOST URI_PORT */
       use_ssl = server_type_check (type);
       i = server_port_check (use_ssl);
     }
 
     /* URI_HTTP URI_HOST URI_PORT */
-    else if(sscanf (pos, HD3, type, addr, port) == 3) {
+    else if (sscanf (pos, HD3, type, addr, &i) == 3) {
       strcpy (url, HTTP_URL);
       use_ssl = server_type_check (type);
       strcpy (url, HTTP_URL);
       use_ssl = server_type_check (type);
-      i = atoi (port);
     }
 
     /* URI_HTTP URI_HOST */
     }
 
     /* URI_HTTP URI_HOST */
-    else if(sscanf (pos, HD4, type, addr) == 2) {
+    else if (sscanf (pos, HD4, type, addr) == 2) {
       strcpy (url, HTTP_URL);
       use_ssl = server_type_check (type);
       i = server_port_check (use_ssl);
       strcpy (url, HTTP_URL);
       use_ssl = server_type_check (type);
       i = server_port_check (use_ssl);
@@ -1128,12 +1202,12 @@ redir (char *pos, char *status_line)
       }
       i = server_port;
       strcpy (type, server_type);
       }
       i = server_port;
       strcpy (type, server_type);
-      strcpy (addr, host_name);
-    }           
+      strcpy (addr, host_name ? host_name : server_address);
+    }
 
     else {
       die (STATE_UNKNOWN,
 
     else {
       die (STATE_UNKNOWN,
-           _("UNKNOWN - Could not parse redirect location - %s%s\n"),
+           _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"),
            pos, (display_html ? "</A>" : ""));
     }
 
            pos, (display_html ? "</A>" : ""));
     }
 
@@ -1143,7 +1217,7 @@ redir (char *pos, char *status_line)
 
   if (++redir_depth > max_depth)
     die (STATE_WARNING,
 
   if (++redir_depth > max_depth)
     die (STATE_WARNING,
-         _("WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
+         _("HTTP WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
          max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
 
   if (server_port==i &&
          max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
 
   if (server_port==i &&
@@ -1151,26 +1225,39 @@ redir (char *pos, char *status_line)
       (host_name && !strcmp(host_name, addr)) &&
       !strcmp(server_url, url))
     die (STATE_WARNING,
       (host_name && !strcmp(host_name, addr)) &&
       !strcmp(server_url, url))
     die (STATE_WARNING,
-         _("WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
+         _("HTTP WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
          type, addr, i, url, (display_html ? "</A>" : ""));
 
          type, addr, i, url, (display_html ? "</A>" : ""));
 
-  server_port = i;
   strcpy (server_type, type);
 
   free (host_name);
   host_name = strdup (addr);
 
   strcpy (server_type, type);
 
   free (host_name);
   host_name = strdup (addr);
 
-  free (server_address);
-  server_address = strdup (addr);
+  if (!(followsticky & STICKY_HOST)) {
+    free (server_address);
+    server_address = strdup (addr);
+  }
+  if (!(followsticky & STICKY_PORT)) {
+    server_port = i;
+  }
 
   free (server_url);
 
   free (server_url);
-  server_url = strdup (url);
+  server_url = url;
+
+  if (server_port > MAX_PORT)
+    die (STATE_UNKNOWN,
+         _("HTTP UNKNOWN - Redirection to port above %d - %s://%s:%d%s%s\n"),
+         MAX_PORT, server_type, server_address, server_port, server_url,
+         display_html ? "</A>" : "");
+
+  if (verbose)
+    printf (_("Redirection to %s://%s:%d%s\n"), server_type,
+            host_name ? host_name : server_address, server_port, server_url);
 
   check_http ();
 }
 
 
 
   check_http ();
 }
 
 
-
 int
 server_type_check (const char *type)
 {
 int
 server_type_check (const char *type)
 {
@@ -1210,16 +1297,15 @@ char *perfd_size (int page_len)
 void
 print_help (void)
 {
 void
 print_help (void)
 {
-  print_revision (progname, revision);
+  print_revision (progname, NP_VERSION);
 
   printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
   printf (COPYRIGHT, copyright, email);
 
 
   printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
   printf (COPYRIGHT, copyright, email);
 
-  printf (_("\
-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."));
+  printf ("%s\n", _("This plugin tests the HTTP service on the specified host. It can test"));
+  printf ("%s\n", _("normal (http) and secure (https) servers, follow redirects, search for"));
+  printf ("%s\n", _("strings and regular expressions, check connection times, and report on"));
+  printf ("%s\n", _("certificate expiration times."));
 
   printf ("\n\n");
 
 
   printf ("\n\n");
 
@@ -1229,106 +1315,115 @@ certificate expiration times."));
 
   printf ("\n");
 
 
   printf ("\n");
 
-  printf (_(UT_HELP_VRSN));
+  printf (UT_HELP_VRSN);
+  printf (UT_EXTRA_OPTS);
 
 
-  printf (_("\
- -H, --hostname=ADDRESS\n\
-    Host name argument for servers using host headers (virtual host)\n\
-    Append a port to include it in the header (eg: example.com:5000)\n\
- -I, --IP-address=ADDRESS\n\
-   IP address or name (use numeric address if possible to bypass DNS lookup).\n\
- -p, --port=INTEGER\n\
-   Port number (default: %d)\n"), HTTP_PORT);
+  printf (" %s\n", "-H, --hostname=ADDRESS");
+  printf ("    %s\n", _("Host name argument for servers using host headers (virtual host)"));
+  printf ("    %s\n", _("Append a port to include it in the header (eg: example.com:5000)"));
+  printf (" %s\n", "-I, --IP-address=ADDRESS");
+  printf ("    %s\n", _("IP address or name (use numeric address if possible to bypass DNS lookup)."));
+  printf (" %s\n", "-p, --port=INTEGER");
+  printf ("    %s", _("Port number (default: "));
+  printf ("%d)\n", HTTP_PORT);
 
 
-  printf (_(UT_IPv46));
+  printf (UT_IPv46);
 
 #ifdef HAVE_SSL
 
 #ifdef HAVE_SSL
-  printf (_("\
- -S, --ssl\n\
-    Connect via SSL\n\
- -C, --certificate=INTEGER\n\
-    Minimum number of days a certificate has to be valid.\n\
-    (when this option is used the url is not checked.)\n"));
+  printf (" %s\n", "-S, --ssl");
+  printf ("   %s\n", _("Connect via SSL. Port defaults to 443"));
+  printf (" %s\n", "--sni");
+  printf ("   %s\n", _("Enable SSL/TLS hostname extension support (SNI)"));
+  printf (" %s\n", "-C, --certificate=INTEGER");
+  printf ("   %s\n", _("Minimum number of days a certificate has to be valid. Port defaults to 443"));
+  printf ("   %s\n", _("(when this option is used the URL is not checked.)\n"));
 #endif
 
 #endif
 
-  printf (_("\
- -e, --expect=STRING\n\
-   String to expect in first (status) line of server response (default: %s)\n\
-   If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)\n\
- -s, --string=STRING\n\
-   String to expect in the content\n\
- -u, --url=PATH\n\
-   URL to GET or POST (default: /)\n\
- -P, --post=STRING\n\
-   URL encoded http POST data\n\
- -N, --no-body\n\
-   Don't wait for document body: stop reading after headers.\n\
-   (Note that this still does an HTTP GET or POST, not a HEAD.)\n\
- -M, --max-age=SECONDS\n\
-   Warn if document is more than SECONDS old. the number can also be of \n\
-   the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days.\n\
- -T, --content-type=STRING\n\
-   specify Content-Type header media type when POSTing\n"), HTTP_EXPECT);
-
-#ifdef HAVE_REGEX_H
-  printf (_("\
- -l, --linespan\n\
-    Allow regex to span newlines (must precede -r or -R)\n\
- -r, --regex, --ereg=STRING\n\
-    Search page for regex STRING\n\
- -R, --eregi=STRING\n\
-    Search page for case-insensitive regex STRING\n"));
-#endif
+  printf (" %s\n", "-e, --expect=STRING");
+  printf ("    %s\n", _("Comma-delimited list of strings, at least one of them is expected in"));
+  printf ("    %s", _("the first (status) line of the server response (default: "));
+  printf ("%s)\n", HTTP_EXPECT);
+  printf ("    %s\n", _("If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)"));
+  printf (" %s\n", "-s, --string=STRING");
+  printf ("    %s\n", _("String to expect in the content"));
+  printf (" %s\n", "-u, --url=PATH");
+  printf ("    %s\n", _("URL to GET or POST (default: /)"));
+  printf (" %s\n", "-P, --post=STRING");
+  printf ("    %s\n", _("URL encoded http POST data"));
+  printf (" %s\n", "-j, --method=STRING  (for example: HEAD, OPTIONS, TRACE, PUT, DELETE)");
+  printf ("    %s\n", _("Set HTTP method."));
+  printf (" %s\n", "-N, --no-body");
+  printf ("    %s\n", _("Don't wait for document body: stop reading after headers."));
+  printf ("    %s\n", _("(Note that this still does an HTTP GET or POST, not a HEAD.)"));
+  printf (" %s\n", "-M, --max-age=SECONDS");
+  printf ("    %s\n", _("Warn if document is more than SECONDS old. the number can also be of"));
+  printf ("    %s\n", _("the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days."));
+  printf (" %s\n", "-T, --content-type=STRING");
+  printf ("    %s\n", _("specify Content-Type header media type when POSTing\n"));
+
+  printf (" %s\n", "-l, --linespan");
+  printf ("    %s\n", _("Allow regex to span newlines (must precede -r or -R)"));
+  printf (" %s\n", "-r, --regex, --ereg=STRING");
+  printf ("    %s\n", _("Search page for regex STRING"));
+  printf (" %s\n", "-R, --eregi=STRING");
+  printf ("    %s\n", _("Search page for case-insensitive regex STRING"));
+  printf (" %s\n", "--invert-regex");
+  printf ("    %s\n", _("Return CRITICAL if found, OK if not\n"));
+
+  printf (" %s\n", "-a, --authorization=AUTH_PAIR");
+  printf ("    %s\n", _("Username:password on sites with basic authentication"));
+  printf (" %s\n", "-b, --proxy-authorization=AUTH_PAIR");
+  printf ("    %s\n", _("Username:password on proxy-servers with basic authentication"));
+  printf (" %s\n", "-A, --useragent=STRING");
+  printf ("    %s\n", _("String to be sent in http header as \"User Agent\""));
+  printf (" %s\n", "-k, --header=STRING");
+  printf ("    %s\n", _(" Any other tags to be sent in http header. Use multiple times for additional headers"));
+  printf (" %s\n", "-L, --link");
+  printf ("    %s\n", _("Wrap output in HTML link (obsoleted by urlize)"));
+  printf (" %s\n", "-f, --onredirect=<ok|warning|critical|follow|sticky|stickyport>");
+  printf ("    %s\n", _("How to handle redirected pages. sticky is like follow but stick to the"));
+  printf ("    %s\n", _("specified IP address. stickyport also ensure post stays the same."));
+  printf (" %s\n", "-m, --pagesize=INTEGER<:INTEGER>");
+  printf ("    %s\n", _("Minimum page size required (bytes) : Maximum page size required (bytes)"));
+
+  printf (UT_WARN_CRIT);
+
+  printf (UT_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
+
+  printf (UT_VERBOSE);
 
 
-  printf (_("\
- -a, --authorization=AUTH_PAIR\n\
-   Username:password on sites with basic authentication\n\
- -A, --useragent=STRING\n\
-   String to be sent in http header as \"User Agent\"\n\
- -k, --header=STRING\n\
-   Any other tags to be sent in http header. Use multiple times for additional headers\n\
- -L, --link=URL\n\
-   Wrap output in HTML link (obsoleted by urlize)\n\
- -f, --onredirect=<ok|warning|critical|follow>\n\
-   How to handle redirected pages\n\
- -m, --pagesize=INTEGER<:INTEGER>\n\
-   Minimum page size required (bytes) : Maximum page size required (bytes)\n"));
-
-  printf (_(UT_WARN_CRIT));
-
-  printf (_(UT_TIMEOUT), DEFAULT_SOCKET_TIMEOUT);
-
-  printf (_(UT_VERBOSE));
-
-          printf (_("\
-This plugin will attempt to open an HTTP connection with the host. Successful\n\
-connects return STATE_OK, refusals and timeouts return STATE_CRITICAL, other\n\
-errors return STATE_UNKNOWN.  Successful connects, but incorrect reponse\n\
-messages from the host result in STATE_WARNING return values.  If you are\n\
-checking a virtual server that uses 'host headers' you must supply the FQDN\n\
-(fully qualified domain name) as the [host_name] argument.\n"));
+  printf ("\n");
+  printf ("%s\n", _("Notes:"));
+  printf (" %s\n", _("This plugin will attempt to open an HTTP connection with the host."));
+  printf (" %s\n", _("Successful connects return STATE_OK, refusals and timeouts return STATE_CRITICAL"));
+  printf (" %s\n", _("other errors return STATE_UNKNOWN.  Successful connects, but incorrect reponse"));
+  printf (" %s\n", _("messages from the host result in STATE_WARNING return values.  If you are"));
+  printf (" %s\n", _("checking a virtual server that uses 'host headers' you must supply the FQDN"));
+  printf (" %s\n", _("(fully qualified domain name) as the [host_name] argument."));
+  printf ("\n");
+  printf (UT_EXTRA_OPTS_NOTES);
 
 #ifdef HAVE_SSL
 
 #ifdef HAVE_SSL
-  printf (_("\n\
-This plugin can also check whether an SSL enabled web server is able to\n\
-serve content (optionally within a specified time) or whether the X509 \n\
-certificate is still valid for the specified number of days.\n"));
-  printf (_("\n\
-CHECK CONTENT: check_http -w 5 -c 10 --ssl www.verisign.com\n\n\
-When the 'www.verisign.com' server returns its content within 5 seconds, a\n\
-STATE_OK will be returned. When the server returns its content but exceeds\n\
-the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,\n\
-a STATE_CRITICAL will be returned.\n\n"));
-
-  printf (_("\
-CHECK CERTIFICATE: check_http www.verisign.com -C 14\n\n\
-When the certificate of 'www.verisign.com' is valid for more than 14 days, a\n\
-STATE_OK is returned. When the certificate is still valid, but for less than\n\
-14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when\n\
-the certificate is expired.\n"));
+  printf ("\n");
+  printf (" %s\n", _("This plugin can also check whether an SSL enabled web server is able to"));
+  printf (" %s\n", _("serve content (optionally within a specified time) or whether the X509 "));
+  printf (" %s\n", _("certificate is still valid for the specified number of days."));
+  printf ("\n");
+  printf ("%s\n", _("Examples:"));
+  printf (" %s\n\n", "CHECK CONTENT: check_http -w 5 -c 10 --ssl -H www.verisign.com");
+  printf (" %s\n", _("When the 'www.verisign.com' server returns its content within 5 seconds,"));
+  printf (" %s\n", _("a STATE_OK will be returned. When the server returns its content but exceeds"));
+  printf (" %s\n", _("the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,"));
+  printf (" %s\n\n", _("a STATE_CRITICAL will be returned."));
+
+  printf (" %s\n\n", "CHECK CERTIFICATE: check_http -H www.verisign.com -C 14");
+  printf (" %s\n", _("When the certificate of 'www.verisign.com' is valid for more than 14 days,"));
+  printf (" %s\n", _("a STATE_OK is returned. When the certificate is still valid, but for less than"));
+  printf (" %s\n", _("14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when"));
+  printf (" %s\n", _("the certificate is expired."));
 #endif
 
 #endif
 
-  printf (_(UT_SUPPORT));
+  printf (UT_SUPPORT);
 
 }
 
 
 }
 
@@ -1339,8 +1434,10 @@ print_usage (void)
 {
   printf (_("Usage:"));
   printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
 {
   printf (_("Usage:"));
   printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
-  printf ("       [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L]\n");
-  printf ("       [-a auth] [-f <ok | warn | critcal | follow>] [-e <expect>]\n");
-  printf ("       [-s string] [-l] [-r <regex> | -R <case-insensitive regex>] [-P string]\n");
-  printf ("       [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>] [-A string] [-k string]\n");
+  printf ("       [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L] [-a auth]\n");
+  printf ("       [-b proxy_auth] [-f <ok|warning|critcal|follow|sticky|stickyport>]\n");
+  printf ("       [-e <expect>] [-s string] [-l] [-r <regex> | -R <case-insensitive regex>]\n");
+  printf ("       [-P string] [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>]\n");
+  printf ("       [-A string] [-k string] [-S] [--sni] [-C <age>] [-T <content-type>]\n");
+  printf ("       [-j method]\n");
 }
 }