--- /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 ] [-c ] [-t ] [-L]\n\ [-a auth] [-f ] [-e ]\n\ [-s string] [-l] [-r | -R ]\n\ - [-P string]" + [-P string] [-N] [-M ]" #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",