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 'C': /* Check SSL cert validity */
257 #ifdef HAVE_SSL
258 if (!is_intnonneg (optarg))
259 usage2 (_("Invalid certificate expiration period"), optarg);
260 else {
261 days_till_exp = atoi (optarg);
262 check_cert = TRUE;
263 }
264 /* Fall through to -S option */
265 #endif
266 case 'S': /* use SSL */
267 #ifndef HAVE_SSL
268 usage4 (_("Invalid option - SSL is not available"));
269 #endif
270 use_ssl = TRUE;
271 if (specify_port == FALSE)
272 server_port = HTTPS_PORT;
273 break;
274 case 'f': /* onredirect */
275 if (!strcmp (optarg, "follow"))
276 onredirect = STATE_DEPENDENT;
277 if (!strcmp (optarg, "unknown"))
278 onredirect = STATE_UNKNOWN;
279 if (!strcmp (optarg, "ok"))
280 onredirect = STATE_OK;
281 if (!strcmp (optarg, "warning"))
282 onredirect = STATE_WARNING;
283 if (!strcmp (optarg, "critical"))
284 onredirect = STATE_CRITICAL;
285 if (verbose)
286 printf(_("option f:%d \n"), onredirect);
287 break;
288 /* Note: H, I, and u must be malloc'd or will fail on redirects */
289 case 'H': /* Host Name (virtual host) */
290 host_name = strdup (optarg);
291 if (strstr (optarg, ":"))
292 sscanf (optarg, "%*[^:]:%d", &server_port);
293 break;
294 case 'I': /* Server IP-address */
295 server_address = strdup (optarg);
296 break;
297 case 'u': /* URL path */
298 server_url = strdup (optarg);
299 server_url_length = strlen (server_url);
300 break;
301 case 'p': /* Server port */
302 if (!is_intnonneg (optarg))
303 usage2 (_("Invalid port number"), optarg);
304 else {
305 server_port = atoi (optarg);
306 specify_port = TRUE;
307 }
308 break;
309 case 'a': /* authorization info */
310 strncpy (user_auth, optarg, MAX_INPUT_BUFFER - 1);
311 user_auth[MAX_INPUT_BUFFER - 1] = 0;
312 break;
313 case 'P': /* HTTP POST data in URL encoded format */
314 if (http_method || http_post_data) break;
315 http_method = strdup("POST");
316 http_post_data = strdup (optarg);
317 break;
318 case 's': /* string or substring */
319 strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1);
320 string_expect[MAX_INPUT_BUFFER - 1] = 0;
321 break;
322 case 'e': /* string or substring */
323 strncpy (server_expect, optarg, MAX_INPUT_BUFFER - 1);
324 server_expect[MAX_INPUT_BUFFER - 1] = 0;
325 server_expect_yn = 1;
326 break;
327 case 'T': /* Content-type */
328 asprintf (&http_content_type, "%s", optarg);
329 break;
330 #ifndef HAVE_REGEX_H
331 case 'l': /* linespan */
332 case 'r': /* linespan */
333 case 'R': /* linespan */
334 usage4 (_("Call for regex which was not a compiled option"));
335 break;
336 #else
337 case 'l': /* linespan */
338 cflags &= ~REG_NEWLINE;
339 break;
340 case 'R': /* regex */
341 cflags |= REG_ICASE;
342 case 'r': /* regex */
343 strncpy (regexp, optarg, MAX_RE_SIZE - 1);
344 regexp[MAX_RE_SIZE - 1] = 0;
345 errcode = regcomp (&preg, regexp, cflags);
346 if (errcode != 0) {
347 (void) regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
348 printf (_("Could Not Compile Regular Expression: %s"), errbuf);
349 return ERROR;
350 }
351 break;
352 #endif
353 case '4':
354 address_family = AF_INET;
355 break;
356 case '6':
357 #ifdef USE_IPV6
358 address_family = AF_INET6;
359 #else
360 usage4 (_("IPv6 support not available"));
361 #endif
362 break;
363 case 'v': /* verbose */
364 verbose = TRUE;
365 break;
366 case 'm': /* min_page_length */
367 {
368 char *tmp;
369 if (strchr(optarg, ':') != (char *)NULL) {
370 /* range, so get two values, min:max */
371 tmp = strtok(optarg, ":");
372 if (tmp == NULL) {
373 printf("Bad format: try \"-m min:max\"\n");
374 exit (STATE_WARNING);
375 } else
376 min_page_len = atoi(tmp);
378 tmp = strtok(NULL, ":");
379 if (tmp == NULL) {
380 printf("Bad format: try \"-m min:max\"\n");
381 exit (STATE_WARNING);
382 } else
383 max_page_len = atoi(tmp);
384 } else
385 min_page_len = atoi (optarg);
386 break;
387 }
388 case 'N': /* no-body */
389 no_body = TRUE;
390 break;
391 case 'M': /* max-age */
392 {
393 int L = strlen(optarg);
394 if (L && optarg[L-1] == 'm')
395 maximum_age = atoi (optarg) * 60;
396 else if (L && optarg[L-1] == 'h')
397 maximum_age = atoi (optarg) * 60 * 60;
398 else if (L && optarg[L-1] == 'd')
399 maximum_age = atoi (optarg) * 60 * 60 * 24;
400 else if (L && (optarg[L-1] == 's' ||
401 isdigit (optarg[L-1])))
402 maximum_age = atoi (optarg);
403 else {
404 fprintf (stderr, "unparsable max-age: %s\n", optarg);
405 exit (STATE_WARNING);
406 }
407 }
408 break;
409 }
410 }
412 c = optind;
414 if (server_address == NULL && c < argc)
415 server_address = strdup (argv[c++]);
417 if (host_name == NULL && c < argc)
418 host_name = strdup (argv[c++]);
420 if (server_address == NULL) {
421 if (host_name == NULL)
422 usage4 (_("You must specify a server address or host name"));
423 else
424 server_address = strdup (host_name);
425 }
427 if (check_critical_time && critical_time>(double)socket_timeout)
428 socket_timeout = (int)critical_time + 1;
430 if (http_method == NULL)
431 http_method = strdup ("GET");
433 return TRUE;
434 }
438 /* written by lauri alanko */
439 static char *
440 base64 (const char *bin, size_t len)
441 {
443 char *buf = (char *) malloc ((len + 2) / 3 * 4 + 1);
444 size_t i = 0, j = 0;
446 char BASE64_END = '=';
447 char base64_table[64];
448 strncpy (base64_table, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 64);
450 while (j < len - 2) {
451 buf[i++] = base64_table[bin[j] >> 2];
452 buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)];
453 buf[i++] = base64_table[((bin[j + 1] & 15) << 2) | (bin[j + 2] >> 6)];
454 buf[i++] = base64_table[bin[j + 2] & 63];
455 j += 3;
456 }
458 switch (len - j) {
459 case 1:
460 buf[i++] = base64_table[bin[j] >> 2];
461 buf[i++] = base64_table[(bin[j] & 3) << 4];
462 buf[i++] = BASE64_END;
463 buf[i++] = BASE64_END;
464 break;
465 case 2:
466 buf[i++] = base64_table[bin[j] >> 2];
467 buf[i++] = base64_table[((bin[j] & 3) << 4) | (bin[j + 1] >> 4)];
468 buf[i++] = base64_table[(bin[j + 1] & 15) << 2];
469 buf[i++] = BASE64_END;
470 break;
471 case 0:
472 break;
473 }
475 buf[i] = '\0';
476 return buf;
477 }
481 /* Returns 1 if we're done processing the document body; 0 to keep going */
482 static int
483 document_headers_done (char *full_page)
484 {
485 const char *body;
487 for (body = full_page; *body; body++) {
488 if (!strncmp (body, "\n\n", 2) || !strncmp (body, "\n\r\n", 3))
489 break;
490 }
492 if (!*body)
493 return 0; /* haven't read end of headers yet */
495 full_page[body - full_page] = 0;
496 return 1;
497 }
499 static time_t
500 parse_time_string (const char *string)
501 {
502 struct tm tm;
503 time_t t;
504 memset (&tm, 0, sizeof(tm));
506 /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
508 if (isupper (string[0]) && /* Tue */
509 islower (string[1]) &&
510 islower (string[2]) &&
511 ',' == string[3] &&
512 ' ' == string[4] &&
513 (isdigit(string[5]) || string[5] == ' ') && /* 25 */
514 isdigit (string[6]) &&
515 ' ' == string[7] &&
516 isupper (string[8]) && /* Dec */
517 islower (string[9]) &&
518 islower (string[10]) &&
519 ' ' == string[11] &&
520 isdigit (string[12]) && /* 2001 */
521 isdigit (string[13]) &&
522 isdigit (string[14]) &&
523 isdigit (string[15]) &&
524 ' ' == string[16] &&
525 isdigit (string[17]) && /* 02: */
526 isdigit (string[18]) &&
527 ':' == string[19] &&
528 isdigit (string[20]) && /* 59: */
529 isdigit (string[21]) &&
530 ':' == string[22] &&
531 isdigit (string[23]) && /* 03 */
532 isdigit (string[24]) &&
533 ' ' == string[25] &&
534 'G' == string[26] && /* GMT */
535 'M' == string[27] && /* GMT */
536 'T' == string[28]) {
538 tm.tm_sec = 10 * (string[23]-'0') + (string[24]-'0');
539 tm.tm_min = 10 * (string[20]-'0') + (string[21]-'0');
540 tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
541 tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
542 tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
543 !strncmp (string+8, "Feb", 3) ? 1 :
544 !strncmp (string+8, "Mar", 3) ? 2 :
545 !strncmp (string+8, "Apr", 3) ? 3 :
546 !strncmp (string+8, "May", 3) ? 4 :
547 !strncmp (string+8, "Jun", 3) ? 5 :
548 !strncmp (string+8, "Jul", 3) ? 6 :
549 !strncmp (string+8, "Aug", 3) ? 7 :
550 !strncmp (string+8, "Sep", 3) ? 8 :
551 !strncmp (string+8, "Oct", 3) ? 9 :
552 !strncmp (string+8, "Nov", 3) ? 10 :
553 !strncmp (string+8, "Dec", 3) ? 11 :
554 -1);
555 tm.tm_year = ((1000 * (string[12]-'0') +
556 100 * (string[13]-'0') +
557 10 * (string[14]-'0') +
558 (string[15]-'0'))
559 - 1900);
561 tm.tm_isdst = 0; /* GMT is never in DST, right? */
563 if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
564 return 0;
566 /*
567 This is actually wrong: we need to subtract the local timezone
568 offset from GMT from this value. But, that's ok in this usage,
569 because we only comparing these two GMT dates against each other,
570 so it doesn't matter what time zone we parse them in.
571 */
573 t = mktime (&tm);
574 if (t == (time_t) -1) t = 0;
576 if (verbose) {
577 const char *s = string;
578 while (*s && *s != '\r' && *s != '\n')
579 fputc (*s++, stdout);
580 printf (" ==> %lu\n", (unsigned long) t);
581 }
583 return t;
585 } else {
586 return 0;
587 }
588 }
592 static void
593 check_document_dates (const char *headers)
594 {
595 const char *s;
596 char *server_date = 0;
597 char *document_date = 0;
599 s = headers;
600 while (*s) {
601 const char *field = s;
602 const char *value = 0;
604 /* Find the end of the header field */
605 while (*s && !isspace(*s) && *s != ':')
606 s++;
608 /* Remember the header value, if any. */
609 if (*s == ':')
610 value = ++s;
612 /* Skip to the end of the header, including continuation lines. */
613 while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
614 s++;
615 s++;
617 /* Process this header. */
618 if (value && value > field+2) {
619 char *ff = (char *) malloc (value-field);
620 char *ss = ff;
621 while (field < value-1)
622 *ss++ = tolower(*field++);
623 *ss++ = 0;
625 if (!strcmp (ff, "date") || !strcmp (ff, "last-modified")) {
626 const char *e;
627 while (*value && isspace (*value))
628 value++;
629 for (e = value; *e && *e != '\r' && *e != '\n'; e++)
630 ;
631 ss = (char *) malloc (e - value + 1);
632 strncpy (ss, value, e - value);
633 ss[e - value] = 0;
634 if (!strcmp (ff, "date")) {
635 if (server_date) free (server_date);
636 server_date = ss;
637 } else {
638 if (document_date) free (document_date);
639 document_date = ss;
640 }
641 }
642 free (ff);
643 }
644 }
646 /* Done parsing the body. Now check the dates we (hopefully) parsed. */
647 if (!server_date || !*server_date) {
648 die (STATE_UNKNOWN, _("Server date unknown\n"));
649 } else if (!document_date || !*document_date) {
650 die (STATE_CRITICAL, _("Document modification date unknown\n"));
651 } else {
652 time_t srv_data = parse_time_string (server_date);
653 time_t doc_data = parse_time_string (document_date);
655 if (srv_data <= 0) {
656 die (STATE_CRITICAL, _("CRITICAL - Server date \"%100s\" unparsable"), server_date);
657 } else if (doc_data <= 0) {
658 die (STATE_CRITICAL, _("CRITICAL - Document date \"%100s\" unparsable"), document_date);
659 } else if (doc_data > srv_data + 30) {
660 die (STATE_CRITICAL, _("CRITICAL - Document is %d seconds in the future\n"), (int)doc_data - (int)srv_data);
661 } else if (doc_data < srv_data - maximum_age) {
662 int n = (srv_data - doc_data);
663 if (n > (60 * 60 * 24 * 2))
664 die (STATE_CRITICAL,
665 _("CRITICAL - Last modified %.1f days ago\n"),
666 ((float) n) / (60 * 60 * 24));
667 else
668 die (STATE_CRITICAL,
669 _("CRITICAL - Last modified %d:%02d:%02d ago\n"),
670 n / (60 * 60), (n / 60) % 60, n % 60);
671 }
673 free (server_date);
674 free (document_date);
675 }
676 }
678 int
679 get_content_length (const char *headers)
680 {
681 const char *s;
682 int content_length = 0;
684 s = headers;
685 while (*s) {
686 const char *field = s;
687 const char *value = 0;
689 /* Find the end of the header field */
690 while (*s && !isspace(*s) && *s != ':')
691 s++;
693 /* Remember the header value, if any. */
694 if (*s == ':')
695 value = ++s;
697 /* Skip to the end of the header, including continuation lines. */
698 while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
699 s++;
700 s++;
702 /* Process this header. */
703 if (value && value > field+2) {
704 char *ff = (char *) malloc (value-field);
705 char *ss = ff;
706 while (field < value-1)
707 *ss++ = tolower(*field++);
708 *ss++ = 0;
710 if (!strcmp (ff, "content-length")) {
711 const char *e;
712 while (*value && isspace (*value))
713 value++;
714 for (e = value; *e && *e != '\r' && *e != '\n'; e++)
715 ;
716 ss = (char *) malloc (e - value + 1);
717 strncpy (ss, value, e - value);
718 ss[e - value] = 0;
719 content_length = atoi(ss);
720 free (ss);
721 }
722 free (ff);
723 }
724 }
725 return (content_length);
726 }
728 int
729 check_http (void)
730 {
731 char *msg;
732 char *status_line;
733 char *status_code;
734 char *header;
735 char *page;
736 char *auth;
737 int http_status;
738 int i = 0;
739 size_t pagesize = 0;
740 char *full_page;
741 char *buf;
742 char *pos;
743 long microsec;
744 double elapsed_time;
745 int page_len = 0;
746 int result = STATE_UNKNOWN;
748 /* try to connect to the host at the given port number */
749 if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
750 die (STATE_CRITICAL, _("Unable to open TCP socket\n"));
751 #ifdef HAVE_SSL
752 if (use_ssl == TRUE) {
753 np_net_ssl_init(sd);
754 if (check_cert == TRUE) {
755 result = np_net_ssl_check_cert(days_till_exp);
756 np_net_ssl_cleanup();
757 if(sd) close(sd);
758 return result;
759 }
760 }
761 #endif /* HAVE_SSL */
763 asprintf (&buf, "%s %s HTTP/1.0\r\n%s\r\n", http_method, server_url, user_agent);
765 /* optionally send the host header info */
766 if (host_name)
767 asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
769 /* optionally send any other header tag */
770 if (http_opt_headers) {
771 for ((pos = strtok(http_opt_headers, INPUT_DELIMITER)); pos; (pos = strtok(NULL, INPUT_DELIMITER)))
772 asprintf (&buf, "%s%s\r\n", buf, pos);
773 }
775 /* optionally send the authentication info */
776 if (strlen(user_auth)) {
777 auth = base64 (user_auth, strlen (user_auth));
778 asprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
779 }
781 /* either send http POST data */
782 if (http_post_data) {
783 if (http_content_type) {
784 asprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
785 } else {
786 asprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
787 }
789 asprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
790 asprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
791 }
792 else {
793 /* or just a newline so the server knows we're done with the request */
794 asprintf (&buf, "%s%s", buf, CRLF);
795 }
797 if (verbose) printf ("%s\n", buf);
798 my_send (buf, strlen (buf));
800 /* fetch the page */
801 full_page = strdup("");
802 while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
803 buffer[i] = '\0';
804 asprintf (&full_page, "%s%s", full_page, buffer);
805 pagesize += i;
807 if (no_body && document_headers_done (full_page)) {
808 i = 0;
809 break;
810 }
811 }
813 if (i < 0 && errno != ECONNRESET) {
814 #ifdef HAVE_SSL
815 /*
816 if (use_ssl) {
817 sslerr=SSL_get_error(ssl, i);
818 if ( sslerr == SSL_ERROR_SSL ) {
819 die (STATE_WARNING, _("Client Certificate Required\n"));
820 } else {
821 die (STATE_CRITICAL, _("Error on receive\n"));
822 }
823 }
824 else {
825 */
826 #endif
827 die (STATE_CRITICAL, _("Error on receive\n"));
828 #ifdef HAVE_SSL
829 /* XXX
830 }
831 */
832 #endif
833 }
835 /* return a CRITICAL status if we couldn't read any data */
836 if (pagesize == (size_t) 0)
837 die (STATE_CRITICAL, _("No data received %s\n"), timestamp);
839 /* close the connection */
840 #ifdef HAVE_SSL
841 np_net_ssl_cleanup();
842 #endif
843 if(sd) close(sd);
845 /* reset the alarm */
846 alarm (0);
848 /* leave full_page untouched so we can free it later */
849 page = full_page;
851 if (verbose)
852 printf ("%s://%s:%d%s is %d characters\n",
853 use_ssl ? "https" : "http", server_address,
854 server_port, server_url, (int)pagesize);
856 /* find status line and null-terminate it */
857 status_line = page;
858 page += (size_t) strcspn (page, "\r\n");
859 pos = page;
860 page += (size_t) strspn (page, "\r\n");
861 status_line[strcspn(status_line, "\r\n")] = 0;
862 strip (status_line);
863 if (verbose)
864 printf ("STATUS: %s\n", status_line);
866 /* find header info and null-terminate it */
867 header = page;
868 while (strcspn (page, "\r\n") > 0) {
869 page += (size_t) strcspn (page, "\r\n");
870 pos = page;
871 if ((strspn (page, "\r") == 1 && strspn (page, "\r\n") >= 2) ||
872 (strspn (page, "\n") == 1 && strspn (page, "\r\n") >= 2))
873 page += (size_t) 2;
874 else
875 page += (size_t) 1;
876 }
877 page += (size_t) strspn (page, "\r\n");
878 header[pos - header] = 0;
879 if (verbose)
880 printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header,
881 (no_body ? " [[ skipped ]]" : page));
883 /* make sure the status line matches the response we are looking for */
884 if (!strstr (status_line, server_expect)) {
885 if (server_port == HTTP_PORT)
886 asprintf (&msg,
887 _("Invalid HTTP response received from host\n"));
888 else
889 asprintf (&msg,
890 _("Invalid HTTP response received from host on port %d\n"),
891 server_port);
892 die (STATE_CRITICAL, "%s", msg);
893 }
895 /* Exit here if server_expect was set by user and not default */
896 if ( server_expect_yn ) {
897 asprintf (&msg,
898 _("HTTP OK: Status line output matched \"%s\"\n"),
899 server_expect);
900 if (verbose)
901 printf ("%s\n",msg);
902 }
903 else {
904 /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
905 /* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
906 /* Status-Code = 3 DIGITS */
908 status_code = strchr (status_line, ' ') + sizeof (char);
909 if (strspn (status_code, "1234567890") != 3)
910 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line);
912 http_status = atoi (status_code);
914 /* check the return code */
916 if (http_status >= 600 || http_status < 100)
917 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
919 /* server errors result in a critical state */
920 else if (http_status >= 500)
921 die (STATE_CRITICAL, _("HTTP CRITICAL: %s\n"), status_line);
923 /* client errors result in a warning state */
924 else if (http_status >= 400)
925 die (STATE_WARNING, _("HTTP WARNING: %s\n"), status_line);
927 /* check redirected page if specified */
928 else if (http_status >= 300) {
930 if (onredirect == STATE_DEPENDENT)
931 redir (header, status_line);
932 else if (onredirect == STATE_UNKNOWN)
933 printf (_("UNKNOWN"));
934 else if (onredirect == STATE_OK)
935 printf (_("OK"));
936 else if (onredirect == STATE_WARNING)
937 printf (_("WARNING"));
938 else if (onredirect == STATE_CRITICAL)
939 printf (_("CRITICAL"));
940 microsec = deltime (tv);
941 elapsed_time = (double)microsec / 1.0e6;
942 die (onredirect,
943 _(" - %s - %.3f second response time %s%s|%s %s\n"),
944 status_line, elapsed_time, timestamp,
945 (display_html ? "</A>" : ""),
946 perfd_time (elapsed_time), perfd_size (pagesize));
947 } /* end if (http_status >= 300) */
949 } /* end else (server_expect_yn) */
951 if (maximum_age >= 0) {
952 check_document_dates (header);
953 }
955 /* check elapsed time */
956 microsec = deltime (tv);
957 elapsed_time = (double)microsec / 1.0e6;
958 asprintf (&msg,
959 _("HTTP WARNING: %s - %.3f second response time %s%s|%s %s\n"),
960 status_line, elapsed_time, timestamp,
961 (display_html ? "</A>" : ""),
962 perfd_time (elapsed_time), perfd_size (pagesize));
963 if (check_critical_time == TRUE && elapsed_time > critical_time)
964 die (STATE_CRITICAL, "%s", msg);
965 if (check_warning_time == TRUE && elapsed_time > warning_time)
966 die (STATE_WARNING, "%s", msg);
968 /* Page and Header content checks go here */
969 /* these checks should be last */
971 if (strlen (string_expect)) {
972 if (strstr (page, string_expect)) {
973 printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
974 status_line, elapsed_time,
975 timestamp, (display_html ? "</A>" : ""),
976 perfd_time (elapsed_time), perfd_size (pagesize));
977 exit (STATE_OK);
978 }
979 else {
980 printf (_("CRITICAL - string not found%s|%s %s\n"),
981 (display_html ? "</A>" : ""),
982 perfd_time (elapsed_time), perfd_size (pagesize));
983 exit (STATE_CRITICAL);
984 }
985 }
986 #ifdef HAVE_REGEX_H
987 if (strlen (regexp)) {
988 errcode = regexec (&preg, page, REGS, pmatch, 0);
989 if (errcode == 0) {
990 printf (_("HTTP OK %s - %.3f second response time %s%s|%s %s\n"),
991 status_line, elapsed_time,
992 timestamp, (display_html ? "</A>" : ""),
993 perfd_time (elapsed_time), perfd_size (pagesize));
994 exit (STATE_OK);
995 }
996 else {
997 if (errcode == REG_NOMATCH) {
998 printf (_("CRITICAL - pattern not found%s|%s %s\n"),
999 (display_html ? "</A>" : ""),
1000 perfd_time (elapsed_time), perfd_size (pagesize));
1001 exit (STATE_CRITICAL);
1002 }
1003 else {
1004 regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
1005 printf (_("CRITICAL - Execute Error: %s\n"), errbuf);
1006 exit (STATE_CRITICAL);
1007 }
1008 }
1009 }
1010 #endif
1012 /* make sure the page is of an appropriate size */
1013 /* page_len = get_content_length(header); */
1014 page_len = pagesize;
1015 if ((max_page_len > 0) && (page_len > max_page_len)) {
1016 printf (_("HTTP WARNING: page size %d too large%s|%s\n"),
1017 page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1018 exit (STATE_WARNING);
1019 } else if ((min_page_len > 0) && (page_len < min_page_len)) {
1020 printf (_("HTTP WARNING: page size %d too small%s|%s\n"),
1021 page_len, (display_html ? "</A>" : ""), perfd_size (page_len) );
1022 exit (STATE_WARNING);
1023 }
1024 /* We only get here if all tests have been passed */
1025 asprintf (&msg, _("HTTP OK %s - %d bytes in %.3f seconds %s%s|%s %s\n"),
1026 status_line, page_len, elapsed_time,
1027 timestamp, (display_html ? "</A>" : ""),
1028 perfd_time (elapsed_time), perfd_size (page_len));
1029 die (STATE_OK, "%s", msg);
1030 return STATE_UNKNOWN;
1031 }
1035 /* per RFC 2396 */
1036 #define HDR_LOCATION "%*[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]: "
1037 #define URI_HTTP "%[HTPShtps]://"
1038 #define URI_HOST "%[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1039 #define URI_PORT ":%[0123456789]"
1040 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1041 #define HD1 URI_HTTP URI_HOST URI_PORT URI_PATH
1042 #define HD2 URI_HTTP URI_HOST URI_PATH
1043 #define HD3 URI_HTTP URI_HOST URI_PORT
1044 #define HD4 URI_HTTP URI_HOST
1045 #define HD5 URI_PATH
1047 void
1048 redir (char *pos, char *status_line)
1049 {
1050 int i = 0;
1051 char *x;
1052 char xx[2];
1053 char type[6];
1054 char *addr;
1055 char port[6];
1056 char *url;
1058 addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
1059 if (addr == NULL)
1060 die (STATE_UNKNOWN, _("Could not allocate addr\n"));
1062 url = malloc (strcspn (pos, "\r\n"));
1063 if (url == NULL)
1064 die (STATE_UNKNOWN, _("Could not allocate url\n"));
1066 while (pos) {
1068 if (sscanf (pos, "%[Ll]%*[Oo]%*[Cc]%*[Aa]%*[Tt]%*[Ii]%*[Oo]%*[Nn]:%n", xx, &i) < 1) {
1070 pos += (size_t) strcspn (pos, "\r\n");
1071 pos += (size_t) strspn (pos, "\r\n");
1072 if (strlen(pos) == 0)
1073 die (STATE_UNKNOWN,
1074 _("UNKNOWN - Could not find redirect location - %s%s\n"),
1075 status_line, (display_html ? "</A>" : ""));
1076 continue;
1077 }
1079 pos += i;
1080 pos += strspn (pos, " \t\r\n");
1082 url = realloc (url, strcspn (pos, "\r\n"));
1083 if (url == NULL)
1084 die (STATE_UNKNOWN, _("could not allocate url\n"));
1086 /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
1087 if (sscanf (pos, HD1, type, addr, port, url) == 4) {
1088 use_ssl = server_type_check (type);
1089 i = atoi (port);
1090 }
1092 /* URI_HTTP URI_HOST URI_PATH */
1093 else if (sscanf (pos, HD2, type, addr, url) == 3 ) {
1094 use_ssl = server_type_check (type);
1095 i = server_port_check (use_ssl);
1096 }
1098 /* URI_HTTP URI_HOST URI_PORT */
1099 else if(sscanf (pos, HD3, type, addr, port) == 3) {
1100 strcpy (url, HTTP_URL);
1101 use_ssl = server_type_check (type);
1102 i = atoi (port);
1103 }
1105 /* URI_HTTP URI_HOST */
1106 else if(sscanf (pos, HD4, type, addr) == 2) {
1107 strcpy (url, HTTP_URL);
1108 use_ssl = server_type_check (type);
1109 i = server_port_check (use_ssl);
1110 }
1112 /* URI_PATH */
1113 else if (sscanf (pos, HD5, url) == 1) {
1114 /* relative url */
1115 if ((url[0] != '/')) {
1116 if ((x = strrchr(server_url, '/')))
1117 *x = '\0';
1118 asprintf (&url, "%s/%s", server_url, url);
1119 }
1120 i = server_port;
1121 strcpy (type, server_type);
1122 strcpy (addr, host_name);
1123 }
1125 else {
1126 die (STATE_UNKNOWN,
1127 _("UNKNOWN - Could not parse redirect location - %s%s\n"),
1128 pos, (display_html ? "</A>" : ""));
1129 }
1131 break;
1133 } /* end while (pos) */
1135 if (++redir_depth > max_depth)
1136 die (STATE_WARNING,
1137 _("WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
1138 max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
1140 if (server_port==i &&
1141 !strcmp(server_address, addr) &&
1142 (host_name && !strcmp(host_name, addr)) &&
1143 !strcmp(server_url, url))
1144 die (STATE_WARNING,
1145 _("WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
1146 type, addr, i, url, (display_html ? "</A>" : ""));
1148 server_port = i;
1149 strcpy (server_type, type);
1151 free (host_name);
1152 host_name = strdup (addr);
1154 free (server_address);
1155 server_address = strdup (addr);
1157 free (server_url);
1158 server_url = strdup (url);
1160 check_http ();
1161 }
1165 int
1166 server_type_check (const char *type)
1167 {
1168 if (strcmp (type, "https"))
1169 return FALSE;
1170 else
1171 return TRUE;
1172 }
1174 int
1175 server_port_check (int ssl_flag)
1176 {
1177 if (ssl_flag)
1178 return HTTPS_PORT;
1179 else
1180 return HTTP_PORT;
1181 }
1183 char *perfd_time (double elapsed_time)
1184 {
1185 return fperfdata ("time", elapsed_time, "s",
1186 check_warning_time, warning_time,
1187 check_critical_time, critical_time,
1188 TRUE, 0, FALSE, 0);
1189 }
1193 char *perfd_size (int page_len)
1194 {
1195 return perfdata ("size", page_len, "B",
1196 (min_page_len>0?TRUE:FALSE), min_page_len,
1197 (min_page_len>0?TRUE:FALSE), 0,
1198 TRUE, 0, FALSE, 0);
1199 }
1201 void
1202 print_help (void)
1203 {
1204 print_revision (progname, revision);
1206 printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
1207 printf (COPYRIGHT, copyright, email);
1209 printf (_("\
1210 This plugin tests the HTTP service on the specified host. It can test\n\
1211 normal (http) and secure (https) servers, follow redirects, search for\n\
1212 strings and regular expressions, check connection times, and report on\n\
1213 certificate expiration times."));
1215 printf ("\n\n");
1217 print_usage ();
1219 printf (_("NOTE: One or both of -H and -I must be specified"));
1221 printf ("\n");
1223 printf (_(UT_HELP_VRSN));
1225 printf (_("\
1226 -H, --hostname=ADDRESS\n\
1227 Host name argument for servers using host headers (virtual host)\n\
1228 Append a port to include it in the header (eg: example.com:5000)\n\
1229 -I, --IP-address=ADDRESS\n\
1230 IP address or name (use numeric address if possible to bypass DNS lookup).\n\
1231 -p, --port=INTEGER\n\
1232 Port number (default: %d)\n"), HTTP_PORT);
1234 printf (_(UT_IPv46));
1236 #ifdef HAVE_SSL
1237 printf (_("\
1238 -S, --ssl\n\
1239 Connect via SSL\n\
1240 -C, --certificate=INTEGER\n\
1241 Minimum number of days a certificate has to be valid.\n\
1242 (when this option is used the url is not checked.)\n"));
1243 #endif
1245 printf (_("\
1246 -e, --expect=STRING\n\
1247 String to expect in first (status) line of server response (default: %s)\n\
1248 If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)\n\
1249 -s, --string=STRING\n\
1250 String to expect in the content\n\
1251 -u, --url=PATH\n\
1252 URL to GET or POST (default: /)\n\
1253 -P, --post=STRING\n\
1254 URL encoded http POST data\n\
1255 -N, --no-body\n\
1256 Don't wait for document body: stop reading after headers.\n\
1257 (Note that this still does an HTTP GET or POST, not a HEAD.)\n\
1258 -M, --max-age=SECONDS\n\
1259 Warn if document is more than SECONDS old. the number can also be of \n\
1260 the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days.\n\
1261 -T, --content-type=STRING\n\
1262 specify Content-Type header media type when POSTing\n"), HTTP_EXPECT);
1264 #ifdef HAVE_REGEX_H
1265 printf (_("\
1266 -l, --linespan\n\
1267 Allow regex to span newlines (must precede -r or -R)\n\
1268 -r, --regex, --ereg=STRING\n\
1269 Search page for regex STRING\n\
1270 -R, --eregi=STRING\n\
1271 Search page for case-insensitive regex STRING\n"));
1272 #endif
1274 printf (_("\
1275 -a, --authorization=AUTH_PAIR\n\
1276 Username:password on sites with basic authentication\n\
1277 -A, --useragent=STRING\n\
1278 String to be sent in http header as \"User Agent\"\n\
1279 -k, --header=STRING\n\
1280 Any other tags to be sent in http header, separated by semicolon\n\
1281 -L, --link=URL\n\
1282 Wrap output in HTML link (obsoleted by urlize)\n\
1283 -f, --onredirect=<ok|warning|critical|follow>\n\
1284 How to handle redirected pages\n\
1285 -m, --pagesize=INTEGER<:INTEGER>\n\
1286 Minimum page size required (bytes) : Maximum page size required (bytes)\n"));
1288 printf (_(UT_WARN_CRIT));
1290 printf (_(UT_TIMEOUT), DEFAULT_SOCKET_TIMEOUT);
1292 printf (_(UT_VERBOSE));
1294 printf (_("\
1295 This plugin will attempt to open an HTTP connection with the host. Successful\n\
1296 connects return STATE_OK, refusals and timeouts return STATE_CRITICAL, other\n\
1297 errors return STATE_UNKNOWN. Successful connects, but incorrect reponse\n\
1298 messages from the host result in STATE_WARNING return values. If you are\n\
1299 checking a virtual server that uses 'host headers' you must supply the FQDN\n\
1300 (fully qualified domain name) as the [host_name] argument.\n"));
1302 #ifdef HAVE_SSL
1303 printf (_("\n\
1304 This plugin can also check whether an SSL enabled web server is able to\n\
1305 serve content (optionally within a specified time) or whether the X509 \n\
1306 certificate is still valid for the specified number of days.\n"));
1307 printf (_("\n\
1308 CHECK CONTENT: check_http -w 5 -c 10 --ssl www.verisign.com\n\n\
1309 When the 'www.verisign.com' server returns its content within 5 seconds, a\n\
1310 STATE_OK will be returned. When the server returns its content but exceeds\n\
1311 the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,\n\
1312 a STATE_CRITICAL will be returned.\n\n"));
1314 printf (_("\
1315 CHECK CERTIFICATE: check_http www.verisign.com -C 14\n\n\
1316 When the certificate of 'www.verisign.com' is valid for more than 14 days, a\n\
1317 STATE_OK is returned. When the certificate is still valid, but for less than\n\
1318 14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when\n\
1319 the certificate is expired.\n"));
1320 #endif
1322 printf (_(UT_SUPPORT));
1324 }
1328 void
1329 print_usage (void)
1330 {
1331 printf (_("Usage:"));
1332 printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
1333 printf (" [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L]\n");
1334 printf (" [-a auth] [-f <ok | warn | critcal | follow>] [-e <expect>]\n");
1335 printf (" [-s string] [-l] [-r <regex> | -R <case-insensitive regex>] [-P string]\n");
1336 printf (" [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>] [-A string] [-k string]\n");
1337 }