[Nagiosplug-devel] additions to check_http.c (--no-body, --max-age)

Jamie Zawinski jwz at jwz.org
Tue Apr 13 20:38:10 CEST 2004


Hi there,

I recently upgraded to Nagios 1.2.0 from an old Netsaint, and
I needed to add a couple of features to the check_http plugin.

I sent these patches in a couple of years ago, but they haven't
made it into the distribution, so here they are again...  (I
hope you include them this time, so that I don't have to rewrite
this code again the next time I upgrade...)


Anyway, I added two new options to check_http:

     -N, --no-body
         Don't wait for document body: stop reading after headers.

I needed this so that I can check for the existence of Icecast MP3
streams: such streams look like audio/mpeg files, except that they
are of infinite length.

     -M, --max-age=SECONDS
         Warn if the document is more than SECONDS old.

I needed this so that I can check whether my webcam is still working:
if the date on the JPEG file has stopped updating, the webcam is down.

Here's how I use these:

    define command{
        command_name    check_icecast
        command_line    $USER1$/check_http2 -H $HOSTADDRESS$ \
                -p 8000 --no-body -u $ARG1$
        }

    define command{
        command_name    check_http_age
        command_line    $USER1$/check_http -H $HOSTALIAS$ \
                -I $HOSTADDRESS$ --no-body -u $ARG1$ --max-age $ARG2$
        }

This diff is against check_http.c 1.24.2.4 from nagios-plugins-1.3.1.

Thanks!

-- 
Jamie Zawinski
jwz at jwz.org             http://www.jwz.org/
jwz at dnalounge.com       http://www.dnalounge.com/



--- /tmp/check_http.c	2004-04-13 20:09:38.000000000 -0700
+++ check_http.c	2004-04-13 20:25:40.000000000 -0700
@@ -21,6 +21,8 @@
  *
  * $Id: check_http.c,v 1.24.2.4 2003/06/21 05:31:23 kdebisschop Exp $
  *
+ *  13-Apr-2004 -- jwz -- added "--no-body" and "--max-age" options
+ *
  *****************************************************************************/
 
 const char *progname = "check_http";
@@ -45,7 +47,7 @@
             [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L]\n\
             [-a auth] [-f <ok | warn | critcal | follow>] [-e <expect>]\n\
             [-s string] [-l] [-r <regex> | -R <case-insensitive regex>]\n\
-            [-P string]"
+            [-P string] [-N] [-M <age>]"
 
 #define LONGOPTIONS "\
  -H, --hostname=ADDRESS\n\
@@ -63,6 +65,13 @@
    Port number (default: %d)\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 the document is more than SECONDS old.  The number can\n\
+    also be of the form \"10m\" for minutes, \"10h\" for hours, or\n\
+    \"10d\" for days.\n\
  -w, --warning=INTEGER\n\
    Response time to result in warning status (seconds)\n\
  -c, --critical=INTEGER\n\
@@ -159,6 +168,10 @@
 int check_certificate (X509 **);
 #endif
 
+/* jwz */
+int no_body = FALSE;
+int maximum_age = -1;
+
 #ifdef HAVE_REGEX_H
 enum {
 	REGS = 2,
@@ -311,6 +324,10 @@
  		{"linespan", no_argument, 0, 'l'},
 		{"onredirect", required_argument, 0, 'f'},
 		{"certificate", required_argument, 0, 'C'},
+
+                /* jwz */
+		{"no-body", no_argument, 0, 'N'},
+		{"max-age", required_argument, 0, 'M'},
 		{0, 0, 0, 0}
 	};
 #endif
@@ -331,7 +348,8 @@
 			strcpy (argv[c], "-n");
 	}
 
-#define OPTCHARS "Vvht:c:w:H:P:I:a:e:p:s:R:r:u:f:C:nlLS"
+/*#define OPTCHARS "Vvht:c:w:H:P:I:a:e:p:s:R:r:u:f:C:nlLS"*/
+#define   OPTCHARS "Vvht:c:w:H:P:I:a:e:p:s:R:r:u:f:C:M:nlLSN" /* jwz */
 
 	while (1) {
 #ifdef HAVE_GETOPT_H
@@ -469,6 +487,27 @@
 		case 'v': /* verbose */
 			verbose = TRUE;
 			break;
+		case 'N': /* no-body (jwz) */
+			no_body = TRUE;
+			break;
+		case 'M': /* max-age (jwz) */
+                  {
+                    int L = strlen(optarg);
+                    if (L && optarg[L-1] == 'm')
+                      maximum_age = atoi (optarg) * 60;
+                    else if (L && optarg[L-1] == 'h')
+                      maximum_age = atoi (optarg) * 60 * 60;
+                    else if (L && optarg[L-1] == 'd')
+                      maximum_age = atoi (optarg) * 60 * 60 * 24;
+                    else if (L && (optarg[L-1] == 's' ||
+                                   isdigit (optarg[L-1])))
+                      maximum_age = atoi (optarg);
+                    else {
+                      fprintf (stderr, "unparsable max-age: %s\n", optarg);
+                      exit (1);
+                    }
+                  }
+                  break;
 		}
 	}
 
