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