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 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;
750 /* try to connect to the host at the given port number */
751 if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
752 die (STATE_CRITICAL, _("Unable to open TCP socket\n"));
753 #ifdef HAVE_SSL
754 if (use_ssl == TRUE) {
755 np_net_ssl_init(sd);
756 if (check_cert == TRUE) {
757 result = np_net_ssl_check_cert(days_till_exp);
758 np_net_ssl_cleanup();
759 if(sd) close(sd);
760 return result;
761 }
762 }
763 #endif /* HAVE_SSL */
765 asprintf (&buf, "%s %s HTTP/1.0\r\n%s\r\n", http_method, server_url, user_agent);
767 /* optionally send the host header info */
768 if (host_name)
769 asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
771 /* optionally send any other header tag */
772 if (http_opt_headers) {
773 for ((pos = strtok(http_opt_headers, INPUT_DELIMITER)); pos; (pos = strtok(NULL, INPUT_DELIMITER)))
774 asprintf (&buf, "%s%s\r\n", buf, pos);
775 }
777 /* optionally send the authentication info */
778 if (strlen(user_auth)) {
779 auth = base64 (user_auth, strlen (user_auth));
780 asprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
781 }
783 /* either send http POST data */
784 if (http_post_data) {
785 if (http_content_type) {
786 asprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
787 } else {
788 asprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
789 }
791 asprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
792 asprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
793 }
794 else {
795 /* or just a newline so the server knows we're done with the request */
796 asprintf (&buf, "%s%s", buf, CRLF);
797 }
799 if (verbose) printf ("%s\n", buf);
800 my_send (buf, strlen (buf));
802 /* fetch the page */
803 full_page = strdup("");
804 while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
805 buffer[i] = '\0';
806 asprintf (&full_page, "%s%s", full_page, buffer);
807 pagesize += i;
809 if (no_body && document_headers_done (full_page)) {
810 i = 0;
811 break;
812 }
813 }
815 if (i < 0 && errno != ECONNRESET) {
816 #ifdef HAVE_SSL
817 /*
818 if (use_ssl) {
819 sslerr=SSL_get_error(ssl, i);
820 if ( sslerr == SSL_ERROR_SSL ) {
821 die (STATE_WARNING, _("Client Certificate Required\n"));
822 } else {
823 die (STATE_CRITICAL, _("Error on receive\n"));
824 }
825 }
826 else {
827 */
828 #endif
829 die (STATE_CRITICAL, _("Error on receive\n"));
830 #ifdef HAVE_SSL
831 /* XXX
832 }
833 */
834 #endif
835 }
837 /* return a CRITICAL status if we couldn't read any data */
838 if (pagesize == (size_t) 0)
839 die (STATE_CRITICAL, _("No data received %s\n"), timestamp);
841 /* close the connection */
842 #ifdef HAVE_SSL
843 np_net_ssl_cleanup();
844 #endif
845 if(sd) close(sd);
847 /* reset the alarm */
848 alarm (0);
850 /* leave full_page untouched so we can free it later */
851 page = full_page;
853 if (verbose)
854 printf ("%s://%s:%d%s is %d characters\n",
855 use_ssl ? "https" : "http", server_address,
856 server_port, server_url, (int)pagesize);
858 /* find status line and null-terminate it */
859 status_line = page;
860 page += (size_t) strcspn (page, "\r\n");
861 pos = page;
862 page += (size_t) strspn (page, "\r\n");
863 status_line[strcspn(status_line, "\r\n")] = 0;
864 strip (status_line);
865 if (verbose)
866 printf ("STATUS: %s\n", status_line);
868 /* find header info and null-terminate it */
869 header = page;
870 while (strcspn (page, "\r\n") > 0) {
871 page += (size_t) strcspn (page, "\r\n");
872 pos = page;
873 if ((strspn (page, "\r") == 1 && strspn (page, "\r\n") >= 2) ||
874 (strspn (page, "\n") == 1 && strspn (page, "\r\n") >= 2))
875 page += (size_t) 2;
876 else
877 page += (size_t) 1;
878 }
879 page += (size_t) strspn (page, "\r\n");
880 header[pos - header] = 0;
881 if (verbose)
882 printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header,
883 (no_body ? " [[ skipped ]]" : page));
885 /* make sure the status line matches the response we are looking for */
886 if (!strstr (status_line, server_expect)) {
887 if (server_port == HTTP_PORT)
888 asprintf (&msg,
889 _("Invalid HTTP response received from host\n"));
890 else
891 asprintf (&msg,
892 _("Invalid HTTP response received from host on port %d\n"),
893 server_port);
894 die (STATE_CRITICAL, "%s", msg);
895 }
897 /* Exit here if server_expect was set by user and not default */
898 if ( server_expect_yn ) {
899 asprintf (&msg,
900 _("HTTP OK: Status line output matched \"%s\"\n"),
901 server_expect);
902 if (verbose)
903 printf ("%s\n",msg);
904 }
905 else {
906 /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
907 /* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
908 /* Status-Code = 3 DIGITS */
910 status_code = strchr (status_line, ' ') + sizeof (char);
911 if (strspn (status_code, "1234567890") != 3)
912 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line);
914 http_status = atoi (status_code);
916 /* check the return code */
918 if (http_status >= 600 || http_status < 100)
919 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
921 /* server errors result in a critical state */
922 else if (http_status >= 500)
923 die (STATE_CRITICAL, _("HTTP CRITICAL: %s\n"), status_line);
925 /* client errors result in a warning state */
926 else if (http_status >= 400)
927 die (STATE_WARNING, _("HTTP WARNING: %s\n"), status_line);
929 /* check redirected page if specified */
930 else if (http_status >= 300) {
932 if (onredirect == STATE_DEPENDENT)
933 redir (header, status_line);
934 else if (onredirect == STATE_UNKNOWN)
935 printf (_("UNKNOWN"));
936 else if (onredirect == STATE_OK)
937 printf (_("OK"));
938 else if (onredirect == STATE_WARNING)
939 printf (_("WARNING"));
940 else if (onredirect == STATE_CRITICAL)
941 printf (_("CRITICAL"));
942 microsec = deltime (tv);
943 elapsed_time = (double)microsec / 1.0e6;
944 die (onredirect,
945 _(" - %s - %.3f second response time %s%s|%s %s\n"),
946 status_line, elapsed_time, timestamp,
947 (display_html ? "</A>" : ""),
948 perfd_time (elapsed_time), perfd_size (pagesize));
949 } /* end if (http_status >= 300) */
951 } /* end else (server_expect_yn) */
953 if (maximum_age >= 0) {
954 check_document_dates (header);
955 }
957 /* check elapsed time */
958 microsec = deltime (tv);
959 elapsed_time = (double)microsec / 1.0e6;
960 asprintf (&msg,
961 _("HTTP WARNING: %s - %.3f second response time %s%s|%s %s\n"),
962 status_line, elapsed_time, timestamp,
963 (display_html ? "</A>" : ""),
964 perfd_time (elapsed_time), perfd_size (pagesize));
965 if (check_critical_time == TRUE && elapsed_time > critical_time)
966 die (STATE_CRITICAL, "%s", msg);
967 if (check_warning_time == TRUE && elapsed_time > warning_time)
968 die (STATE_WARNING, "%s", msg);
970 /* Page and Header content checks go here */
971 /* these checks should be last */
973 if (strlen (string_expect)) {
974 if (strstr (page, string_expect)) {
975 printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
976 status_line, elapsed_time,
977 timestamp, (display_html ? "</A>" : ""),
978 perfd_time (elapsed_time), perfd_size (pagesize));
979 exit (STATE_OK);
980 }
981 else {
982 printf (_("CRITICAL - string not found%s|%s %s\n"),
983 (display_html ? "</A>" : ""),
984 perfd_time (elapsed_time), perfd_size (pagesize));
985 exit (STATE_CRITICAL);
986 }
987 }
988 #ifdef HAVE_REGEX_H
989 if (strlen (regexp)) {
990 errcode = regexec (&preg, page, REGS, pmatch, 0);
991 if (errcode == 0) {
992 printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
993 status_line, elapsed_time,
994 timestamp, (display_html ? "</A>" : ""),
995 perfd_time (elapsed_time), perfd_size (pagesize));
996 exit (STATE_OK);
997 }
998 else {
999 if (errcode == REG_NOMATCH) {
1000 printf (_("CRITICAL - pattern not found%s|%s %s\n"),
1001 (display_html ? "</A>" : ""),
1002 perfd_time (elapsed_time), perfd_size (pagesize));
1003 exit (STATE_CRITICAL);
1004 }
1005 else {
1006 regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
1007 printf (_("CRITICAL - Execute Error: %s\n"), errbuf);
1008 exit (STATE_CRITICAL);
1009 }
1010 }
1011 }
1012 #endif
1014 /* make sure the page is of an appropriate size */
1015 /* page_len = get_content_length(header); */
1016 page_len = pagesize;
1017 if ((max_page_len > 0) && (page_len > max_page_len)) {
1018 printf (_("HTTP WARNING: page size %d too large%s|%s\n"),
1019 page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1020 exit (STATE_WARNING);
1021 } else if ((min_page_len > 0) && (page_len < min_page_len)) {
1022 printf (_("HTTP WARNING: page size %d too small%s|%s\n"),
1023 page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1024 exit (STATE_WARNING);
1025 }
1026 /* We only get here if all tests have been passed */
1027 asprintf (&msg, _("HTTP OK %s - %d bytes in %.3f seconds %s%s|%s %s\n"),
1028 status_line, page_len, elapsed_time,
1029 timestamp, (display_html ? "</A>" : ""),
1030 perfd_time (elapsed_time), perfd_size (page_len));
1031 die (STATE_OK, "%s", msg);
1032 return STATE_UNKNOWN;
1033 }
1037 /* per RFC 2396 */
1038 #define HDR_LOCATION "%*[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]: "
1039 #define URI_HTTP "%[HTPShtps]://"
1040 #define URI_HOST "%[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1041 #define URI_PORT ":%[0123456789]"
1042 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1043 #define HD1 URI_HTTP URI_HOST URI_PORT URI_PATH
1044 #define HD2 URI_HTTP URI_HOST URI_PATH
1045 #define HD3 URI_HTTP URI_HOST URI_PORT
1046 #define HD4 URI_HTTP URI_HOST
1047 #define HD5 URI_PATH
1049 void
1050 redir (char *pos, char *status_line)
1051 {
1052 int i = 0;
1053 char *x;
1054 char xx[2];
1055 char type[6];
1056 char *addr;
1057 char port[6];
1058 char *url;
1060 addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
1061 if (addr == NULL)
1062 die (STATE_UNKNOWN, _("Could not allocate addr\n"));
1064 url = malloc (strcspn (pos, "\r\n"));
1065 if (url == NULL)
1066 die (STATE_UNKNOWN, _("Could not allocate url\n"));
1068 while (pos) {
1070 if (sscanf (pos, "%[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]:%n", xx, &i) < 1) {
1072 pos += (size_t) strcspn (pos, "\r\n");
1073 pos += (size_t) strspn (pos, "\r\n");
1074 if (strlen(pos) == 0)
1075 die (STATE_UNKNOWN,
1076 _("UNKNOWN - Could not find redirect location - %s%s\n"),
1077 status_line, (display_html ? "</A>" : ""));
1078 continue;
1079 }
1081 pos += i;
1082 pos += strspn (pos, " \t\r\n");
1084 url = realloc (url, strcspn (pos, "\r\n"));
1085 if (url == NULL)
1086 die (STATE_UNKNOWN, _("could not allocate url\n"));
1088 /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
1089 if (sscanf (pos, HD1, type, addr, port, url) == 4) {
1090 use_ssl = server_type_check (type);
1091 i = atoi (port);
1092 }
1094 /* URI_HTTP URI_HOST URI_PATH */
1095 else if (sscanf (pos, HD2, type, addr, url) == 3 ) {
1096 use_ssl = server_type_check (type);
1097 i = server_port_check (use_ssl);
1098 }
1100 /* URI_HTTP URI_HOST URI_PORT */
1101 else if(sscanf (pos, HD3, type, addr, port) == 3) {
1102 strcpy (url, HTTP_URL);
1103 use_ssl = server_type_check (type);
1104 i = atoi (port);
1105 }
1107 /* URI_HTTP URI_HOST */
1108 else if(sscanf (pos, HD4, type, addr) == 2) {
1109 strcpy (url, HTTP_URL);
1110 use_ssl = server_type_check (type);
1111 i = server_port_check (use_ssl);
1112 }
1114 /* URI_PATH */
1115 else if (sscanf (pos, HD5, url) == 1) {
1116 /* relative url */
1117 if ((url[0] != '/')) {
1118 if ((x = strrchr(server_url, '/')))
1119 *x = '\0';
1120 asprintf (&url, "%s/%s", server_url, url);
1121 }
1122 i = server_port;
1123 strcpy (type, server_type);
1124 strcpy (addr, host_name);
1125 }
1127 else {
1128 die (STATE_UNKNOWN,
1129 _("UNKNOWN - Could not parse redirect location - %s%s\n"),
1130 pos, (display_html ? "</A>" : ""));
1131 }
1133 break;
1135 } /* end while (pos) */
1137 if (++redir_depth > max_depth)
1138 die (STATE_WARNING,
1139 _("WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
1140 max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
1142 if (server_port==i &&
1143 !strcmp(server_address, addr) &&
1144 (host_name && !strcmp(host_name, addr)) &&
1145 !strcmp(server_url, url))
1146 die (STATE_WARNING,
1147 _("WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
1148 type, addr, i, url, (display_html ? "</A>" : ""));
1150 server_port = i;
1151 strcpy (server_type, type);
1153 free (host_name);
1154 host_name = strdup (addr);
1156 free (server_address);
1157 server_address = strdup (addr);
1159 free (server_url);
1160 server_url = strdup (url);
1162 check_http ();
1163 }
1167 int
1168 server_type_check (const char *type)
1169 {
1170 if (strcmp (type, "https"))
1171 return FALSE;
1172 else
1173 return TRUE;
1174 }
1176 int
1177 server_port_check (int ssl_flag)
1178 {
1179 if (ssl_flag)
1180 return HTTPS_PORT;
1181 else
1182 return HTTP_PORT;
1183 }
1185 char *perfd_time (double elapsed_time)
1186 {
1187 return fperfdata ("time", elapsed_time, "s",
1188 check_warning_time, warning_time,
1189 check_critical_time, critical_time,
1190 TRUE, 0, FALSE, 0);
1191 }
1195 char *perfd_size (int page_len)
1196 {
1197 return perfdata ("size", page_len, "B",
1198 (min_page_len>0?TRUE:FALSE), min_page_len,
1199 (min_page_len>0?TRUE:FALSE), 0,
1200 TRUE, 0, FALSE, 0);
1201 }
1203 void
1204 print_help (void)
1205 {
1206 print_revision (progname, revision);
1208 printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
1209 printf (COPYRIGHT, copyright, email);
1211 printf (_("\
1212 This plugin tests the HTTP service on the specified host. It can test\n\
1213 normal (http) and secure (https) servers, follow redirects, search for\n\
1214 strings and regular expressions, check connection times, and report on\n\
1215 certificate expiration times."));
1217 printf ("\n\n");
1219 print_usage ();
1221 printf (_("NOTE: One or both of -H and -I must be specified"));
1223 printf ("\n");
1225 printf (_(UT_HELP_VRSN));
1227 printf (_("\
1228 -H, --hostname=ADDRESS\n\
1229 Host name argument for servers using host headers (virtual host)\n\
1230 Append a port to include it in the header (eg: example.com:5000)\n\
1231 -I, --IP-address=ADDRESS\n\
1232 IP address or name (use numeric address if possible to bypass DNS lookup).\n\
1233 -p, --port=INTEGER\n\
1234 Port number (default: %d)\n"), HTTP_PORT);
1236 printf (_(UT_IPv46));
1238 #ifdef HAVE_SSL
1239 printf (_("\
1240 -S, --ssl\n\
1241 Connect via SSL\n\
1242 -C, --certificate=INTEGER\n\
1243 Minimum number of days a certificate has to be valid.\n\
1244 (when this option is used the url is not checked.)\n"));
1245 #endif
1247 printf (_("\
1248 -e, --expect=STRING\n\
1249 String to expect in first (status) line of server response (default: %s)\n\
1250 If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)\n\
1251 -s, --string=STRING\n\
1252 String to expect in the content\n\
1253 -u, --url=PATH\n\
1254 URL to GET or POST (default: /)\n\
1255 -P, --post=STRING\n\
1256 URL encoded http POST data\n\
1257 -N, --no-body\n\
1258 Don't wait for document body: stop reading after headers.\n\
1259 (Note that this still does an HTTP GET or POST, not a HEAD.)\n\
1260 -M, --max-age=SECONDS\n\
1261 Warn if document is more than SECONDS old. the number can also be of \n\
1262 the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days.\n\
1263 -T, --content-type=STRING\n\
1264 specify Content-Type header media type when POSTing\n"), HTTP_EXPECT);
1266 #ifdef HAVE_REGEX_H
1267 printf (_("\
1268 -l, --linespan\n\
1269 Allow regex to span newlines (must precede -r or -R)\n\
1270 -r, --regex, --ereg=STRING\n\
1271 Search page for regex STRING\n\
1272 -R, --eregi=STRING\n\
1273 Search page for case-insensitive regex STRING\n"));
1274 #endif
1276 printf (_("\
1277 -a, --authorization=AUTH_PAIR\n\
1278 Username:password on sites with basic authentication\n\
1279 -A, --useragent=STRING\n\
1280 String to be sent in http header as \"User Agent\"\n\
1281 -k, --header=STRING\n\
1282 Any other tags to be sent in http header, separated by semicolon\n\
1283 -L, --link=URL\n\
1284 Wrap output in HTML link (obsoleted by urlize)\n\
1285 -f, --onredirect=<ok|warning|critical|follow>\n\
1286 How to handle redirected pages\n\
1287 -m, --pagesize=INTEGER<:INTEGER>\n\
1288 Minimum page size required (bytes) : Maximum page size required (bytes)\n"));
1290 printf (_(UT_WARN_CRIT));
1292 printf (_(UT_TIMEOUT), DEFAULT_SOCKET_TIMEOUT);
1294 printf (_(UT_VERBOSE));
1296 printf (_("\
1297 This plugin will attempt to open an HTTP connection with the host. Successful\n\
1298 connects return STATE_OK, refusals and timeouts return STATE_CRITICAL, other\n\
1299 errors return STATE_UNKNOWN. Successful connects, but incorrect reponse\n\
1300 messages from the host result in STATE_WARNING return values. If you are\n\
1301 checking a virtual server that uses 'host headers' you must supply the FQDN\n\
1302 (fully qualified domain name) as the [host_name] argument.\n"));
1304 #ifdef HAVE_SSL
1305 printf (_("\n\
1306 This plugin can also check whether an SSL enabled web server is able to\n\
1307 serve content (optionally within a specified time) or whether the X509 \n\
1308 certificate is still valid for the specified number of days.\n"));
1309 printf (_("\n\
1310 CHECK CONTENT: check_http -w 5 -c 10 --ssl www.verisign.com\n\n\
1311 When the 'www.verisign.com' server returns its content within 5 seconds, a\n\
1312 STATE_OK will be returned. When the server returns its content but exceeds\n\
1313 the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,\n\
1314 a STATE_CRITICAL will be returned.\n\n"));
1316 printf (_("\
1317 CHECK CERTIFICATE: check_http www.verisign.com -C 14\n\n\
1318 When the certificate of 'www.verisign.com' is valid for more than 14 days, a\n\
1319 STATE_OK is returned. When the certificate is still valid, but for less than\n\
1320 14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when\n\
1321 the certificate is expired.\n"));
1322 #endif
1324 printf (_(UT_SUPPORT));
1326 }
1330 void
1331 print_usage (void)
1332 {
1333 printf (_("Usage:"));
1334 printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
1335 printf (" [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L]\n");
1336 printf (" [-a auth] [-f <ok | warn | critcal | follow>] [-e <expect>]\n");
1337 printf (" [-s string] [-l] [-r <regex> | -R <case-insensitive regex>] [-P string]\n");
1338 printf (" [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>] [-A string] [-k string]\n");
1339 }