Code

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