Code

58cf83cf236ae8cdf3f11c9579e500c64d3892ca
[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 'C': /* Check SSL cert validity */
257 #ifdef HAVE_SSL
258       if (!is_intnonneg (optarg))
259         usage2 (_("Invalid certificate expiration period"), optarg);
260       else {
261         days_till_exp = atoi (optarg);
262         check_cert = TRUE;
263       }
264      /* Fall through to -S option */
265 #endif
266     case 'S': /* use SSL */
267 #ifndef HAVE_SSL
268       usage4 (_("Invalid option - SSL is not available"));
269 #endif
270       use_ssl = TRUE;
271       if (specify_port == FALSE)
272         server_port = HTTPS_PORT;
273       break;
274     case 'f': /* onredirect */
275       if (!strcmp (optarg, "follow"))
276         onredirect = STATE_DEPENDENT;
277       if (!strcmp (optarg, "unknown"))
278         onredirect = STATE_UNKNOWN;
279       if (!strcmp (optarg, "ok"))
280         onredirect = STATE_OK;
281       if (!strcmp (optarg, "warning"))
282         onredirect = STATE_WARNING;
283       if (!strcmp (optarg, "critical"))
284         onredirect = STATE_CRITICAL;
285       if (verbose)
286         printf(_("option f:%d \n"), onredirect);  
287       break;
288     /* Note: H, I, and u must be malloc'd or will fail on redirects */
289     case 'H': /* Host Name (virtual host) */
290       host_name = strdup (optarg);
291       if (strstr (optarg, ":"))
292         sscanf (optarg, "%*[^:]:%d", &server_port);
293       break;
294     case 'I': /* Server IP-address */
295       server_address = strdup (optarg);
296       break;
297     case 'u': /* URL path */
298       server_url = strdup (optarg);
299       server_url_length = strlen (server_url);
300       break;
301     case 'p': /* Server port */
302       if (!is_intnonneg (optarg))
303         usage2 (_("Invalid port number"), optarg);
304       else {
305         server_port = atoi (optarg);
306         specify_port = TRUE;
307       }
308       break;
309     case 'a': /* authorization info */
310       strncpy (user_auth, optarg, MAX_INPUT_BUFFER - 1);
311       user_auth[MAX_INPUT_BUFFER - 1] = 0;
312       break;
313     case 'P': /* HTTP POST data in URL encoded format */
314       if (http_method || http_post_data) break;
315       http_method = strdup("POST");
316       http_post_data = strdup (optarg);
317       break;
318     case 's': /* string or substring */
319       strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1);
320       string_expect[MAX_INPUT_BUFFER - 1] = 0;
321       break;
322     case 'e': /* string or substring */
323       strncpy (server_expect, optarg, MAX_INPUT_BUFFER - 1);
324       server_expect[MAX_INPUT_BUFFER - 1] = 0;
325       server_expect_yn = 1;
326       break;
327     case 'T': /* Content-type */
328       asprintf (&http_content_type, "%s", optarg);
329       break;
330 #ifndef HAVE_REGEX_H
331     case 'l': /* linespan */
332     case 'r': /* linespan */
333     case 'R': /* linespan */
334       usage4 (_("Call for regex which was not a compiled option"));
335       break;
336 #else
337     case 'l': /* linespan */
338       cflags &= ~REG_NEWLINE;
339       break;
340     case 'R': /* regex */
341       cflags |= REG_ICASE;
342     case 'r': /* regex */
343       strncpy (regexp, optarg, MAX_RE_SIZE - 1);
344       regexp[MAX_RE_SIZE - 1] = 0;
345       errcode = regcomp (&preg, regexp, cflags);
346       if (errcode != 0) {
347         (void) regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
348         printf (_("Could Not Compile Regular Expression: %s"), errbuf);
349         return ERROR;
350       }
351       break;
352 #endif
353     case '4':
354       address_family = AF_INET;
355       break;
356     case '6':
357 #ifdef USE_IPV6
358       address_family = AF_INET6;
359 #else
360       usage4 (_("IPv6 support not available"));
361 #endif
362       break;
363     case 'v': /* verbose */
364       verbose = TRUE;
365       break;
366     case 'm': /* min_page_length */
367       {
368       char *tmp;
369       if (strchr(optarg, ':') != (char *)NULL) {
370         /* range, so get two values, min:max */
371         tmp = strtok(optarg, ":");
372         if (tmp == NULL) {
373           printf("Bad format: try \"-m min:max\"\n");
374           exit (STATE_WARNING);
375         } else
376           min_page_len = atoi(tmp);
378         tmp = strtok(NULL, ":");
379         if (tmp == NULL) {
380           printf("Bad format: try \"-m min:max\"\n");
381           exit (STATE_WARNING);
382         } else
383           max_page_len = atoi(tmp);
384       } else 
385         min_page_len = atoi (optarg);
386       break;
387       }
388     case 'N': /* no-body */
389       no_body = TRUE;
390       break;
391     case 'M': /* max-age */
392                   {
393                     int L = strlen(optarg);
394                     if (L && optarg[L-1] == 'm')
395                       maximum_age = atoi (optarg) * 60;
396                     else if (L && optarg[L-1] == 'h')
397                       maximum_age = atoi (optarg) * 60 * 60;
398                     else if (L && optarg[L-1] == 'd')
399                       maximum_age = atoi (optarg) * 60 * 60 * 24;
400                     else if (L && (optarg[L-1] == 's' ||
401                                    isdigit (optarg[L-1])))
402                       maximum_age = atoi (optarg);
403                     else {
404                       fprintf (stderr, "unparsable max-age: %s\n", optarg);
405                       exit (STATE_WARNING);
406                     }
407                   }
408                   break;
409     }
410   }
412   c = optind;
414   if (server_address == NULL && c < argc)
415     server_address = strdup (argv[c++]);
417   if (host_name == NULL && c < argc)
418     host_name = strdup (argv[c++]);
420   if (server_address == NULL) {
421     if (host_name == NULL)
422       usage4 (_("You must specify a server address or host name"));
423     else
424       server_address = strdup (host_name);
425   }
427   if (check_critical_time && critical_time>(double)socket_timeout)
428     socket_timeout = (int)critical_time + 1;
430   if (http_method == NULL)
431     http_method = strdup ("GET");
433   return TRUE;
438 /* written by lauri alanko */
439 static char *
440 base64 (const char *bin, size_t len)
443   char *buf = (char *) malloc ((len + 2) / 3 * 4 + 1);
444   size_t i = 0, j = 0;
446   char BASE64_END = '=';
447   char base64_table[64];
448   strncpy (base64_table, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 64);
450   while (j < len - 2) {
451     buf[i++] = base64_table[bin[j] >> 2];
452     buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)];
453     buf[i++] = base64_table[((bin[j + 1] & 15) << 2) | (bin[j + 2] >> 6)];
454     buf[i++] = base64_table[bin[j + 2] & 63];
455     j += 3;
456   }
458   switch (len - j) {
459   case 1:
460     buf[i++] = base64_table[bin[j] >> 2];
461     buf[i++] = base64_table[(bin[j] & 3) << 4];
462     buf[i++] = BASE64_END;
463     buf[i++] = BASE64_END;
464     break;
465   case 2:
466     buf[i++] = base64_table[bin[j] >> 2];
467     buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)];
468     buf[i++] = base64_table[(bin[j + 1] & 15) << 2];
469     buf[i++] = BASE64_END;
470     break;
471   case 0:
472     break;
473   }
475   buf[i] = '\0';
476   return buf;
481 /* Returns 1 if we're done processing the document body; 0 to keep going */
482 static int
483 document_headers_done (char *full_page)
485   const char *body;
487   for (body = full_page; *body; body++) {
488     if (!strncmp (body, "\n\n", 2) || !strncmp (body, "\n\r\n", 3))
489       break;
490   }
492   if (!*body)
493     return 0;  /* haven't read end of headers yet */
495   full_page[body - full_page] = 0;
496   return 1;
499 static time_t
500 parse_time_string (const char *string)
502   struct tm tm;
503   time_t t;
504   memset (&tm, 0, sizeof(tm));
506   /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
508   if (isupper (string[0])  &&  /* Tue */
509     islower (string[1])  &&
510     islower (string[2])  &&
511     ',' ==   string[3]   &&
512     ' ' ==   string[4]   &&
513     (isdigit(string[5]) || string[5] == ' ') &&   /* 25 */
514     isdigit (string[6])  &&
515     ' ' ==   string[7]   &&
516     isupper (string[8])  &&  /* Dec */
517     islower (string[9])  &&
518     islower (string[10]) &&
519     ' ' ==   string[11]  &&
520     isdigit (string[12]) &&  /* 2001 */
521     isdigit (string[13]) &&
522     isdigit (string[14]) &&
523     isdigit (string[15]) &&
524     ' ' ==   string[16]  &&
525     isdigit (string[17]) &&  /* 02: */
526     isdigit (string[18]) &&
527     ':' ==   string[19]  &&
528     isdigit (string[20]) &&  /* 59: */
529     isdigit (string[21]) &&
530     ':' ==   string[22]  &&
531     isdigit (string[23]) &&  /* 03 */
532     isdigit (string[24]) &&
533     ' ' ==   string[25]  &&
534     'G' ==   string[26]  &&  /* GMT */
535     'M' ==   string[27]  &&  /* GMT */
536     'T' ==   string[28]) {
538     tm.tm_sec  = 10 * (string[23]-'0') + (string[24]-'0');
539     tm.tm_min  = 10 * (string[20]-'0') + (string[21]-'0');
540     tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
541     tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
542     tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
543       !strncmp (string+8, "Feb", 3) ? 1 :
544       !strncmp (string+8, "Mar", 3) ? 2 :
545       !strncmp (string+8, "Apr", 3) ? 3 :
546       !strncmp (string+8, "May", 3) ? 4 :
547       !strncmp (string+8, "Jun", 3) ? 5 :
548       !strncmp (string+8, "Jul", 3) ? 6 :
549       !strncmp (string+8, "Aug", 3) ? 7 :
550       !strncmp (string+8, "Sep", 3) ? 8 :
551       !strncmp (string+8, "Oct", 3) ? 9 :
552       !strncmp (string+8, "Nov", 3) ? 10 :
553       !strncmp (string+8, "Dec", 3) ? 11 :
554       -1);
555     tm.tm_year = ((1000 * (string[12]-'0') +
556       100 * (string[13]-'0') +
557       10 * (string[14]-'0') +
558       (string[15]-'0'))
559       - 1900);
561     tm.tm_isdst = 0;  /* GMT is never in DST, right? */
563     if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
564       return 0;
566     /* 
567     This is actually wrong: we need to subtract the local timezone
568     offset from GMT from this value.  But, that's ok in this usage,
569     because we only comparing these two GMT dates against each other,
570     so it doesn't matter what time zone we parse them in.
571     */
573     t = mktime (&tm);
574     if (t == (time_t) -1) t = 0;
576     if (verbose) {
577       const char *s = string;
578       while (*s && *s != '\r' && *s != '\n')
579       fputc (*s++, stdout);
580       printf (" ==> %lu\n", (unsigned long) t);
581     }
583     return t;
585   } else {
586     return 0;
587   }
592 static void
593 check_document_dates (const char *headers)
595   const char *s;
596   char *server_date = 0;
597   char *document_date = 0;
599   s = headers;
600   while (*s) {
601     const char *field = s;
602     const char *value = 0;
604     /* Find the end of the header field */
605     while (*s && !isspace(*s) && *s != ':')
606       s++;
608     /* Remember the header value, if any. */
609     if (*s == ':')
610       value = ++s;
612     /* Skip to the end of the header, including continuation lines. */
613     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
614       s++;
615     s++;
617     /* Process this header. */
618     if (value && value > field+2) {
619       char *ff = (char *) malloc (value-field);
620       char *ss = ff;
621       while (field < value-1)
622         *ss++ = tolower(*field++);
623       *ss++ = 0;
625       if (!strcmp (ff, "date") || !strcmp (ff, "last-modified")) {
626         const char *e;
627         while (*value && isspace (*value))
628           value++;
629         for (e = value; *e && *e != '\r' && *e != '\n'; e++)
630           ;
631         ss = (char *) malloc (e - value + 1);
632         strncpy (ss, value, e - value);
633         ss[e - value] = 0;
634         if (!strcmp (ff, "date")) {
635           if (server_date) free (server_date);
636           server_date = ss;
637         } else {
638           if (document_date) free (document_date);
639           document_date = ss;
640         }
641       }
642       free (ff);
643     }
644   }
646   /* Done parsing the body.  Now check the dates we (hopefully) parsed.  */
647   if (!server_date || !*server_date) {
648     die (STATE_UNKNOWN, _("Server date unknown\n"));
649   } else if (!document_date || !*document_date) {
650     die (STATE_CRITICAL, _("Document modification date unknown\n"));
651   } else {
652     time_t srv_data = parse_time_string (server_date);
653     time_t doc_data = parse_time_string (document_date);
655     if (srv_data <= 0) {
656       die (STATE_CRITICAL, _("CRITICAL - Server date \"%100s\" unparsable"), server_date);
657     } else if (doc_data <= 0) {
658       die (STATE_CRITICAL, _("CRITICAL - Document date \"%100s\" unparsable"), document_date);
659     } else if (doc_data > srv_data + 30) {
660       die (STATE_CRITICAL, _("CRITICAL - Document is %d seconds in the future\n"), (int)doc_data - (int)srv_data);
661     } else if (doc_data < srv_data - maximum_age) {
662     int n = (srv_data - doc_data);
663     if (n > (60 * 60 * 24 * 2))
664       die (STATE_CRITICAL,
665         _("CRITICAL - Last modified %.1f days ago\n"),
666         ((float) n) / (60 * 60 * 24));
667   else
668     die (STATE_CRITICAL,
669         _("CRITICAL - Last modified %d:%02d:%02d ago\n"),
670         n / (60 * 60), (n / 60) % 60, n % 60);
671     }
673     free (server_date);
674     free (document_date);
675   }
678 int
679 get_content_length (const char *headers)
681   const char *s;
682   int content_length = 0;
684   s = headers;
685   while (*s) {
686     const char *field = s;
687     const char *value = 0;
689     /* Find the end of the header field */
690     while (*s && !isspace(*s) && *s != ':')
691       s++;
693     /* Remember the header value, if any. */
694     if (*s == ':')
695       value = ++s;
697     /* Skip to the end of the header, including continuation lines. */
698     while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
699       s++;
700     s++;
702     /* Process this header. */
703     if (value && value > field+2) {
704       char *ff = (char *) malloc (value-field);
705       char *ss = ff;
706       while (field < value-1)
707         *ss++ = tolower(*field++);
708       *ss++ = 0;
710       if (!strcmp (ff, "content-length")) {
711         const char *e;
712         while (*value && isspace (*value))
713           value++;
714         for (e = value; *e && *e != '\r' && *e != '\n'; e++)
715           ;
716         ss = (char *) malloc (e - value + 1);
717         strncpy (ss, value, e - value);
718         ss[e - value] = 0;
719         content_length = atoi(ss);
720         free (ss);
721       }
722       free (ff);
723     }
724   }
725   return (content_length);
728 int
729 check_http (void)
731   char *msg;
732   char *status_line;
733   char *status_code;
734   char *header;
735   char *page;
736   char *auth;
737   int http_status;
738   int i = 0;
739   size_t pagesize = 0;
740   char *full_page;
741   char *buf;
742   char *pos;
743   long microsec;
744   double elapsed_time;
745   int page_len = 0;
746   int result = STATE_UNKNOWN;
748   /* try to connect to the host at the given port number */
749   if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
750     die (STATE_CRITICAL, _("Unable to open TCP socket\n"));
751 #ifdef HAVE_SSL
752   if (use_ssl == TRUE) {
753     np_net_ssl_init(sd);
754     if (check_cert == TRUE) {
755       result = np_net_ssl_check_cert(days_till_exp);
756       np_net_ssl_cleanup();
757       if(sd) close(sd);
758       return result;
759     }
760   }
761 #endif /* HAVE_SSL */
763   asprintf (&buf, "%s %s HTTP/1.0\r\n%s\r\n", http_method, server_url, user_agent);
765   /* optionally send the host header info */
766   if (host_name)
767     asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
769   /* optionally send any other header tag */
770   if (http_opt_headers) {
771     for ((pos = strtok(http_opt_headers, INPUT_DELIMITER)); pos; (pos = strtok(NULL, INPUT_DELIMITER)))
772       asprintf (&buf, "%s%s\r\n", buf, pos);
773   }
775   /* optionally send the authentication info */
776   if (strlen(user_auth)) {
777     auth = base64 (user_auth, strlen (user_auth));
778     asprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
779   }
781   /* either send http POST data */
782   if (http_post_data) {
783     if (http_content_type) {
784       asprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
785     } else {
786       asprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
787     }
788     
789     asprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
790     asprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
791   }
792   else {
793     /* or just a newline so the server knows we're done with the request */
794     asprintf (&buf, "%s%s", buf, CRLF);
795   }
797   if (verbose) printf ("%s\n", buf);
798   my_send (buf, strlen (buf));
800   /* fetch the page */
801   full_page = strdup("");
802   while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
803     buffer[i] = '\0';
804     asprintf (&full_page, "%s%s", full_page, buffer);
805     pagesize += i;
807                 if (no_body && document_headers_done (full_page)) {
808                   i = 0;
809                   break;
810                 }
811   }
813   if (i < 0 && errno != ECONNRESET) {
814 #ifdef HAVE_SSL
815     /*
816     if (use_ssl) {
817       sslerr=SSL_get_error(ssl, i);
818       if ( sslerr == SSL_ERROR_SSL ) {
819         die (STATE_WARNING, _("Client Certificate Required\n"));
820       } else {
821         die (STATE_CRITICAL, _("Error on receive\n"));
822       }
823     }
824     else {
825     */
826 #endif
827       die (STATE_CRITICAL, _("Error on receive\n"));
828 #ifdef HAVE_SSL
829       /* XXX
830     }
831     */
832 #endif
833   }
835   /* return a CRITICAL status if we couldn't read any data */
836   if (pagesize == (size_t) 0)
837     die (STATE_CRITICAL, _("No data received %s\n"), timestamp);
839   /* close the connection */
840 #ifdef HAVE_SSL
841   np_net_ssl_cleanup();
842 #endif
843   if(sd) close(sd);
845   /* reset the alarm */
846   alarm (0);
848   /* leave full_page untouched so we can free it later */
849   page = full_page;
851   if (verbose)
852     printf ("%s://%s:%d%s is %d characters\n",
853       use_ssl ? "https" : "http", server_address,
854       server_port, server_url, (int)pagesize);
856   /* find status line and null-terminate it */
857   status_line = page;
858   page += (size_t) strcspn (page, "\r\n");
859   pos = page;
860   page += (size_t) strspn (page, "\r\n");
861   status_line[strcspn(status_line, "\r\n")] = 0;
862   strip (status_line);
863   if (verbose)
864     printf ("STATUS: %s\n", status_line);
866   /* find header info and null-terminate it */
867   header = page;
868   while (strcspn (page, "\r\n") > 0) {
869     page += (size_t) strcspn (page, "\r\n");
870     pos = page;
871     if ((strspn (page, "\r") == 1 && strspn (page, "\r\n") >= 2) ||
872         (strspn (page, "\n") == 1 && strspn (page, "\r\n") >= 2))
873       page += (size_t) 2;
874     else
875       page += (size_t) 1;
876   }
877   page += (size_t) strspn (page, "\r\n");
878   header[pos - header] = 0;
879   if (verbose)
880     printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header,
881                 (no_body ? "  [[ skipped ]]" : page));
883   /* make sure the status line matches the response we are looking for */
884   if (!strstr (status_line, server_expect)) {
885     if (server_port == HTTP_PORT)
886       asprintf (&msg,
887                 _("Invalid HTTP response received from host\n"));
888     else
889       asprintf (&msg,
890                 _("Invalid HTTP response received from host on port %d\n"),
891                 server_port);
892     die (STATE_CRITICAL, "%s", msg);
893   }
895   /* Exit here if server_expect was set by user and not default */
896   if ( server_expect_yn  )  {
897     asprintf (&msg,
898               _("HTTP OK: Status line output matched \"%s\"\n"),
899               server_expect);
900     if (verbose)
901       printf ("%s\n",msg);
902   }
903   else {
904     /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
905     /* HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
906     /* Status-Code = 3 DIGITS */
908     status_code = strchr (status_line, ' ') + sizeof (char);
909     if (strspn (status_code, "1234567890") != 3)
910       die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line);
912     http_status = atoi (status_code);
914     /* check the return code */
916     if (http_status >= 600 || http_status < 100)
917       die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
919     /* server errors result in a critical state */
920     else if (http_status >= 500)
921       die (STATE_CRITICAL, _("HTTP CRITICAL: %s\n"), status_line);
923     /* client errors result in a warning state */
924     else if (http_status >= 400)
925       die (STATE_WARNING, _("HTTP WARNING: %s\n"), status_line);
927     /* check redirected page if specified */
928     else if (http_status >= 300) {
930       if (onredirect == STATE_DEPENDENT)
931         redir (header, status_line);
932       else if (onredirect == STATE_UNKNOWN)
933         printf (_("UNKNOWN"));
934       else if (onredirect == STATE_OK)
935         printf (_("OK"));
936       else if (onredirect == STATE_WARNING)
937         printf (_("WARNING"));
938       else if (onredirect == STATE_CRITICAL)
939         printf (_("CRITICAL"));
940       microsec = deltime (tv);
941       elapsed_time = (double)microsec / 1.0e6;
942       die (onredirect,
943            _(" - %s - %.3f second response time %s%s|%s %s\n"),
944            status_line, elapsed_time, timestamp,
945            (display_html ? "</A>" : ""),
946            perfd_time (elapsed_time), perfd_size (pagesize));
947     } /* end if (http_status >= 300) */
949   } /* end else (server_expect_yn)  */
950     
951         if (maximum_age >= 0) {
952           check_document_dates (header);
953         }
955   /* check elapsed time */
956   microsec = deltime (tv);
957   elapsed_time = (double)microsec / 1.0e6;
958   asprintf (&msg,
959             _("HTTP WARNING: %s - %.3f second response time %s%s|%s %s\n"),
960             status_line, elapsed_time, timestamp,
961             (display_html ? "</A>" : ""),
962             perfd_time (elapsed_time), perfd_size (pagesize));
963   if (check_critical_time == TRUE && elapsed_time > critical_time)
964     die (STATE_CRITICAL, "%s", msg);
965   if (check_warning_time == TRUE && elapsed_time > warning_time)
966     die (STATE_WARNING, "%s", msg);
968   /* Page and Header content checks go here */
969   /* these checks should be last */
971   if (strlen (string_expect)) {
972     if (strstr (page, string_expect)) {
973       printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
974               status_line, elapsed_time,
975               timestamp, (display_html ? "</A>" : ""),
976               perfd_time (elapsed_time), perfd_size (pagesize));
977       exit (STATE_OK);
978     }
979     else {
980       printf (_("CRITICAL - string not found%s|%s %s\n"),
981               (display_html ? "</A>" : ""),
982               perfd_time (elapsed_time), perfd_size (pagesize));
983       exit (STATE_CRITICAL);
984     }
985   }
986 #ifdef HAVE_REGEX_H
987   if (strlen (regexp)) {
988     errcode = regexec (&preg, page, REGS, pmatch, 0);
989     if (errcode == 0) {
990       printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
991               status_line, elapsed_time,
992               timestamp, (display_html ? "</A>" : ""),
993               perfd_time (elapsed_time), perfd_size (pagesize));
994       exit (STATE_OK);
995     }
996     else {
997       if (errcode == REG_NOMATCH) {
998         printf (_("CRITICAL - pattern not found%s|%s %s\n"),
999                 (display_html ? "</A>" : ""),
1000                 perfd_time (elapsed_time), perfd_size (pagesize));
1001         exit (STATE_CRITICAL);
1002       }
1003       else {
1004         regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
1005         printf (_("CRITICAL - Execute Error: %s\n"), errbuf);
1006         exit (STATE_CRITICAL);
1007       }
1008     }
1009   }
1010 #endif
1012   /* make sure the page is of an appropriate size */
1013   /* page_len = get_content_length(header); */
1014   page_len = pagesize;
1015   if ((max_page_len > 0) && (page_len > max_page_len)) {
1016     printf (_("HTTP WARNING: page size %d too large%s|%s\n"),
1017       page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1018     exit (STATE_WARNING);
1019   } else if ((min_page_len > 0) && (page_len < min_page_len)) {
1020     printf (_("HTTP WARNING: page size %d too small%s|%s\n"),
1021       page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1022     exit (STATE_WARNING);
1023   }
1024   /* We only get here if all tests have been passed */
1025   asprintf (&msg, _("HTTP OK %s - %d bytes in %.3f seconds %s%s|%s %s\n"),
1026             status_line, page_len, elapsed_time,
1027             timestamp, (display_html ? "</A>" : ""),
1028             perfd_time (elapsed_time), perfd_size (page_len));
1029   die (STATE_OK, "%s", msg);
1030   return STATE_UNKNOWN;
1035 /* per RFC 2396 */
1036 #define HDR_LOCATION "%*[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]: "
1037 #define URI_HTTP "%[HTPShtps]://"
1038 #define URI_HOST "%[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1039 #define URI_PORT ":%[0123456789]"
1040 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1041 #define HD1 URI_HTTP URI_HOST URI_PORT URI_PATH
1042 #define HD2 URI_HTTP URI_HOST URI_PATH
1043 #define HD3 URI_HTTP URI_HOST URI_PORT
1044 #define HD4 URI_HTTP URI_HOST
1045 #define HD5 URI_PATH
1047 void
1048 redir (char *pos, char *status_line)
1050   int i = 0;
1051   char *x;
1052   char xx[2];
1053   char type[6];
1054   char *addr;
1055   char port[6];
1056   char *url;
1058   addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
1059   if (addr == NULL)
1060     die (STATE_UNKNOWN, _("Could not allocate addr\n"));
1061   
1062   url = malloc (strcspn (pos, "\r\n"));
1063   if (url == NULL)
1064     die (STATE_UNKNOWN, _("Could not allocate url\n"));
1066   while (pos) {
1068     if (sscanf (pos, "%[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]:%n", xx, &i) < 1) {
1070       pos += (size_t) strcspn (pos, "\r\n");
1071       pos += (size_t) strspn (pos, "\r\n");
1072       if (strlen(pos) == 0) 
1073         die (STATE_UNKNOWN,
1074              _("UNKNOWN - Could not find redirect location - %s%s\n"),
1075              status_line, (display_html ? "</A>" : ""));
1076       continue;
1077     }
1079     pos += i;
1080     pos += strspn (pos, " \t\r\n");
1082     url = realloc (url, strcspn (pos, "\r\n"));
1083     if (url == NULL)
1084       die (STATE_UNKNOWN, _("could not allocate url\n"));
1086     /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
1087     if (sscanf (pos, HD1, type, addr, port, url) == 4) {
1088       use_ssl = server_type_check (type);
1089       i = atoi (port);
1090     }
1092     /* URI_HTTP URI_HOST URI_PATH */
1093     else if (sscanf (pos, HD2, type, addr, url) == 3 ) { 
1094       use_ssl = server_type_check (type);
1095       i = server_port_check (use_ssl);
1096     }
1098     /* URI_HTTP URI_HOST URI_PORT */
1099     else if(sscanf (pos, HD3, type, addr, port) == 3) {
1100       strcpy (url, HTTP_URL);
1101       use_ssl = server_type_check (type);
1102       i = atoi (port);
1103     }
1105     /* URI_HTTP URI_HOST */
1106     else if(sscanf (pos, HD4, type, addr) == 2) {
1107       strcpy (url, HTTP_URL);
1108       use_ssl = server_type_check (type);
1109       i = server_port_check (use_ssl);
1110     }
1112     /* URI_PATH */
1113     else if (sscanf (pos, HD5, url) == 1) {
1114       /* relative url */
1115       if ((url[0] != '/')) {
1116         if ((x = strrchr(server_url, '/')))
1117           *x = '\0';
1118         asprintf (&url, "%s/%s", server_url, url);
1119       }
1120       i = server_port;
1121       strcpy (type, server_type);
1122       strcpy (addr, host_name);
1123     }           
1125     else {
1126       die (STATE_UNKNOWN,
1127            _("UNKNOWN - Could not parse redirect location - %s%s\n"),
1128            pos, (display_html ? "</A>" : ""));
1129     }
1131     break;
1133   } /* end while (pos) */
1135   if (++redir_depth > max_depth)
1136     die (STATE_WARNING,
1137          _("WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
1138          max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
1140   if (server_port==i &&
1141       !strcmp(server_address, addr) &&
1142       (host_name && !strcmp(host_name, addr)) &&
1143       !strcmp(server_url, url))
1144     die (STATE_WARNING,
1145          _("WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
1146          type, addr, i, url, (display_html ? "</A>" : ""));
1148   server_port = i;
1149   strcpy (server_type, type);
1151   free (host_name);
1152   host_name = strdup (addr);
1154   free (server_address);
1155   server_address = strdup (addr);
1157   free (server_url);
1158   server_url = strdup (url);
1160   check_http ();
1165 int
1166 server_type_check (const char *type)
1168   if (strcmp (type, "https"))
1169     return FALSE;
1170   else
1171     return TRUE;
1174 int
1175 server_port_check (int ssl_flag)
1177   if (ssl_flag)
1178     return HTTPS_PORT;
1179   else
1180     return HTTP_PORT;
1183 char *perfd_time (double elapsed_time)
1185   return fperfdata ("time", elapsed_time, "s",
1186             check_warning_time, warning_time,
1187             check_critical_time, critical_time,
1188                    TRUE, 0, FALSE, 0);
1193 char *perfd_size (int page_len)
1195   return perfdata ("size", page_len, "B",
1196             (min_page_len>0?TRUE:FALSE), min_page_len,
1197             (min_page_len>0?TRUE:FALSE), 0,
1198             TRUE, 0, FALSE, 0);
1201 void
1202 print_help (void)
1204   print_revision (progname, revision);
1206   printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
1207   printf (COPYRIGHT, copyright, email);
1209   printf (_("\
1210 This plugin tests the HTTP service on the specified host. It can test\n\
1211 normal (http) and secure (https) servers, follow redirects, search for\n\
1212 strings and regular expressions, check connection times, and report on\n\
1213 certificate expiration times."));
1215   printf ("\n\n");
1217   print_usage ();
1219   printf (_("NOTE: One or both of -H and -I must be specified"));
1221   printf ("\n");
1223   printf (_(UT_HELP_VRSN));
1225   printf (_("\
1226  -H, --hostname=ADDRESS\n\
1227     Host name argument for servers using host headers (virtual host)\n\
1228     Append a port to include it in the header (eg: example.com:5000)\n\
1229  -I, --IP-address=ADDRESS\n\
1230    IP address or name (use numeric address if possible to bypass DNS lookup).\n\
1231  -p, --port=INTEGER\n\
1232    Port number (default: %d)\n"), HTTP_PORT);
1234   printf (_(UT_IPv46));
1236 #ifdef HAVE_SSL
1237   printf (_("\
1238  -S, --ssl\n\
1239     Connect via SSL\n\
1240  -C, --certificate=INTEGER\n\
1241     Minimum number of days a certificate has to be valid.\n\
1242     (when this option is used the url is not checked.)\n"));
1243 #endif
1245   printf (_("\
1246  -e, --expect=STRING\n\
1247    String to expect in first (status) line of server response (default: %s)\n\
1248    If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)\n\
1249  -s, --string=STRING\n\
1250    String to expect in the content\n\
1251  -u, --url=PATH\n\
1252    URL to GET or POST (default: /)\n\
1253  -P, --post=STRING\n\
1254    URL encoded http POST data\n\
1255  -N, --no-body\n\
1256    Don't wait for document body: stop reading after headers.\n\
1257    (Note that this still does an HTTP GET or POST, not a HEAD.)\n\
1258  -M, --max-age=SECONDS\n\
1259    Warn if document is more than SECONDS old. the number can also be of \n\
1260    the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days.\n\
1261  -T, --content-type=STRING\n\
1262    specify Content-Type header media type when POSTing\n"), HTTP_EXPECT);
1264 #ifdef HAVE_REGEX_H
1265   printf (_("\
1266  -l, --linespan\n\
1267     Allow regex to span newlines (must precede -r or -R)\n\
1268  -r, --regex, --ereg=STRING\n\
1269     Search page for regex STRING\n\
1270  -R, --eregi=STRING\n\
1271     Search page for case-insensitive regex STRING\n"));
1272 #endif
1274   printf (_("\
1275  -a, --authorization=AUTH_PAIR\n\
1276    Username:password on sites with basic authentication\n\
1277  -A, --useragent=STRING\n\
1278    String to be sent in http header as \"User Agent\"\n\
1279  -k, --header=STRING\n\
1280    Any other tags to be sent in http header, separated by semicolon\n\
1281  -L, --link=URL\n\
1282    Wrap output in HTML link (obsoleted by urlize)\n\
1283  -f, --onredirect=<ok|warning|critical|follow>\n\
1284    How to handle redirected pages\n\
1285  -m, --pagesize=INTEGER<:INTEGER>\n\
1286    Minimum page size required (bytes) : Maximum page size required (bytes)\n"));
1288   printf (_(UT_WARN_CRIT));
1290   printf (_(UT_TIMEOUT), DEFAULT_SOCKET_TIMEOUT);
1292   printf (_(UT_VERBOSE));
1294           printf (_("\
1295 This plugin will attempt to open an HTTP connection with the host. Successful\n\
1296 connects return STATE_OK, refusals and timeouts return STATE_CRITICAL, other\n\
1297 errors return STATE_UNKNOWN.  Successful connects, but incorrect reponse\n\
1298 messages from the host result in STATE_WARNING return values.  If you are\n\
1299 checking a virtual server that uses 'host headers' you must supply the FQDN\n\
1300 (fully qualified domain name) as the [host_name] argument.\n"));
1302 #ifdef HAVE_SSL
1303   printf (_("\n\
1304 This plugin can also check whether an SSL enabled web server is able to\n\
1305 serve content (optionally within a specified time) or whether the X509 \n\
1306 certificate is still valid for the specified number of days.\n"));
1307   printf (_("\n\
1308 CHECK CONTENT: check_http -w 5 -c 10 --ssl www.verisign.com\n\n\
1309 When the 'www.verisign.com' server returns its content within 5 seconds, a\n\
1310 STATE_OK will be returned. When the server returns its content but exceeds\n\
1311 the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,\n\
1312 a STATE_CRITICAL will be returned.\n\n"));
1314   printf (_("\
1315 CHECK CERTIFICATE: check_http www.verisign.com -C 14\n\n\
1316 When the certificate of 'www.verisign.com' is valid for more than 14 days, a\n\
1317 STATE_OK is returned. When the certificate is still valid, but for less than\n\
1318 14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when\n\
1319 the certificate is expired.\n"));
1320 #endif
1322   printf (_(UT_SUPPORT));
1328 void
1329 print_usage (void)
1331   printf (_("Usage:"));
1332   printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
1333   printf ("       [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L]\n");
1334   printf ("       [-a auth] [-f <ok | warn | critcal | follow>] [-e <expect>]\n");
1335   printf ("       [-s string] [-l] [-r <regex> | -R <case-insensitive regex>] [-P string]\n");
1336   printf ("       [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>] [-A string] [-k string]\n");