Code

return-logic related fix for cert checking, thanks to emmet hogan.
[nagiosplug.git] / plugins / check_http.c
1 /******************************************************************************
3  This program is free software; you can redistribute it and/or modify
4  it under the terms of the GNU General Public License as published by
5  the Free Software Foundation; either version 2 of the License, or
6  (at your option) any later version.
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  GNU General Public License for more details.
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  $Id$
18  
19 ******************************************************************************/
20 /* splint -I. -I../../plugins -I../../lib/ -I/usr/kerberos/include/ ../../plugins/check_http.c */
22 const char *progname = "check_http";
23 const char *revision = "$Revision$";
24 const char *copyright = "1999-2005";
25 const char *email = "nagiosplug-devel@lists.sourceforge.net";
27 #include "common.h"
28 #include "netutils.h"
29 #include "utils.h"
31 #define INPUT_DELIMITER ";"
33 #define HTTP_EXPECT "HTTP/1."
34 enum {
35   MAX_IPV4_HOSTLENGTH = 255,
36   HTTP_PORT = 80,
37   HTTPS_PORT = 443
38 };
40 #ifdef HAVE_SSL
41 int check_cert = FALSE;
42 int days_till_exp;
43 char *randbuff;
44 X509 *server_cert;
45 #  define my_recv(buf, len) ((use_ssl) ? np_net_ssl_read(buf, len) : read(sd, buf, len))
46 #  define my_send(buf, len) ((use_ssl) ? np_net_ssl_write(buf, len) : send(sd, buf, len, 0))
47 #else /* ifndef HAVE_SSL */
48 #  define my_recv(buf, len) read(sd, buf, len)
49 #  define my_send(buf, len) send(sd, buf, len, 0)
50 #endif /* HAVE_SSL */
51 int no_body = FALSE;
52 int maximum_age = -1;
54 #ifdef HAVE_REGEX_H
55 enum {
56   REGS = 2,
57   MAX_RE_SIZE = 256
58 };
59 #include <regex.h>
60 regex_t preg;
61 regmatch_t pmatch[REGS];
62 char regexp[MAX_RE_SIZE];
63 char errbuf[MAX_INPUT_BUFFER];
64 int cflags = REG_NOSUB | REG_EXTENDED | REG_NEWLINE;
65 int errcode;
66 #endif
68 struct timeval tv;
70 #define HTTP_URL "/"
71 #define CRLF "\r\n"
73 char timestamp[17] = "";
74 int specify_port = FALSE;
75 int server_port = HTTP_PORT;
76 char server_port_text[6] = "";
77 char server_type[6] = "http";
78 char *server_address;
79 char *host_name;
80 char *server_url;
81 char *user_agent;
82 int server_url_length;
83 int server_expect_yn = 0;
84 char server_expect[MAX_INPUT_BUFFER] = HTTP_EXPECT;
85 char string_expect[MAX_INPUT_BUFFER] = "";
86 double warning_time = 0;
87 int check_warning_time = FALSE;
88 double critical_time = 0;
89 int check_critical_time = FALSE;
90 char user_auth[MAX_INPUT_BUFFER] = "";
91 int display_html = FALSE;
92 char *http_opt_headers;
93 int onredirect = STATE_OK;
94 int use_ssl = FALSE;
95 int verbose = FALSE;
96 int sd;
97 int min_page_len = 0;
98 int max_page_len = 0;
99 int redir_depth = 0;
100 int max_depth = 15;
101 char *http_method;
102 char *http_post_data;
103 char *http_content_type;
104 char buffer[MAX_INPUT_BUFFER];
106 int process_arguments (int, char **);
107 static char *base64 (const char *bin, size_t len);
108 int check_http (void);
109 void redir (char *pos, char *status_line);
110 int server_type_check(const char *type);
111 int server_port_check(int ssl_flag);
112 char *perfd_time (double microsec);
113 char *perfd_size (int page_len);
114 void print_help (void);
115 void print_usage (void);
117 int
118 main (int argc, char **argv)
120   int result = STATE_UNKNOWN;
122   /* Set default URL. Must be malloced for subsequent realloc if --onredirect=follow */
123   server_url = strdup(HTTP_URL);
124   server_url_length = strlen(server_url);
125   asprintf (&user_agent, "User-Agent: check_http/%s (nagios-plugins %s)",
126             clean_revstring (revision), VERSION);
128   if (process_arguments (argc, argv) == ERROR)
129     usage4 (_("Could not parse arguments"));
131   if (strstr (timestamp, ":")) {
132     if (strstr (server_url, "?"))
133       asprintf (&server_url, "%s&%s", server_url, timestamp);
134     else
135       asprintf (&server_url, "%s?%s", server_url, timestamp);
136   }
138   if (display_html == TRUE)
139     printf ("<A HREF=\"%s://%s:%d%s\" target=\"_blank\">", 
140       use_ssl ? "https" : "http", host_name,
141       server_port, server_url);
143   /* initialize alarm signal handling, set socket timeout, start timer */
144   (void) signal (SIGALRM, socket_timeout_alarm_handler);
145   (void) alarm (socket_timeout);
146   gettimeofday (&tv, NULL);
148   result = check_http ();
149   return result;
154 /* process command-line arguments */
155 int
156 process_arguments (int argc, char **argv)
158   int c = 1;
160   int option = 0;
161   static struct option longopts[] = {
162     STD_LONG_OPTS,
163     {"file",required_argument,0,'F'},
164     {"link", no_argument, 0, 'L'},
165     {"nohtml", no_argument, 0, 'n'},
166     {"ssl", no_argument, 0, 'S'},
167     {"verbose", no_argument, 0, 'v'},
168     {"post", required_argument, 0, 'P'},
169     {"IP-address", required_argument, 0, 'I'},
170     {"url", required_argument, 0, 'u'},
171     {"string", required_argument, 0, 's'},
172     {"regex", required_argument, 0, 'r'},
173     {"ereg", required_argument, 0, 'r'},
174     {"eregi", required_argument, 0, 'R'},
175     {"linespan", no_argument, 0, 'l'},
176     {"onredirect", required_argument, 0, 'f'},
177     {"certificate", required_argument, 0, 'C'},
178     {"useragent", required_argument, 0, 'A'},
179     {"header", required_argument, 0, 'k'},
180     {"no-body", no_argument, 0, 'N'},
181     {"max-age", required_argument, 0, 'M'},
182     {"content-type", required_argument, 0, 'T'},
183     {"pagesize", required_argument, 0, 'm'},
184     {"use-ipv4", no_argument, 0, '4'},
185     {"use-ipv6", no_argument, 0, '6'},
186     {0, 0, 0, 0}
187   };
189   if (argc < 2)
190     return ERROR;
192   for (c = 1; c < argc; c++) {
193     if (strcmp ("-to", argv[c]) == 0)
194       strcpy (argv[c], "-t");
195     if (strcmp ("-hn", argv[c]) == 0)
196       strcpy (argv[c], "-H");
197     if (strcmp ("-wt", argv[c]) == 0)
198       strcpy (argv[c], "-w");
199     if (strcmp ("-ct", argv[c]) == 0)
200       strcpy (argv[c], "-c");
201     if (strcmp ("-nohtml", argv[c]) == 0)
202       strcpy (argv[c], "-n");
203   }
205   while (1) {
206     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);
207     if (c == -1 || c == EOF)
208       break;
210     switch (c) {
211     case '?': /* usage */
212       usage2 (_("Unknown argument"), optarg);
213       break;
214     case 'h': /* help */
215       print_help ();
216       exit (STATE_OK);
217       break;
218     case 'V': /* version */
219       print_revision (progname, revision);
220       exit (STATE_OK);
221       break;
222     case 't': /* timeout period */
223       if (!is_intnonneg (optarg))
224         usage2 (_("Timeout interval must be a positive integer"), optarg);
225       else
226         socket_timeout = atoi (optarg);
227       break;
228     case 'c': /* critical time threshold */
229       if (!is_nonnegative (optarg))
230         usage2 (_("Critical threshold must be integer"), optarg);
231       else {
232         critical_time = strtod (optarg, NULL);
233         check_critical_time = TRUE;
234       }
235       break;
236     case 'w': /* warning time threshold */
237       if (!is_nonnegative (optarg))
238         usage2 (_("Warning threshold must be integer"), optarg);
239       else {
240         warning_time = strtod (optarg, NULL);
241         check_warning_time = TRUE;
242       }
243       break;
244     case 'A': /* User Agent String */
245       asprintf (&user_agent, "User-Agent: %s", optarg);
246       break;
247     case 'k': /* Additional headers */
248       asprintf (&http_opt_headers, "%s", optarg);
249       break;
250     case 'L': /* show html link */
251       display_html = TRUE;
252       break;
253     case 'n': /* do not show html link */
254       display_html = FALSE;
255       break;
256     case 'S': /* use SSL */
257 #ifndef HAVE_SSL
258       usage4 (_("Invalid option - SSL is not available"));
259 #endif
260       use_ssl = TRUE;
261       if (specify_port == FALSE)
262         server_port = HTTPS_PORT;
263       break;
264     case 'C': /* Check SSL cert validity */
265 #ifdef USE_OPENSSL
266       if (!is_intnonneg (optarg))
267         usage2 (_("Invalid certificate expiration period"), optarg);
268       else {
269         days_till_exp = atoi (optarg);
270         check_cert = TRUE;
271       }
272 #else
273       usage4 (_("Invalid option - SSL is not available"));
274 #endif
275       break;
276     case 'f': /* onredirect */
277       if (!strcmp (optarg, "follow"))
278         onredirect = STATE_DEPENDENT;
279       if (!strcmp (optarg, "unknown"))
280         onredirect = STATE_UNKNOWN;
281       if (!strcmp (optarg, "ok"))
282         onredirect = STATE_OK;
283       if (!strcmp (optarg, "warning"))
284         onredirect = STATE_WARNING;
285       if (!strcmp (optarg, "critical"))
286         onredirect = STATE_CRITICAL;
287       if (verbose)
288         printf(_("option f:%d \n"), onredirect);  
289       break;
290     /* Note: H, I, and u must be malloc'd or will fail on redirects */
291     case 'H': /* Host Name (virtual host) */
292       host_name = strdup (optarg);
293       if (strstr (optarg, ":"))
294         sscanf (optarg, "%*[^:]:%d", &server_port);
295       break;
296     case 'I': /* Server IP-address */
297       server_address = strdup (optarg);
298       break;
299     case 'u': /* URL path */
300       server_url = strdup (optarg);
301       server_url_length = strlen (server_url);
302       break;
303     case 'p': /* Server port */
304       if (!is_intnonneg (optarg))
305         usage2 (_("Invalid port number"), optarg);
306       else {
307         server_port = atoi (optarg);
308         specify_port = TRUE;
309       }
310       break;
311     case 'a': /* authorization info */
312       strncpy (user_auth, optarg, MAX_INPUT_BUFFER - 1);
313       user_auth[MAX_INPUT_BUFFER - 1] = 0;
314       break;
315     case 'P': /* HTTP POST data in URL encoded format */
316       if (http_method || http_post_data) break;
317       http_method = strdup("POST");
318       http_post_data = strdup (optarg);
319       break;
320     case 's': /* string or substring */
321       strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1);
322       string_expect[MAX_INPUT_BUFFER - 1] = 0;
323       break;
324     case 'e': /* string or substring */
325       strncpy (server_expect, optarg, MAX_INPUT_BUFFER - 1);
326       server_expect[MAX_INPUT_BUFFER - 1] = 0;
327       server_expect_yn = 1;
328       break;
329     case 'T': /* Content-type */
330       asprintf (&http_content_type, "%s", optarg);
331       break;
332 #ifndef HAVE_REGEX_H
333     case 'l': /* linespan */
334     case 'r': /* linespan */
335     case 'R': /* linespan */
336       usage4 (_("Call for regex which was not a compiled option"));
337       break;
338 #else
339     case 'l': /* linespan */
340       cflags &= ~REG_NEWLINE;
341       break;
342     case 'R': /* regex */
343       cflags |= REG_ICASE;
344     case 'r': /* regex */
345       strncpy (regexp, optarg, MAX_RE_SIZE - 1);
346       regexp[MAX_RE_SIZE - 1] = 0;
347       errcode = regcomp (&preg, regexp, cflags);
348       if (errcode != 0) {
349         (void) regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
350         printf (_("Could Not Compile Regular Expression: %s"), errbuf);
351         return ERROR;
352       }
353       break;
354 #endif
355     case '4':
356       address_family = AF_INET;
357       break;
358     case '6':
359 #ifdef USE_IPV6
360       address_family = AF_INET6;
361 #else
362       usage4 (_("IPv6 support not available"));
363 #endif
364       break;
365     case 'v': /* verbose */
366       verbose = TRUE;
367       break;
368     case 'm': /* min_page_length */
369       {
370       char *tmp;
371       if (strchr(optarg, ':') != (char *)NULL) {
372         /* range, so get two values, min:max */
373         tmp = strtok(optarg, ":");
374         if (tmp == NULL) {
375           printf("Bad format: try \"-m min:max\"\n");
376           exit (STATE_WARNING);
377         } else
378           min_page_len = atoi(tmp);
380         tmp = strtok(NULL, ":");
381         if (tmp == NULL) {
382           printf("Bad format: try \"-m min:max\"\n");
383           exit (STATE_WARNING);
384         } else
385           max_page_len = atoi(tmp);
386       } else 
387         min_page_len = atoi (optarg);
388       break;
389       }
390     case 'N': /* no-body */
391       no_body = TRUE;
392       break;
393     case 'M': /* max-age */
394                   {
395                     int L = strlen(optarg);
396                     if (L && optarg[L-1] == 'm')
397                       maximum_age = atoi (optarg) * 60;
398                     else if (L && optarg[L-1] == 'h')
399                       maximum_age = atoi (optarg) * 60 * 60;
400                     else if (L && optarg[L-1] == 'd')
401                       maximum_age = atoi (optarg) * 60 * 60 * 24;
402                     else if (L && (optarg[L-1] == 's' ||
403                                    isdigit (optarg[L-1])))
404                       maximum_age = atoi (optarg);
405                     else {
406                       fprintf (stderr, "unparsable max-age: %s\n", optarg);
407                       exit (STATE_WARNING);
408                     }
409                   }
410                   break;
411     }
412   }
414   c = optind;
416   if (server_address == NULL && c < argc)
417     server_address = strdup (argv[c++]);
419   if (host_name == NULL && c < argc)
420     host_name = strdup (argv[c++]);
422   if (server_address == NULL) {
423     if (host_name == NULL)
424       usage4 (_("You must specify a server address or host name"));
425     else
426       server_address = strdup (host_name);
427   }
429   if (check_critical_time && critical_time>(double)socket_timeout)
430     socket_timeout = (int)critical_time + 1;
432   if (http_method == NULL)
433     http_method = strdup ("GET");
435   return TRUE;
440 /* written by lauri alanko */
441 static char *
442 base64 (const char *bin, size_t len)
445   char *buf = (char *) malloc ((len + 2) / 3 * 4 + 1);
446   size_t i = 0, j = 0;
448   char BASE64_END = '=';
449   char base64_table[64];
450   strncpy (base64_table, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 64);
452   while (j < len - 2) {
453     buf[i++] = base64_table[bin[j] >> 2];
454     buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)];
455     buf[i++] = base64_table[((bin[j + 1] & 15) << 2) | (bin[j + 2] >> 6)];
456     buf[i++] = base64_table[bin[j + 2] & 63];
457     j += 3;
458   }
460   switch (len - j) {
461   case 1:
462     buf[i++] = base64_table[bin[j] >> 2];
463     buf[i++] = base64_table[(bin[j] & 3) << 4];
464     buf[i++] = BASE64_END;
465     buf[i++] = BASE64_END;
466     break;
467   case 2:
468     buf[i++] = base64_table[bin[j] >> 2];
469     buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)];
470     buf[i++] = base64_table[(bin[j + 1] & 15) << 2];
471     buf[i++] = BASE64_END;
472     break;
473   case 0:
474     break;
475   }
477   buf[i] = '\0';
478   return buf;
483 /* Returns 1 if we're done processing the document body; 0 to keep going */
484 static int
485 document_headers_done (char *full_page)
487   const char *body;
489   for (body = full_page; *body; body++) {
490     if (!strncmp (body, "\n\n", 2) || !strncmp (body, "\n\r\n", 3))
491       break;
492   }
494   if (!*body)
495     return 0;  /* haven't read end of headers yet */
497   full_page[body - full_page] = 0;
498   return 1;
501 static time_t
502 parse_time_string (const char *string)
504   struct tm tm;
505   time_t t;
506   memset (&tm, 0, sizeof(tm));
508   /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
510   if (isupper (string[0])  &&  /* Tue */
511     islower (string[1])  &&
512     islower (string[2])  &&
513     ',' ==   string[3]   &&
514     ' ' ==   string[4]   &&
515     (isdigit(string[5]) || string[5] == ' ') &&   /* 25 */
516     isdigit (string[6])  &&
517     ' ' ==   string[7]   &&
518     isupper (string[8])  &&  /* Dec */
519     islower (string[9])  &&
520     islower (string[10]) &&
521     ' ' ==   string[11]  &&
522     isdigit (string[12]) &&  /* 2001 */
523     isdigit (string[13]) &&
524     isdigit (string[14]) &&
525     isdigit (string[15]) &&
526     ' ' ==   string[16]  &&
527     isdigit (string[17]) &&  /* 02: */
528     isdigit (string[18]) &&
529     ':' ==   string[19]  &&
530     isdigit (string[20]) &&  /* 59: */
531     isdigit (string[21]) &&
532     ':' ==   string[22]  &&
533     isdigit (string[23]) &&  /* 03 */
534     isdigit (string[24]) &&
535     ' ' ==   string[25]  &&
536     'G' ==   string[26]  &&  /* GMT */
537     'M' ==   string[27]  &&  /* GMT */
538     'T' ==   string[28]) {
540     tm.tm_sec  = 10 * (string[23]-'0') + (string[24]-'0');
541     tm.tm_min  = 10 * (string[20]-'0') + (string[21]-'0');
542     tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
543     tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
544     tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
545       !strncmp (string+8, "Feb", 3) ? 1 :
546       !strncmp (string+8, "Mar", 3) ? 2 :
547       !strncmp (string+8, "Apr", 3) ? 3 :
548       !strncmp (string+8, "May", 3) ? 4 :
549       !strncmp (string+8, "Jun", 3) ? 5 :
550       !strncmp (string+8, "Jul", 3) ? 6 :
551       !strncmp (string+8, "Aug", 3) ? 7 :
552       !strncmp (string+8, "Sep", 3) ? 8 :
553       !strncmp (string+8, "Oct", 3) ? 9 :
554       !strncmp (string+8, "Nov", 3) ? 10 :
555       !strncmp (string+8, "Dec", 3) ? 11 :
556       -1);
557     tm.tm_year = ((1000 * (string[12]-'0') +
558       100 * (string[13]-'0') +
559       10 * (string[14]-'0') +
560       (string[15]-'0'))
561       - 1900);
563     tm.tm_isdst = 0;  /* GMT is never in DST, right? */
565     if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
566       return 0;
568     /* 
569     This is actually wrong: we need to subtract the local timezone
570     offset from GMT from this value.  But, that's ok in this usage,
571     because we only comparing these two GMT dates against each other,
572     so it doesn't matter what time zone we parse them in.
573     */
575     t = mktime (&tm);
576     if (t == (time_t) -1) t = 0;
578     if (verbose) {
579       const char *s = string;
580       while (*s && *s != '\r' && *s != '\n')
581       fputc (*s++, stdout);
582       printf (" ==> %lu\n", (unsigned long) t);
583     }
585     return t;
587   } else {
588     return 0;
589   }
594 static void
595 check_document_dates (const char *headers)
597   const char *s;
598   char *server_date = 0;
599   char *document_date = 0;
601   s = headers;
602   while (*s) {
603     const char *field = s;
604     const char *value = 0;
606     /* Find the end of the header field */
607     while (*s && !isspace(*s) && *s != ':')
608       s++;
610     /* Remember the header value, if any. */
611     if (*s == ':')
612       value = ++s;
614     /* Skip to the end of the header, including continuation lines. */
615     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
616       s++;
617     s++;
619     /* Process this header. */
620     if (value && value > field+2) {
621       char *ff = (char *) malloc (value-field);
622       char *ss = ff;
623       while (field < value-1)
624         *ss++ = tolower(*field++);
625       *ss++ = 0;
627       if (!strcmp (ff, "date") || !strcmp (ff, "last-modified")) {
628         const char *e;
629         while (*value && isspace (*value))
630           value++;
631         for (e = value; *e && *e != '\r' && *e != '\n'; e++)
632           ;
633         ss = (char *) malloc (e - value + 1);
634         strncpy (ss, value, e - value);
635         ss[e - value] = 0;
636         if (!strcmp (ff, "date")) {
637           if (server_date) free (server_date);
638           server_date = ss;
639         } else {
640           if (document_date) free (document_date);
641           document_date = ss;
642         }
643       }
644       free (ff);
645     }
646   }
648   /* Done parsing the body.  Now check the dates we (hopefully) parsed.  */
649   if (!server_date || !*server_date) {
650     die (STATE_UNKNOWN, _("Server date unknown\n"));
651   } else if (!document_date || !*document_date) {
652     die (STATE_CRITICAL, _("Document modification date unknown\n"));
653   } else {
654     time_t srv_data = parse_time_string (server_date);
655     time_t doc_data = parse_time_string (document_date);
657     if (srv_data <= 0) {
658       die (STATE_CRITICAL, _("CRITICAL - Server date \"%100s\" unparsable"), server_date);
659     } else if (doc_data <= 0) {
660       die (STATE_CRITICAL, _("CRITICAL - Document date \"%100s\" unparsable"), document_date);
661     } else if (doc_data > srv_data + 30) {
662       die (STATE_CRITICAL, _("CRITICAL - Document is %d seconds in the future\n"), (int)doc_data - (int)srv_data);
663     } else if (doc_data < srv_data - maximum_age) {
664     int n = (srv_data - doc_data);
665     if (n > (60 * 60 * 24 * 2))
666       die (STATE_CRITICAL,
667         _("CRITICAL - Last modified %.1f days ago\n"),
668         ((float) n) / (60 * 60 * 24));
669   else
670     die (STATE_CRITICAL,
671         _("CRITICAL - Last modified %d:%02d:%02d ago\n"),
672         n / (60 * 60), (n / 60) % 60, n % 60);
673     }
675     free (server_date);
676     free (document_date);
677   }
680 int
681 get_content_length (const char *headers)
683   const char *s;
684   int content_length = 0;
686   s = headers;
687   while (*s) {
688     const char *field = s;
689     const char *value = 0;
691     /* Find the end of the header field */
692     while (*s && !isspace(*s) && *s != ':')
693       s++;
695     /* Remember the header value, if any. */
696     if (*s == ':')
697       value = ++s;
699     /* Skip to the end of the header, including continuation lines. */
700     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
701       s++;
702     s++;
704     /* Process this header. */
705     if (value && value > field+2) {
706       char *ff = (char *) malloc (value-field);
707       char *ss = ff;
708       while (field < value-1)
709         *ss++ = tolower(*field++);
710       *ss++ = 0;
712       if (!strcmp (ff, "content-length")) {
713         const char *e;
714         while (*value && isspace (*value))
715           value++;
716         for (e = value; *e && *e != '\r' && *e != '\n'; e++)
717           ;
718         ss = (char *) malloc (e - value + 1);
719         strncpy (ss, value, e - value);
720         ss[e - value] = 0;
721         content_length = atoi(ss);
722         free (ss);
723       }
724       free (ff);
725     }
726   }
727   return (content_length);
730 int
731 check_http (void)
733   char *msg;
734   char *status_line;
735   char *status_code;
736   char *header;
737   char *page;
738   char *auth;
739   int http_status;
740   int i = 0;
741   size_t pagesize = 0;
742   char *full_page;
743   char *buf;
744   char *pos;
745   long microsec;
746   double elapsed_time;
747   int page_len = 0;
748   int result = STATE_UNKNOWN;
750   /* try to connect to the host at the given port number */
751   if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
752     die (STATE_CRITICAL, _("Unable to open TCP socket\n"));
753 #ifdef HAVE_SSL
754   if (use_ssl == TRUE) {
755     np_net_ssl_init(sd);
756     if (check_cert == TRUE) {
757       result = np_net_ssl_check_cert(days_till_exp);
758       np_net_ssl_cleanup();
759       if(sd) close(sd);
760       return result;
761     }
762   }
763 #endif /* HAVE_SSL */
765   asprintf (&buf, "%s %s HTTP/1.0\r\n%s\r\n", http_method, server_url, user_agent);
767   /* optionally send the host header info */
768   if (host_name)
769     asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
771   /* optionally send any other header tag */
772   if (http_opt_headers) {
773     for ((pos = strtok(http_opt_headers, INPUT_DELIMITER)); pos; (pos = strtok(NULL, INPUT_DELIMITER)))
774       asprintf (&buf, "%s%s\r\n", buf, pos);
775   }
777   /* optionally send the authentication info */
778   if (strlen(user_auth)) {
779     auth = base64 (user_auth, strlen (user_auth));
780     asprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
781   }
783   /* either send http POST data */
784   if (http_post_data) {
785     if (http_content_type) {
786       asprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
787     } else {
788       asprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
789     }
790     
791     asprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
792     asprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
793   }
794   else {
795     /* or just a newline so the server knows we're done with the request */
796     asprintf (&buf, "%s%s", buf, CRLF);
797   }
799   if (verbose) printf ("%s\n", buf);
800   my_send (buf, strlen (buf));
802   /* fetch the page */
803   full_page = strdup("");
804   while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
805     buffer[i] = '\0';
806     asprintf (&full_page, "%s%s", full_page, buffer);
807     pagesize += i;
809                 if (no_body && document_headers_done (full_page)) {
810                   i = 0;
811                   break;
812                 }
813   }
815   if (i < 0 && errno != ECONNRESET) {
816 #ifdef HAVE_SSL
817     /*
818     if (use_ssl) {
819       sslerr=SSL_get_error(ssl, i);
820       if ( sslerr == SSL_ERROR_SSL ) {
821         die (STATE_WARNING, _("Client Certificate Required\n"));
822       } else {
823         die (STATE_CRITICAL, _("Error on receive\n"));
824       }
825     }
826     else {
827     */
828 #endif
829       die (STATE_CRITICAL, _("Error on receive\n"));
830 #ifdef HAVE_SSL
831       /* XXX
832     }
833     */
834 #endif
835   }
837   /* return a CRITICAL status if we couldn't read any data */
838   if (pagesize == (size_t) 0)
839     die (STATE_CRITICAL, _("No data received %s\n"), timestamp);
841   /* close the connection */
842 #ifdef HAVE_SSL
843   np_net_ssl_cleanup();
844 #endif
845   if(sd) close(sd);
847   /* reset the alarm */
848   alarm (0);
850   /* leave full_page untouched so we can free it later */
851   page = full_page;
853   if (verbose)
854     printf ("%s://%s:%d%s is %d characters\n",
855       use_ssl ? "https" : "http", server_address,
856       server_port, server_url, (int)pagesize);
858   /* find status line and null-terminate it */
859   status_line = page;
860   page += (size_t) strcspn (page, "\r\n");
861   pos = page;
862   page += (size_t) strspn (page, "\r\n");
863   status_line[strcspn(status_line, "\r\n")] = 0;
864   strip (status_line);
865   if (verbose)
866     printf ("STATUS: %s\n", status_line);
868   /* find header info and null-terminate it */
869   header = page;
870   while (strcspn (page, "\r\n") > 0) {
871     page += (size_t) strcspn (page, "\r\n");
872     pos = page;
873     if ((strspn (page, "\r") == 1 && strspn (page, "\r\n") >= 2) ||
874         (strspn (page, "\n") == 1 && strspn (page, "\r\n") >= 2))
875       page += (size_t) 2;
876     else
877       page += (size_t) 1;
878   }
879   page += (size_t) strspn (page, "\r\n");
880   header[pos - header] = 0;
881   if (verbose)
882     printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header,
883                 (no_body ? "  [[ skipped ]]" : page));
885   /* make sure the status line matches the response we are looking for */
886   if (!strstr (status_line, server_expect)) {
887     if (server_port == HTTP_PORT)
888       asprintf (&msg,
889                 _("Invalid HTTP response received from host\n"));
890     else
891       asprintf (&msg,
892                 _("Invalid HTTP response received from host on port %d\n"),
893                 server_port);
894     die (STATE_CRITICAL, "%s", msg);
895   }
897   /* Exit here if server_expect was set by user and not default */
898   if ( server_expect_yn  )  {
899     asprintf (&msg,
900               _("HTTP OK: Status line output matched \"%s\"\n"),
901               server_expect);
902     if (verbose)
903       printf ("%s\n",msg);
904   }
905   else {
906     /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
907     /* HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
908     /* Status-Code = 3 DIGITS */
910     status_code = strchr (status_line, ' ') + sizeof (char);
911     if (strspn (status_code, "1234567890") != 3)
912       die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line);
914     http_status = atoi (status_code);
916     /* check the return code */
918     if (http_status >= 600 || http_status < 100)
919       die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
921     /* server errors result in a critical state */
922     else if (http_status >= 500)
923       die (STATE_CRITICAL, _("HTTP CRITICAL: %s\n"), status_line);
925     /* client errors result in a warning state */
926     else if (http_status >= 400)
927       die (STATE_WARNING, _("HTTP WARNING: %s\n"), status_line);
929     /* check redirected page if specified */
930     else if (http_status >= 300) {
932       if (onredirect == STATE_DEPENDENT)
933         redir (header, status_line);
934       else if (onredirect == STATE_UNKNOWN)
935         printf (_("UNKNOWN"));
936       else if (onredirect == STATE_OK)
937         printf (_("OK"));
938       else if (onredirect == STATE_WARNING)
939         printf (_("WARNING"));
940       else if (onredirect == STATE_CRITICAL)
941         printf (_("CRITICAL"));
942       microsec = deltime (tv);
943       elapsed_time = (double)microsec / 1.0e6;
944       die (onredirect,
945            _(" - %s - %.3f second response time %s%s|%s %s\n"),
946            status_line, elapsed_time, timestamp,
947            (display_html ? "</A>" : ""),
948            perfd_time (elapsed_time), perfd_size (pagesize));
949     } /* end if (http_status >= 300) */
951   } /* end else (server_expect_yn)  */
952     
953         if (maximum_age >= 0) {
954           check_document_dates (header);
955         }
957   /* check elapsed time */
958   microsec = deltime (tv);
959   elapsed_time = (double)microsec / 1.0e6;
960   asprintf (&msg,
961             _("HTTP WARNING: %s - %.3f second response time %s%s|%s %s\n"),
962             status_line, elapsed_time, timestamp,
963             (display_html ? "</A>" : ""),
964             perfd_time (elapsed_time), perfd_size (pagesize));
965   if (check_critical_time == TRUE && elapsed_time > critical_time)
966     die (STATE_CRITICAL, "%s", msg);
967   if (check_warning_time == TRUE && elapsed_time > warning_time)
968     die (STATE_WARNING, "%s", msg);
970   /* Page and Header content checks go here */
971   /* these checks should be last */
973   if (strlen (string_expect)) {
974     if (strstr (page, string_expect)) {
975       printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
976               status_line, elapsed_time,
977               timestamp, (display_html ? "</A>" : ""),
978               perfd_time (elapsed_time), perfd_size (pagesize));
979       exit (STATE_OK);
980     }
981     else {
982       printf (_("CRITICAL - string not found%s|%s %s\n"),
983               (display_html ? "</A>" : ""),
984               perfd_time (elapsed_time), perfd_size (pagesize));
985       exit (STATE_CRITICAL);
986     }
987   }
988 #ifdef HAVE_REGEX_H
989   if (strlen (regexp)) {
990     errcode = regexec (&preg, page, REGS, pmatch, 0);
991     if (errcode == 0) {
992       printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
993               status_line, elapsed_time,
994               timestamp, (display_html ? "</A>" : ""),
995               perfd_time (elapsed_time), perfd_size (pagesize));
996       exit (STATE_OK);
997     }
998     else {
999       if (errcode == REG_NOMATCH) {
1000         printf (_("CRITICAL - pattern not found%s|%s %s\n"),
1001                 (display_html ? "</A>" : ""),
1002                 perfd_time (elapsed_time), perfd_size (pagesize));
1003         exit (STATE_CRITICAL);
1004       }
1005       else {
1006         regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
1007         printf (_("CRITICAL - Execute Error: %s\n"), errbuf);
1008         exit (STATE_CRITICAL);
1009       }
1010     }
1011   }
1012 #endif
1014   /* make sure the page is of an appropriate size */
1015   /* page_len = get_content_length(header); */
1016   page_len = pagesize;
1017   if ((max_page_len > 0) && (page_len > max_page_len)) {
1018     printf (_("HTTP WARNING: page size %d too large%s|%s\n"),
1019       page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1020     exit (STATE_WARNING);
1021   } else if ((min_page_len > 0) && (page_len < min_page_len)) {
1022     printf (_("HTTP WARNING: page size %d too small%s|%s\n"),
1023       page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1024     exit (STATE_WARNING);
1025   }
1026   /* We only get here if all tests have been passed */
1027   asprintf (&msg, _("HTTP OK %s - %d bytes in %.3f seconds %s%s|%s %s\n"),
1028             status_line, page_len, elapsed_time,
1029             timestamp, (display_html ? "</A>" : ""),
1030             perfd_time (elapsed_time), perfd_size (page_len));
1031   die (STATE_OK, "%s", msg);
1032   return STATE_UNKNOWN;
1037 /* per RFC 2396 */
1038 #define HDR_LOCATION "%*[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]: "
1039 #define URI_HTTP "%[HTPShtps]://"
1040 #define URI_HOST "%[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1041 #define URI_PORT ":%[0123456789]"
1042 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1043 #define HD1 URI_HTTP URI_HOST URI_PORT URI_PATH
1044 #define HD2 URI_HTTP URI_HOST URI_PATH
1045 #define HD3 URI_HTTP URI_HOST URI_PORT
1046 #define HD4 URI_HTTP URI_HOST
1047 #define HD5 URI_PATH
1049 void
1050 redir (char *pos, char *status_line)
1052   int i = 0;
1053   char *x;
1054   char xx[2];
1055   char type[6];
1056   char *addr;
1057   char port[6];
1058   char *url;
1060   addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
1061   if (addr == NULL)
1062     die (STATE_UNKNOWN, _("Could not allocate addr\n"));
1063   
1064   url = malloc (strcspn (pos, "\r\n"));
1065   if (url == NULL)
1066     die (STATE_UNKNOWN, _("Could not allocate url\n"));
1068   while (pos) {
1070     if (sscanf (pos, "%[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]:%n", xx, &i) < 1) {
1072       pos += (size_t) strcspn (pos, "\r\n");
1073       pos += (size_t) strspn (pos, "\r\n");
1074       if (strlen(pos) == 0) 
1075         die (STATE_UNKNOWN,
1076              _("UNKNOWN - Could not find redirect location - %s%s\n"),
1077              status_line, (display_html ? "</A>" : ""));
1078       continue;
1079     }
1081     pos += i;
1082     pos += strspn (pos, " \t\r\n");
1084     url = realloc (url, strcspn (pos, "\r\n"));
1085     if (url == NULL)
1086       die (STATE_UNKNOWN, _("could not allocate url\n"));
1088     /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
1089     if (sscanf (pos, HD1, type, addr, port, url) == 4) {
1090       use_ssl = server_type_check (type);
1091       i = atoi (port);
1092     }
1094     /* URI_HTTP URI_HOST URI_PATH */
1095     else if (sscanf (pos, HD2, type, addr, url) == 3 ) { 
1096       use_ssl = server_type_check (type);
1097       i = server_port_check (use_ssl);
1098     }
1100     /* URI_HTTP URI_HOST URI_PORT */
1101     else if(sscanf (pos, HD3, type, addr, port) == 3) {
1102       strcpy (url, HTTP_URL);
1103       use_ssl = server_type_check (type);
1104       i = atoi (port);
1105     }
1107     /* URI_HTTP URI_HOST */
1108     else if(sscanf (pos, HD4, type, addr) == 2) {
1109       strcpy (url, HTTP_URL);
1110       use_ssl = server_type_check (type);
1111       i = server_port_check (use_ssl);
1112     }
1114     /* URI_PATH */
1115     else if (sscanf (pos, HD5, url) == 1) {
1116       /* relative url */
1117       if ((url[0] != '/')) {
1118         if ((x = strrchr(server_url, '/')))
1119           *x = '\0';
1120         asprintf (&url, "%s/%s", server_url, url);
1121       }
1122       i = server_port;
1123       strcpy (type, server_type);
1124       strcpy (addr, host_name);
1125     }           
1127     else {
1128       die (STATE_UNKNOWN,
1129            _("UNKNOWN - Could not parse redirect location - %s%s\n"),
1130            pos, (display_html ? "</A>" : ""));
1131     }
1133     break;
1135   } /* end while (pos) */
1137   if (++redir_depth > max_depth)
1138     die (STATE_WARNING,
1139          _("WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
1140          max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
1142   if (server_port==i &&
1143       !strcmp(server_address, addr) &&
1144       (host_name && !strcmp(host_name, addr)) &&
1145       !strcmp(server_url, url))
1146     die (STATE_WARNING,
1147          _("WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
1148          type, addr, i, url, (display_html ? "</A>" : ""));
1150   server_port = i;
1151   strcpy (server_type, type);
1153   free (host_name);
1154   host_name = strdup (addr);
1156   free (server_address);
1157   server_address = strdup (addr);
1159   free (server_url);
1160   server_url = strdup (url);
1162   check_http ();
1167 int
1168 server_type_check (const char *type)
1170   if (strcmp (type, "https"))
1171     return FALSE;
1172   else
1173     return TRUE;
1176 int
1177 server_port_check (int ssl_flag)
1179   if (ssl_flag)
1180     return HTTPS_PORT;
1181   else
1182     return HTTP_PORT;
1185 char *perfd_time (double elapsed_time)
1187   return fperfdata ("time", elapsed_time, "s",
1188             check_warning_time, warning_time,
1189             check_critical_time, critical_time,
1190                    TRUE, 0, FALSE, 0);
1195 char *perfd_size (int page_len)
1197   return perfdata ("size", page_len, "B",
1198             (min_page_len>0?TRUE:FALSE), min_page_len,
1199             (min_page_len>0?TRUE:FALSE), 0,
1200             TRUE, 0, FALSE, 0);
1203 void
1204 print_help (void)
1206   print_revision (progname, revision);
1208   printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
1209   printf (COPYRIGHT, copyright, email);
1211   printf (_("\
1212 This plugin tests the HTTP service on the specified host. It can test\n\
1213 normal (http) and secure (https) servers, follow redirects, search for\n\
1214 strings and regular expressions, check connection times, and report on\n\
1215 certificate expiration times."));
1217   printf ("\n\n");
1219   print_usage ();
1221   printf (_("NOTE: One or both of -H and -I must be specified"));
1223   printf ("\n");
1225   printf (_(UT_HELP_VRSN));
1227   printf (_("\
1228  -H, --hostname=ADDRESS\n\
1229     Host name argument for servers using host headers (virtual host)\n\
1230     Append a port to include it in the header (eg: example.com:5000)\n\
1231  -I, --IP-address=ADDRESS\n\
1232    IP address or name (use numeric address if possible to bypass DNS lookup).\n\
1233  -p, --port=INTEGER\n\
1234    Port number (default: %d)\n"), HTTP_PORT);
1236   printf (_(UT_IPv46));
1238 #ifdef HAVE_SSL
1239   printf (_("\
1240  -S, --ssl\n\
1241     Connect via SSL\n\
1242  -C, --certificate=INTEGER\n\
1243     Minimum number of days a certificate has to be valid.\n\
1244     (when this option is used the url is not checked.)\n"));
1245 #endif
1247   printf (_("\
1248  -e, --expect=STRING\n\
1249    String to expect in first (status) line of server response (default: %s)\n\
1250    If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)\n\
1251  -s, --string=STRING\n\
1252    String to expect in the content\n\
1253  -u, --url=PATH\n\
1254    URL to GET or POST (default: /)\n\
1255  -P, --post=STRING\n\
1256    URL encoded http POST data\n\
1257  -N, --no-body\n\
1258    Don't wait for document body: stop reading after headers.\n\
1259    (Note that this still does an HTTP GET or POST, not a HEAD.)\n\
1260  -M, --max-age=SECONDS\n\
1261    Warn if document is more than SECONDS old. the number can also be of \n\
1262    the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days.\n\
1263  -T, --content-type=STRING\n\
1264    specify Content-Type header media type when POSTing\n"), HTTP_EXPECT);
1266 #ifdef HAVE_REGEX_H
1267   printf (_("\
1268  -l, --linespan\n\
1269     Allow regex to span newlines (must precede -r or -R)\n\
1270  -r, --regex, --ereg=STRING\n\
1271     Search page for regex STRING\n\
1272  -R, --eregi=STRING\n\
1273     Search page for case-insensitive regex STRING\n"));
1274 #endif
1276   printf (_("\
1277  -a, --authorization=AUTH_PAIR\n\
1278    Username:password on sites with basic authentication\n\
1279  -A, --useragent=STRING\n\
1280    String to be sent in http header as \"User Agent\"\n\
1281  -k, --header=STRING\n\
1282    Any other tags to be sent in http header, separated by semicolon\n\
1283  -L, --link=URL\n\
1284    Wrap output in HTML link (obsoleted by urlize)\n\
1285  -f, --onredirect=<ok|warning|critical|follow>\n\
1286    How to handle redirected pages\n\
1287  -m, --pagesize=INTEGER<:INTEGER>\n\
1288    Minimum page size required (bytes) : Maximum page size required (bytes)\n"));
1290   printf (_(UT_WARN_CRIT));
1292   printf (_(UT_TIMEOUT), DEFAULT_SOCKET_TIMEOUT);
1294   printf (_(UT_VERBOSE));
1296           printf (_("\
1297 This plugin will attempt to open an HTTP connection with the host. Successful\n\
1298 connects return STATE_OK, refusals and timeouts return STATE_CRITICAL, other\n\
1299 errors return STATE_UNKNOWN.  Successful connects, but incorrect reponse\n\
1300 messages from the host result in STATE_WARNING return values.  If you are\n\
1301 checking a virtual server that uses 'host headers' you must supply the FQDN\n\
1302 (fully qualified domain name) as the [host_name] argument.\n"));
1304 #ifdef HAVE_SSL
1305   printf (_("\n\
1306 This plugin can also check whether an SSL enabled web server is able to\n\
1307 serve content (optionally within a specified time) or whether the X509 \n\
1308 certificate is still valid for the specified number of days.\n"));
1309   printf (_("\n\
1310 CHECK CONTENT: check_http -w 5 -c 10 --ssl www.verisign.com\n\n\
1311 When the 'www.verisign.com' server returns its content within 5 seconds, a\n\
1312 STATE_OK will be returned. When the server returns its content but exceeds\n\
1313 the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,\n\
1314 a STATE_CRITICAL will be returned.\n\n"));
1316   printf (_("\
1317 CHECK CERTIFICATE: check_http www.verisign.com -C 14\n\n\
1318 When the certificate of 'www.verisign.com' is valid for more than 14 days, a\n\
1319 STATE_OK is returned. When the certificate is still valid, but for less than\n\
1320 14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when\n\
1321 the certificate is expired.\n"));
1322 #endif
1324   printf (_(UT_SUPPORT));
1330 void
1331 print_usage (void)
1333   printf (_("Usage:"));
1334   printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
1335   printf ("       [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L]\n");
1336   printf ("       [-a auth] [-f <ok | warn | critcal | follow>] [-e <expect>]\n");
1337   printf ("       [-s string] [-l] [-r <regex> | -R <case-insensitive regex>] [-P string]\n");
1338   printf ("       [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>] [-A string] [-k string]\n");