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$
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)
120 {
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;
151 }
155 /* process command-line arguments */
156 int
157 process_arguments (int argc, char **argv)
158 {
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;
440 }
444 /* written by lauri alanko */
445 static char *
446 base64 (const char *bin, size_t len)
447 {
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;
483 }
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)
490 {
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;
503 }
505 static time_t
506 parse_time_string (const char *string)
507 {
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 }
594 }
598 static void
599 check_document_dates (const char *headers)
600 {
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 }
682 }
684 int
685 get_content_length (const char *headers)
686 {
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);
732 }
734 int
735 check_http (void)
736 {
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 }
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) */
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;
1040 }
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)
1058 {
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"));
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 ();
1170 }
1174 int
1175 server_type_check (const char *type)
1176 {
1177 if (strcmp (type, "https"))
1178 return FALSE;
1179 else
1180 return TRUE;
1181 }
1183 int
1184 server_port_check (int ssl_flag)
1185 {
1186 if (ssl_flag)
1187 return HTTPS_PORT;
1188 else
1189 return HTTP_PORT;
1190 }
1192 char *perfd_time (double elapsed_time)
1193 {
1194 return fperfdata ("time", elapsed_time, "s",
1195 check_warning_time, warning_time,
1196 check_critical_time, critical_time,
1197 TRUE, 0, FALSE, 0);
1198 }
1202 char *perfd_size (int page_len)
1203 {
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);
1208 }
1210 void
1211 print_help (void)
1212 {
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));
1333 }
1337 void
1338 print_usage (void)
1339 {
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");
1346 }