Code

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