Code

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