@@ -534,6 +573,223 @@
 
 
 
+/* Returns 1 if we're done processing the document body; 0 to keep going.
+   (jwz)
+ */
+static int
+document_headers_done (char *full_page)
+{
+  const char *body, *s;
+  const char *end;
+
+  for (body = full_page; *body; body++) {
+    if (!strncmp (body, "\n\n", 2) ||
+        !strncmp (body, "\n\r\n", 3))
+      break;
+  }
+
+  if (!*body)
+    return 0;  /* haven't read end of headers yet */
+
+  full_page[body - full_page] = 0;
+  return 1;
+}
+
+
+/* jwz */
+static time_t
+parse_time_string (const char *string)
+{
+  struct tm tm;
+  time_t t;
+  memset (&tm, 0, sizeof(tm));
+
+  /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
+
+  if (isupper (string[0])  &&  /* Tue */
+      islower (string[1])  &&
+      islower (string[2])  &&
+      ',' ==   string[3]   &&
+      ' ' ==   string[4]   &&
+      (isdigit(string[5]) || string[5] == ' ') &&   /* 25 */
+      isdigit (string[6])  &&
+      ' ' ==   string[7]   &&
+      isupper (string[8])  &&  /* Dec */
+      islower (string[9])  &&
+      islower (string[10]) &&
+      ' ' ==   string[11]  &&
+      isdigit (string[12]) &&  /* 2001 */
+      isdigit (string[13]) &&
+      isdigit (string[14]) &&
+      isdigit (string[15]) &&
+      ' ' ==   string[16]  &&
+      isdigit (string[17]) &&  /* 02: */
+      isdigit (string[18]) &&
+      ':' ==   string[19]  &&
+      isdigit (string[20]) &&  /* 59: */
+      isdigit (string[21]) &&
+      ':' ==   string[22]  &&
+      isdigit (string[23]) &&  /* 03 */
+      isdigit (string[24]) &&
+      ' ' ==   string[25]  &&
+      'G' ==   string[26]  &&  /* GMT */
+      'M' ==   string[27]  &&  /* GMT */
+      'T' ==   string[28]) {
+
+    tm.tm_sec  = 10 * (string[23]-'0') + (string[24]-'0');
+    tm.tm_min  = 10 * (string[20]-'0') + (string[21]-'0');
+    tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
+    tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
+    tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
+                 !strncmp (string+8, "Feb", 3) ? 1 :
+                 !strncmp (string+8, "Mar", 3) ? 2 :
+                 !strncmp (string+8, "Apr", 3) ? 3 :
+                 !strncmp (string+8, "May", 3) ? 4 :
+                 !strncmp (string+8, "Jun", 3) ? 5 :
+                 !strncmp (string+8, "Jul", 3) ? 6 :
+                 !strncmp (string+8, "Aug", 3) ? 7 :
+                 !strncmp (string+8, "Sep", 3) ? 8 :
+                 !strncmp (string+8, "Oct", 3) ? 9 :
+                 !strncmp (string+8, "Nov", 3) ? 10 :
+                 !strncmp (string+8, "Dec", 3) ? 11 :
+                 -1);
+    tm.tm_year = ((1000 * (string[12]-'0') +
+                   100 * (string[13]-'0') +
+                   10 * (string[14]-'0') +
+                   (string[15]-'0'))
+                  - 1900);
+
+    tm.tm_isdst = 0;  /* GMT is never in DST, right? */
+
+    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,
+            so it doesn't matter what time zone we parse them in.
+     */
+
+    t = mktime (&tm);
+    if (t == (time_t) -1) t = 0;
+
+    if (verbose) {
+      const char *s = string;
+      while (*s && *s != '\r' && *s != '\n')
+        fputc (*s++, stdout);
+      printf (" ==> %lu\n", (unsigned long) t);
+    }
+
+    return t;
+
+  } else {
+    return 0;
+  }
+}
+
+
+/* jwz */
+static void
+check_document_dates (const char *headers)
+{
+  const char *s;
+  char *server_date = 0;
+  char *document_date = 0;
+
+  s = headers;
+  while (*s) {
+    const char *field = s;
+    const char *value = 0;
+
+    /* Find the end of the header field */
+    while (*s && !isspace(*s) && *s != ':')
+      s++;
+
+    /* Remember the header value, if any. */
+    if (*s == ':')
+      value = ++s;
+
+    /* Skip to the end of the header, including continuation lines. */
+    while (*s &&
+           !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
+      s++;
+    s++;
+
+    /* Process this header. */
+    if (value && value > field+2) {
+      char *ff = (char *) malloc (value-field);
+      char *ss = ff;
+      while (field < value-1)
+        *ss++ = tolower(*field++);
+      *ss++ = 0;
+
+      if (!strcmp (ff, "date") ||
+          !strcmp (ff, "last-modified")) {
+        const char *e;
+        while (*value && isspace (*value))
+          value++;
+        for (e = value; *e && *e != '\r' && *e != '\n'; e++)
+            ;
+        ss = (char *) malloc (e - value + 1);
+        strncpy (ss, value, e - value);
+        ss[e - value] = 0;
+        if (!strcmp (ff, "date")) {
+          if (server_date) free (server_date);
+          server_date = ss;
+        } else {
+          if (document_date) free (document_date);
+          document_date = ss;
+        }
+      }
+      free (ff);
+    }
+  }
+
+  /* Done parsing the body.  Now check the dates we (hopefully) parsed.
+   */
+  if (!server_date || !*server_date) {
+    terminate (STATE_UNKNOWN, "server date unknown\n");
+  } else if (!document_date || !*document_date) {
+    terminate (STATE_CRITICAL, "document modification date unknown\n");
+  } else {
+    time_t sd = parse_time_string (server_date);
+    time_t dd = parse_time_string (document_date);
+    char buf [255];
+
+    if (sd <= 0) {
+      sprintf (buf, "server date \"%100s\" unparsable", server_date);
+      terminate (STATE_CRITICAL, buf);
+
+    } else if (dd <= 0) {
+      sprintf (buf, "document date \"%100s\" unparsable", document_date);
+      terminate (STATE_CRITICAL, buf);
+
+    } else if (dd > sd + 30) {
+      char buf[200];
+      sprintf (buf, "document is %d seconds in the future\n", dd - sd);
+      terminate (STATE_CRITICAL, buf);
+
+    } else if (dd < sd - maximum_age) {
+      char buf[200];
+      int n = (sd - dd);
+      if (n > (60 * 60 * 24 * 2))
+        sprintf (buf, "last modified %.1f days ago\n",
+                 ((float) n) / (60 * 60 * 24));
+      else
+        sprintf (buf, "last modified %d:%02d:%02d ago\n",
+                 n / (60 * 60), (n / 60) % 60, n % 60);
+      terminate (STATE_CRITICAL, buf);
+    }
+
+    free (server_date);
+    free (document_date);
+  }
+}
+
+
+
 int
 check_http (void)
 {
@@ -625,6 +881,12 @@
 		buffer[i] = '\0';
 		asprintf (&full_page, "%s%s", full_page, buffer);
 		pagesize += i;
+
+                /* jwz */
+                if (no_body && document_headers_done (full_page)) {
+                  i = 0;
+                  break;
+                }
 	}
 
 	if (i < 0 && errno != ECONNRESET) {
@@ -685,7 +947,8 @@
 	page += (size_t) strspn (page, "\r\n");
 	header[pos - header] = 0;
 	if (verbose)
-		printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header, page);
+		printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header,
+                (no_body ? "  [[ skipped ]]" : page)); /* jwz */
 
 	/* make sure the status line matches the response we are looking for */
 	if (!strstr (status_line, server_expect)) {
@@ -810,6 +1073,11 @@
 	} /* end else (server_expect_yn)  */
 
 		
+        /* jwz */
+        if (maximum_age >= 0) {
+          check_document_dates (header);
+        }
+
 	/* check elapsed time */
 	elapsed_time = delta_time (tv);
 	asprintf (&msg, "HTTP problem: %s - %7.3f second response time %s%s|time=%7.3f\n",




More information about the Devel mailing list