patch to support the concept of virtual ports
[monitoring-plugins.git] / plugins / check_http.c
blob2ce7e215a6cd1f8b88560e76a8f9c57c3257502d
1 /*****************************************************************************
3 * Monitoring check_http plugin
5 * License: GPL
6 * Copyright (c) 1999-2013 Monitoring Plugins Development Team
8 * Description:
10 * This file contains the check_http plugin
12 * This plugin tests the HTTP service on the specified host. It can test
13 * normal (http) and secure (https) servers, follow redirects, search for
14 * strings and regular expressions, check connection times, and report on
15 * certificate expiration times.
18 * This program is free software: you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation, either version 3 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
32 *****************************************************************************/
34 /* splint -I. -I../../plugins -I../../lib/ -I/usr/kerberos/include/ ../../plugins/check_http.c */
36 const char *progname = "check_http";
37 const char *copyright = "1999-2013";
38 const char *email = "devel@monitoring-plugins.org";
40 #include "common.h"
41 #include "netutils.h"
42 #include "utils.h"
43 #include "base64.h"
44 #include <ctype.h>
46 #define STICKY_NONE 0
47 #define STICKY_HOST 1
48 #define STICKY_PORT 2
50 #define HTTP_EXPECT "HTTP/1."
51 enum {
52 MAX_IPV4_HOSTLENGTH = 255,
53 HTTP_PORT = 80,
54 HTTPS_PORT = 443,
55 MAX_PORT = 65535
58 #ifdef HAVE_SSL
59 int check_cert = FALSE;
60 int ssl_version = 0;
61 int days_till_exp_warn, days_till_exp_crit;
62 char *randbuff;
63 X509 *server_cert;
64 # define my_recv(buf, len) ((use_ssl) ? np_net_ssl_read(buf, len) : read(sd, buf, len))
65 # define my_send(buf, len) ((use_ssl) ? np_net_ssl_write(buf, len) : send(sd, buf, len, 0))
66 #else /* ifndef HAVE_SSL */
67 # define my_recv(buf, len) read(sd, buf, len)
68 # define my_send(buf, len) send(sd, buf, len, 0)
69 #endif /* HAVE_SSL */
70 int no_body = FALSE;
71 int maximum_age = -1;
73 enum {
74 REGS = 2,
75 MAX_RE_SIZE = 256
77 #include "regex.h"
78 regex_t preg;
79 regmatch_t pmatch[REGS];
80 char regexp[MAX_RE_SIZE];
81 char errbuf[MAX_INPUT_BUFFER];
82 int cflags = REG_NOSUB | REG_EXTENDED | REG_NEWLINE;
83 int errcode;
84 int invert_regex = 0;
86 struct timeval tv;
87 struct timeval tv_temp;
89 #define HTTP_URL "/"
90 #define CRLF "\r\n"
92 int specify_port = FALSE;
93 int server_port = HTTP_PORT;
94 int virtual_port = 0;
95 char server_port_text[6] = "";
96 char server_type[6] = "http";
97 char *server_address;
98 char *host_name;
99 int host_name_length;
100 char *server_url;
101 char *user_agent;
102 int server_url_length;
103 int server_expect_yn = 0;
104 char server_expect[MAX_INPUT_BUFFER] = HTTP_EXPECT;
105 char header_expect[MAX_INPUT_BUFFER] = "";
106 char string_expect[MAX_INPUT_BUFFER] = "";
107 char output_header_search[30] = "";
108 char output_string_search[30] = "";
109 char *warning_thresholds = NULL;
110 char *critical_thresholds = NULL;
111 thresholds *thlds;
112 char user_auth[MAX_INPUT_BUFFER] = "";
113 char proxy_auth[MAX_INPUT_BUFFER] = "";
114 int display_html = FALSE;
115 char **http_opt_headers;
116 int http_opt_headers_count = 0;
117 int onredirect = STATE_OK;
118 int followsticky = STICKY_NONE;
119 int use_ssl = FALSE;
120 int use_sni = FALSE;
121 int verbose = FALSE;
122 int show_extended_perfdata = FALSE;
123 int sd;
124 int min_page_len = 0;
125 int max_page_len = 0;
126 int redir_depth = 0;
127 int max_depth = 15;
128 char *http_method;
129 char *http_post_data;
130 char *http_content_type;
131 char buffer[MAX_INPUT_BUFFER];
132 char *client_cert = NULL;
133 char *client_privkey = NULL;
135 int process_arguments (int, char **);
136 int check_http (void);
137 void redir (char *pos, char *status_line);
138 int server_type_check(const char *type);
139 int server_port_check(int ssl_flag);
140 char *perfd_time (double microsec);
141 char *perfd_time_connect (double microsec);
142 char *perfd_time_ssl (double microsec);
143 char *perfd_time_firstbyte (double microsec);
144 char *perfd_time_headers (double microsec);
145 char *perfd_time_transfer (double microsec);
146 char *perfd_size (int page_len);
147 void print_help (void);
148 void print_usage (void);
151 main (int argc, char **argv)
153 int result = STATE_UNKNOWN;
155 setlocale (LC_ALL, "");
156 bindtextdomain (PACKAGE, LOCALEDIR);
157 textdomain (PACKAGE);
159 /* Set default URL. Must be malloced for subsequent realloc if --onredirect=follow */
160 server_url = strdup(HTTP_URL);
161 server_url_length = strlen(server_url);
162 xasprintf (&user_agent, "User-Agent: check_http/v%s (monitoring-plugins %s)",
163 NP_VERSION, VERSION);
165 /* Parse extra opts if any */
166 argv=np_extra_opts (&argc, argv, progname);
168 if (process_arguments (argc, argv) == ERROR)
169 usage4 (_("Could not parse arguments"));
171 if (display_html == TRUE)
172 printf ("<A HREF=\"%s://%s:%d%s\" target=\"_blank\">",
173 use_ssl ? "https" : "http", host_name ? host_name : server_address,
174 server_port, server_url);
176 /* initialize alarm signal handling, set socket timeout, start timer */
177 (void) signal (SIGALRM, socket_timeout_alarm_handler);
178 (void) alarm (socket_timeout);
179 gettimeofday (&tv, NULL);
181 result = check_http ();
182 return result;
185 /* check whether a file exists */
186 void
187 test_file (char *path)
189 if (access(path, R_OK) == 0)
190 return;
191 usage2 (_("file does not exist or is not readable"), path);
194 /* process command-line arguments */
196 process_arguments (int argc, char **argv)
198 int c = 1;
199 char *p;
200 char *temp;
202 enum {
203 INVERT_REGEX = CHAR_MAX + 1,
204 SNI_OPTION
207 int option = 0;
208 static struct option longopts[] = {
209 STD_LONG_OPTS,
210 {"link", no_argument, 0, 'L'},
211 {"nohtml", no_argument, 0, 'n'},
212 {"ssl", optional_argument, 0, 'S'},
213 {"sni", no_argument, 0, SNI_OPTION},
214 {"post", required_argument, 0, 'P'},
215 {"method", required_argument, 0, 'j'},
216 {"IP-address", required_argument, 0, 'I'},
217 {"url", required_argument, 0, 'u'},
218 {"port", required_argument, 0, 'p'},
219 {"authorization", required_argument, 0, 'a'},
220 {"proxy-authorization", required_argument, 0, 'b'},
221 {"header-string", required_argument, 0, 'd'},
222 {"string", required_argument, 0, 's'},
223 {"expect", required_argument, 0, 'e'},
224 {"regex", required_argument, 0, 'r'},
225 {"ereg", required_argument, 0, 'r'},
226 {"eregi", required_argument, 0, 'R'},
227 {"linespan", no_argument, 0, 'l'},
228 {"onredirect", required_argument, 0, 'f'},
229 {"certificate", required_argument, 0, 'C'},
230 {"client-cert", required_argument, 0, 'J'},
231 {"private-key", required_argument, 0, 'K'},
232 {"useragent", required_argument, 0, 'A'},
233 {"header", required_argument, 0, 'k'},
234 {"no-body", no_argument, 0, 'N'},
235 {"max-age", required_argument, 0, 'M'},
236 {"content-type", required_argument, 0, 'T'},
237 {"pagesize", required_argument, 0, 'm'},
238 {"invert-regex", no_argument, NULL, INVERT_REGEX},
239 {"use-ipv4", no_argument, 0, '4'},
240 {"use-ipv6", no_argument, 0, '6'},
241 {"extended-perfdata", no_argument, 0, 'E'},
242 {0, 0, 0, 0}
245 if (argc < 2)
246 return ERROR;
248 for (c = 1; c < argc; c++) {
249 if (strcmp ("-to", argv[c]) == 0)
250 strcpy (argv[c], "-t");
251 if (strcmp ("-hn", argv[c]) == 0)
252 strcpy (argv[c], "-H");
253 if (strcmp ("-wt", argv[c]) == 0)
254 strcpy (argv[c], "-w");
255 if (strcmp ("-ct", argv[c]) == 0)
256 strcpy (argv[c], "-c");
257 if (strcmp ("-nohtml", argv[c]) == 0)
258 strcpy (argv[c], "-n");
261 while (1) {
262 c = getopt_long (argc, argv, "Vvh46t:c:w:A:k:H:P:j:T:I:a:b:d:e:p:s:R:r:u:f:C:J:K:nlLS::m:M:NE", longopts, &option);
263 if (c == -1 || c == EOF)
264 break;
266 switch (c) {
267 case '?': /* usage */
268 usage5 ();
269 break;
270 case 'h': /* help */
271 print_help ();
272 exit (STATE_UNKNOWN);
273 break;
274 case 'V': /* version */
275 print_revision (progname, NP_VERSION);
276 exit (STATE_UNKNOWN);
277 break;
278 case 't': /* timeout period */
279 if (!is_intnonneg (optarg))
280 usage2 (_("Timeout interval must be a positive integer"), optarg);
281 else
282 socket_timeout = atoi (optarg);
283 break;
284 case 'c': /* critical time threshold */
285 critical_thresholds = optarg;
286 break;
287 case 'w': /* warning time threshold */
288 warning_thresholds = optarg;
289 break;
290 case 'A': /* User Agent String */
291 xasprintf (&user_agent, "User-Agent: %s", optarg);
292 break;
293 case 'k': /* Additional headers */
294 if (http_opt_headers_count == 0)
295 http_opt_headers = malloc (sizeof (char *) * (++http_opt_headers_count));
296 else
297 http_opt_headers = realloc (http_opt_headers, sizeof (char *) * (++http_opt_headers_count));
298 http_opt_headers[http_opt_headers_count - 1] = optarg;
299 /* xasprintf (&http_opt_headers, "%s", optarg); */
300 break;
301 case 'L': /* show html link */
302 display_html = TRUE;
303 break;
304 case 'n': /* do not show html link */
305 display_html = FALSE;
306 break;
307 case 'C': /* Check SSL cert validity */
308 #ifdef HAVE_SSL
309 if ((temp=strchr(optarg,','))!=NULL) {
310 *temp='\0';
311 if (!is_intnonneg (optarg))
312 usage2 (_("Invalid certificate expiration period"), optarg);
313 days_till_exp_warn = atoi(optarg);
314 *temp=',';
315 temp++;
316 if (!is_intnonneg (temp))
317 usage2 (_("Invalid certificate expiration period"), temp);
318 days_till_exp_crit = atoi (temp);
320 else {
321 days_till_exp_crit=0;
322 if (!is_intnonneg (optarg))
323 usage2 (_("Invalid certificate expiration period"), optarg);
324 days_till_exp_warn = atoi (optarg);
326 check_cert = TRUE;
327 goto enable_ssl;
328 #endif
329 case 'J': /* use client certificate */
330 #ifdef HAVE_SSL
331 test_file(optarg);
332 client_cert = optarg;
333 goto enable_ssl;
334 #endif
335 case 'K': /* use client private key */
336 #ifdef HAVE_SSL
337 test_file(optarg);
338 client_privkey = optarg;
339 goto enable_ssl;
340 #endif
341 case 'S': /* use SSL */
342 #ifdef HAVE_SSL
343 enable_ssl:
344 /* ssl_version initialized to 0 as a default. Only set if it's non-zero. This helps when we include multiple
345 parameters, like -S and -C combinations */
346 use_ssl = TRUE;
347 if (c=='S' && optarg != NULL) {
348 int got_plus = strchr(optarg, '+') != NULL;
350 if (!strncmp (optarg, "1.2", 3))
351 ssl_version = got_plus ? MP_TLSv1_2_OR_NEWER : MP_TLSv1_2;
352 else if (!strncmp (optarg, "1.1", 3))
353 ssl_version = got_plus ? MP_TLSv1_1_OR_NEWER : MP_TLSv1_1;
354 else if (optarg[0] == '1')
355 ssl_version = got_plus ? MP_TLSv1_OR_NEWER : MP_TLSv1;
356 else if (optarg[0] == '3')
357 ssl_version = got_plus ? MP_SSLv3_OR_NEWER : MP_SSLv3;
358 else if (optarg[0] == '2')
359 ssl_version = got_plus ? MP_SSLv2_OR_NEWER : MP_SSLv2;
360 else
361 usage4 (_("Invalid option - Valid SSL/TLS versions: 2, 3, 1, 1.1, 1.2 (with optional '+' suffix)"));
363 if (specify_port == FALSE)
364 server_port = HTTPS_PORT;
365 #else
366 /* -C -J and -K fall through to here without SSL */
367 usage4 (_("Invalid option - SSL is not available"));
368 #endif
369 break;
370 case SNI_OPTION:
371 use_sni = TRUE;
372 break;
373 case 'f': /* onredirect */
374 if (!strcmp (optarg, "stickyport"))
375 onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST|STICKY_PORT;
376 else if (!strcmp (optarg, "sticky"))
377 onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST;
378 else if (!strcmp (optarg, "follow"))
379 onredirect = STATE_DEPENDENT, followsticky = STICKY_NONE;
380 else if (!strcmp (optarg, "unknown"))
381 onredirect = STATE_UNKNOWN;
382 else if (!strcmp (optarg, "ok"))
383 onredirect = STATE_OK;
384 else if (!strcmp (optarg, "warning"))
385 onredirect = STATE_WARNING;
386 else if (!strcmp (optarg, "critical"))
387 onredirect = STATE_CRITICAL;
388 else usage2 (_("Invalid onredirect option"), optarg);
389 if (verbose)
390 printf(_("option f:%d \n"), onredirect);
391 break;
392 /* Note: H, I, and u must be malloc'd or will fail on redirects */
393 case 'H': /* Host Name (virtual host) */
394 host_name = strdup (optarg);
395 if (host_name[0] == '[') {
396 if ((p = strstr (host_name, "]:")) != NULL) { /* [IPv6]:port */
397 virtual_port = atoi (p + 2);
398 /* cut off the port */
399 host_name_length = strlen (host_name) - strlen (p) - 1;
400 free (host_name);
401 host_name = strndup (optarg, host_name_length);
402 if (specify_port == FALSE)
403 server_port = virtual_port;
405 } else if ((p = strchr (host_name, ':')) != NULL
406 && strchr (++p, ':') == NULL) { /* IPv4:port or host:port */
407 virtual_port = atoi (p);
408 /* cut off the port */
409 host_name_length = strlen (host_name) - strlen (p) - 1;
410 free (host_name);
411 host_name = strndup (optarg, host_name_length);
412 if (specify_port == FALSE)
413 server_port = virtual_port;
415 break;
416 case 'I': /* Server IP-address */
417 server_address = strdup (optarg);
418 break;
419 case 'u': /* URL path */
420 server_url = strdup (optarg);
421 server_url_length = strlen (server_url);
422 break;
423 case 'p': /* Server port */
424 if (!is_intnonneg (optarg))
425 usage2 (_("Invalid port number"), optarg);
426 else {
427 server_port = atoi (optarg);
428 specify_port = TRUE;
430 break;
431 case 'a': /* authorization info */
432 strncpy (user_auth, optarg, MAX_INPUT_BUFFER - 1);
433 user_auth[MAX_INPUT_BUFFER - 1] = 0;
434 break;
435 case 'b': /* proxy-authorization info */
436 strncpy (proxy_auth, optarg, MAX_INPUT_BUFFER - 1);
437 proxy_auth[MAX_INPUT_BUFFER - 1] = 0;
438 break;
439 case 'P': /* HTTP POST data in URL encoded format; ignored if settings already */
440 if (! http_post_data)
441 http_post_data = strdup (optarg);
442 if (! http_method)
443 http_method = strdup("POST");
444 break;
445 case 'j': /* Set HTTP method */
446 if (http_method)
447 free(http_method);
448 http_method = strdup (optarg);
449 break;
450 case 'd': /* string or substring */
451 strncpy (header_expect, optarg, MAX_INPUT_BUFFER - 1);
452 header_expect[MAX_INPUT_BUFFER - 1] = 0;
453 break;
454 case 's': /* string or substring */
455 strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1);
456 string_expect[MAX_INPUT_BUFFER - 1] = 0;
457 break;
458 case 'e': /* string or substring */
459 strncpy (server_expect, optarg, MAX_INPUT_BUFFER - 1);
460 server_expect[MAX_INPUT_BUFFER - 1] = 0;
461 server_expect_yn = 1;
462 break;
463 case 'T': /* Content-type */
464 xasprintf (&http_content_type, "%s", optarg);
465 break;
466 case 'l': /* linespan */
467 cflags &= ~REG_NEWLINE;
468 break;
469 case 'R': /* regex */
470 cflags |= REG_ICASE;
471 case 'r': /* regex */
472 strncpy (regexp, optarg, MAX_RE_SIZE - 1);
473 regexp[MAX_RE_SIZE - 1] = 0;
474 errcode = regcomp (&preg, regexp, cflags);
475 if (errcode != 0) {
476 (void) regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
477 printf (_("Could Not Compile Regular Expression: %s"), errbuf);
478 return ERROR;
480 break;
481 case INVERT_REGEX:
482 invert_regex = 1;
483 break;
484 case '4':
485 address_family = AF_INET;
486 break;
487 case '6':
488 #ifdef USE_IPV6
489 address_family = AF_INET6;
490 #else
491 usage4 (_("IPv6 support not available"));
492 #endif
493 break;
494 case 'v': /* verbose */
495 verbose = TRUE;
496 break;
497 case 'm': /* min_page_length */
499 char *tmp;
500 if (strchr(optarg, ':') != (char *)NULL) {
501 /* range, so get two values, min:max */
502 tmp = strtok(optarg, ":");
503 if (tmp == NULL) {
504 printf("Bad format: try \"-m min:max\"\n");
505 exit (STATE_WARNING);
506 } else
507 min_page_len = atoi(tmp);
509 tmp = strtok(NULL, ":");
510 if (tmp == NULL) {
511 printf("Bad format: try \"-m min:max\"\n");
512 exit (STATE_WARNING);
513 } else
514 max_page_len = atoi(tmp);
515 } else
516 min_page_len = atoi (optarg);
517 break;
519 case 'N': /* no-body */
520 no_body = TRUE;
521 break;
522 case 'M': /* max-age */
524 int L = strlen(optarg);
525 if (L && optarg[L-1] == 'm')
526 maximum_age = atoi (optarg) * 60;
527 else if (L && optarg[L-1] == 'h')
528 maximum_age = atoi (optarg) * 60 * 60;
529 else if (L && optarg[L-1] == 'd')
530 maximum_age = atoi (optarg) * 60 * 60 * 24;
531 else if (L && (optarg[L-1] == 's' ||
532 isdigit (optarg[L-1])))
533 maximum_age = atoi (optarg);
534 else {
535 fprintf (stderr, "unparsable max-age: %s\n", optarg);
536 exit (STATE_WARNING);
539 break;
540 case 'E': /* show extended perfdata */
541 show_extended_perfdata = TRUE;
542 break;
546 c = optind;
548 if (server_address == NULL && c < argc)
549 server_address = strdup (argv[c++]);
551 if (host_name == NULL && c < argc)
552 host_name = strdup (argv[c++]);
554 if (server_address == NULL) {
555 if (host_name == NULL)
556 usage4 (_("You must specify a server address or host name"));
557 else
558 server_address = strdup (host_name);
561 set_thresholds(&thlds, warning_thresholds, critical_thresholds);
563 if (critical_thresholds && thlds->critical->end>(double)socket_timeout)
564 socket_timeout = (int)thlds->critical->end + 1;
566 if (http_method == NULL)
567 http_method = strdup ("GET");
569 if (client_cert && !client_privkey)
570 usage4 (_("If you use a client certificate you must also specify a private key file"));
572 if (virtual_port == 0)
573 virtual_port = server_port;
575 return TRUE;
580 /* Returns 1 if we're done processing the document body; 0 to keep going */
581 static int
582 document_headers_done (char *full_page)
584 const char *body;
586 for (body = full_page; *body; body++) {
587 if (!strncmp (body, "\n\n", 2) || !strncmp (body, "\n\r\n", 3))
588 break;
591 if (!*body)
592 return 0; /* haven't read end of headers yet */
594 full_page[body - full_page] = 0;
595 return 1;
598 static time_t
599 parse_time_string (const char *string)
601 struct tm tm;
602 time_t t;
603 memset (&tm, 0, sizeof(tm));
605 /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
607 if (isupper (string[0]) && /* Tue */
608 islower (string[1]) &&
609 islower (string[2]) &&
610 ',' == string[3] &&
611 ' ' == string[4] &&
612 (isdigit(string[5]) || string[5] == ' ') && /* 25 */
613 isdigit (string[6]) &&
614 ' ' == string[7] &&
615 isupper (string[8]) && /* Dec */
616 islower (string[9]) &&
617 islower (string[10]) &&
618 ' ' == string[11] &&
619 isdigit (string[12]) && /* 2001 */
620 isdigit (string[13]) &&
621 isdigit (string[14]) &&
622 isdigit (string[15]) &&
623 ' ' == string[16] &&
624 isdigit (string[17]) && /* 02: */
625 isdigit (string[18]) &&
626 ':' == string[19] &&
627 isdigit (string[20]) && /* 59: */
628 isdigit (string[21]) &&
629 ':' == string[22] &&
630 isdigit (string[23]) && /* 03 */
631 isdigit (string[24]) &&
632 ' ' == string[25] &&
633 'G' == string[26] && /* GMT */
634 'M' == string[27] && /* GMT */
635 'T' == string[28]) {
637 tm.tm_sec = 10 * (string[23]-'0') + (string[24]-'0');
638 tm.tm_min = 10 * (string[20]-'0') + (string[21]-'0');
639 tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
640 tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
641 tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
642 !strncmp (string+8, "Feb", 3) ? 1 :
643 !strncmp (string+8, "Mar", 3) ? 2 :
644 !strncmp (string+8, "Apr", 3) ? 3 :
645 !strncmp (string+8, "May", 3) ? 4 :
646 !strncmp (string+8, "Jun", 3) ? 5 :
647 !strncmp (string+8, "Jul", 3) ? 6 :
648 !strncmp (string+8, "Aug", 3) ? 7 :
649 !strncmp (string+8, "Sep", 3) ? 8 :
650 !strncmp (string+8, "Oct", 3) ? 9 :
651 !strncmp (string+8, "Nov", 3) ? 10 :
652 !strncmp (string+8, "Dec", 3) ? 11 :
653 -1);
654 tm.tm_year = ((1000 * (string[12]-'0') +
655 100 * (string[13]-'0') +
656 10 * (string[14]-'0') +
657 (string[15]-'0'))
658 - 1900);
660 tm.tm_isdst = 0; /* GMT is never in DST, right? */
662 if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
663 return 0;
666 This is actually wrong: we need to subtract the local timezone
667 offset from GMT from this value. But, that's ok in this usage,
668 because we only comparing these two GMT dates against each other,
669 so it doesn't matter what time zone we parse them in.
672 t = mktime (&tm);
673 if (t == (time_t) -1) t = 0;
675 if (verbose) {
676 const char *s = string;
677 while (*s && *s != '\r' && *s != '\n')
678 fputc (*s++, stdout);
679 printf (" ==> %lu\n", (unsigned long) t);
682 return t;
684 } else {
685 return 0;
689 /* Checks if the server 'reply' is one of the expected 'statuscodes' */
690 static int
691 expected_statuscode (const char *reply, const char *statuscodes)
693 char *expected, *code;
694 int result = 0;
696 if ((expected = strdup (statuscodes)) == NULL)
697 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
699 for (code = strtok (expected, ","); code != NULL; code = strtok (NULL, ","))
700 if (strstr (reply, code) != NULL) {
701 result = 1;
702 break;
705 free (expected);
706 return result;
709 static int
710 check_document_dates (const char *headers, char **msg)
712 const char *s;
713 char *server_date = 0;
714 char *document_date = 0;
715 int date_result = STATE_OK;
717 s = headers;
718 while (*s) {
719 const char *field = s;
720 const char *value = 0;
722 /* Find the end of the header field */
723 while (*s && !isspace(*s) && *s != ':')
724 s++;
726 /* Remember the header value, if any. */
727 if (*s == ':')
728 value = ++s;
730 /* Skip to the end of the header, including continuation lines. */
731 while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
732 s++;
734 /* Avoid stepping over end-of-string marker */
735 if (*s)
736 s++;
738 /* Process this header. */
739 if (value && value > field+2) {
740 char *ff = (char *) malloc (value-field);
741 char *ss = ff;
742 while (field < value-1)
743 *ss++ = tolower(*field++);
744 *ss++ = 0;
746 if (!strcmp (ff, "date") || !strcmp (ff, "last-modified")) {
747 const char *e;
748 while (*value && isspace (*value))
749 value++;
750 for (e = value; *e && *e != '\r' && *e != '\n'; e++)
752 ss = (char *) malloc (e - value + 1);
753 strncpy (ss, value, e - value);
754 ss[e - value] = 0;
755 if (!strcmp (ff, "date")) {
756 if (server_date) free (server_date);
757 server_date = ss;
758 } else {
759 if (document_date) free (document_date);
760 document_date = ss;
763 free (ff);
767 /* Done parsing the body. Now check the dates we (hopefully) parsed. */
768 if (!server_date || !*server_date) {
769 xasprintf (msg, _("%sServer date unknown, "), *msg);
770 date_result = max_state_alt(STATE_UNKNOWN, date_result);
771 } else if (!document_date || !*document_date) {
772 xasprintf (msg, _("%sDocument modification date unknown, "), *msg);
773 date_result = max_state_alt(STATE_CRITICAL, date_result);
774 } else {
775 time_t srv_data = parse_time_string (server_date);
776 time_t doc_data = parse_time_string (document_date);
778 if (srv_data <= 0) {
779 xasprintf (msg, _("%sServer date \"%100s\" unparsable, "), *msg, server_date);
780 date_result = max_state_alt(STATE_CRITICAL, date_result);
781 } else if (doc_data <= 0) {
782 xasprintf (msg, _("%sDocument date \"%100s\" unparsable, "), *msg, document_date);
783 date_result = max_state_alt(STATE_CRITICAL, date_result);
784 } else if (doc_data > srv_data + 30) {
785 xasprintf (msg, _("%sDocument is %d seconds in the future, "), *msg, (int)doc_data - (int)srv_data);
786 date_result = max_state_alt(STATE_CRITICAL, date_result);
787 } else if (doc_data < srv_data - maximum_age) {
788 int n = (srv_data - doc_data);
789 if (n > (60 * 60 * 24 * 2)) {
790 xasprintf (msg, _("%sLast modified %.1f days ago, "), *msg, ((float) n) / (60 * 60 * 24));
791 date_result = max_state_alt(STATE_CRITICAL, date_result);
792 } else {
793 xasprintf (msg, _("%sLast modified %d:%02d:%02d ago, "), *msg, n / (60 * 60), (n / 60) % 60, n % 60);
794 date_result = max_state_alt(STATE_CRITICAL, date_result);
797 free (server_date);
798 free (document_date);
800 return date_result;
804 get_content_length (const char *headers)
806 const char *s;
807 int content_length = 0;
809 s = headers;
810 while (*s) {
811 const char *field = s;
812 const char *value = 0;
814 /* Find the end of the header field */
815 while (*s && !isspace(*s) && *s != ':')
816 s++;
818 /* Remember the header value, if any. */
819 if (*s == ':')
820 value = ++s;
822 /* Skip to the end of the header, including continuation lines. */
823 while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
824 s++;
826 /* Avoid stepping over end-of-string marker */
827 if (*s)
828 s++;
830 /* Process this header. */
831 if (value && value > field+2) {
832 char *ff = (char *) malloc (value-field);
833 char *ss = ff;
834 while (field < value-1)
835 *ss++ = tolower(*field++);
836 *ss++ = 0;
838 if (!strcmp (ff, "content-length")) {
839 const char *e;
840 while (*value && isspace (*value))
841 value++;
842 for (e = value; *e && *e != '\r' && *e != '\n'; e++)
844 ss = (char *) malloc (e - value + 1);
845 strncpy (ss, value, e - value);
846 ss[e - value] = 0;
847 content_length = atoi(ss);
848 free (ss);
850 free (ff);
853 return (content_length);
856 char *
857 prepend_slash (char *path)
859 char *newpath;
861 if (path[0] == '/')
862 return path;
864 if ((newpath = malloc (strlen(path) + 2)) == NULL)
865 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
866 newpath[0] = '/';
867 strcpy (newpath + 1, path);
868 free (path);
869 return newpath;
873 check_http (void)
875 char *msg;
876 char *status_line;
877 char *status_code;
878 char *header;
879 char *page;
880 char *auth;
881 int http_status;
882 int i = 0;
883 size_t pagesize = 0;
884 char *full_page;
885 char *full_page_new;
886 char *buf;
887 char *pos;
888 long microsec = 0L;
889 double elapsed_time = 0.0;
890 long microsec_connect = 0L;
891 double elapsed_time_connect = 0.0;
892 long microsec_ssl = 0L;
893 double elapsed_time_ssl = 0.0;
894 long microsec_firstbyte = 0L;
895 double elapsed_time_firstbyte = 0.0;
896 long microsec_headers = 0L;
897 double elapsed_time_headers = 0.0;
898 long microsec_transfer = 0L;
899 double elapsed_time_transfer = 0.0;
900 int page_len = 0;
901 int result = STATE_OK;
902 char *force_host_header = NULL;
904 /* try to connect to the host at the given port number */
905 gettimeofday (&tv_temp, NULL);
906 if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
907 die (STATE_CRITICAL, _("HTTP CRITICAL - Unable to open TCP socket\n"));
908 microsec_connect = deltime (tv_temp);
910 /* if we are called with the -I option, the -j method is CONNECT and */
911 /* we received -S for SSL, then we tunnel the request through a proxy*/
912 /* @20100414, public[at]frank4dd.com, http://www.frank4dd.com/howto */
914 if ( server_address != NULL && strcmp(http_method, "CONNECT") == 0
915 && host_name != NULL && use_ssl == TRUE) {
917 if (verbose) printf ("Entering CONNECT tunnel mode with proxy %s:%d to dst %s:%d\n", server_address, server_port, host_name, HTTPS_PORT);
918 asprintf (&buf, "%s %s:%d HTTP/1.1\r\n%s\r\n", http_method, host_name, HTTPS_PORT, user_agent);
919 asprintf (&buf, "%sProxy-Connection: keep-alive\r\n", buf);
920 asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
921 /* we finished our request, send empty line with CRLF */
922 asprintf (&buf, "%s%s", buf, CRLF);
923 if (verbose) printf ("%s\n", buf);
924 send(sd, buf, strlen (buf), 0);
925 buf[0]='\0';
927 if (verbose) printf ("Receive response from proxy\n");
928 read (sd, buffer, MAX_INPUT_BUFFER-1);
929 if (verbose) printf ("%s", buffer);
930 /* Here we should check if we got HTTP/1.1 200 Connection established */
932 #ifdef HAVE_SSL
933 elapsed_time_connect = (double)microsec_connect / 1.0e6;
934 if (use_ssl == TRUE) {
935 gettimeofday (&tv_temp, NULL);
936 result = np_net_ssl_init_with_hostname_version_and_cert(sd, (use_sni ? host_name : NULL), ssl_version, client_cert, client_privkey);
937 if (verbose) printf ("SSL initialized\n");
938 if (result != STATE_OK)
939 die (STATE_CRITICAL, NULL);
940 microsec_ssl = deltime (tv_temp);
941 elapsed_time_ssl = (double)microsec_ssl / 1.0e6;
942 if (check_cert == TRUE) {
943 result = np_net_ssl_check_cert(days_till_exp_warn, days_till_exp_crit);
944 np_net_ssl_cleanup();
945 if (sd) close(sd);
946 return result;
949 #endif /* HAVE_SSL */
951 if ( server_address != NULL && strcmp(http_method, "CONNECT") == 0
952 && host_name != NULL && use_ssl == TRUE)
953 asprintf (&buf, "%s %s %s\r\n%s\r\n", "GET", server_url, host_name ? "HTTP/1.1" : "HTTP/1.0", user_agent);
954 else
955 asprintf (&buf, "%s %s %s\r\n%s\r\n", http_method, server_url, host_name ? "HTTP/1.1" : "HTTP/1.0", user_agent);
957 /* tell HTTP/1.1 servers not to keep the connection alive */
958 xasprintf (&buf, "%sConnection: close\r\n", buf);
960 /* check if Host header is explicitly set in options */
961 if (http_opt_headers_count) {
962 for (i = 0; i < http_opt_headers_count ; i++) {
963 if (strncmp(http_opt_headers[i], "Host:", 5) == 0) {
964 force_host_header = http_opt_headers[i];
969 /* optionally send the host header info */
970 if (host_name) {
971 if (force_host_header) {
972 xasprintf (&buf, "%s%s\r\n", buf, force_host_header);
974 else {
976 * Specify the port only if we're using a non-default port (see RFC 2616,
977 * 14.23). Some server applications/configurations cause trouble if the
978 * (default) port is explicitly specified in the "Host:" header line.
980 if ((use_ssl == FALSE && virtual_port == HTTP_PORT) ||
981 (use_ssl == TRUE && virtual_port == HTTPS_PORT) ||
982 (server_address != NULL && strcmp(http_method, "CONNECT") == 0
983 && host_name != NULL && use_ssl == TRUE))
984 xasprintf (&buf, "%sHost: %s\r\n", buf, host_name);
985 else
986 xasprintf (&buf, "%sHost: %s:%d\r\n", buf, host_name, virtual_port);
990 /* optionally send any other header tag */
991 if (http_opt_headers_count) {
992 for (i = 0; i < http_opt_headers_count ; i++) {
993 if (force_host_header != http_opt_headers[i]) {
994 xasprintf (&buf, "%s%s\r\n", buf, http_opt_headers[i]);
997 /* This cannot be free'd here because a redirection will then try to access this and segfault */
998 /* Covered in a testcase in tests/check_http.t */
999 /* free(http_opt_headers); */
1002 /* optionally send the authentication info */
1003 if (strlen(user_auth)) {
1004 base64_encode_alloc (user_auth, strlen (user_auth), &auth);
1005 xasprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
1008 /* optionally send the proxy authentication info */
1009 if (strlen(proxy_auth)) {
1010 base64_encode_alloc (proxy_auth, strlen (proxy_auth), &auth);
1011 xasprintf (&buf, "%sProxy-Authorization: Basic %s\r\n", buf, auth);
1014 /* either send http POST data (any data, not only POST)*/
1015 if (http_post_data) {
1016 if (http_content_type) {
1017 xasprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
1018 } else {
1019 xasprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
1022 xasprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
1023 xasprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
1025 else {
1026 /* or just a newline so the server knows we're done with the request */
1027 xasprintf (&buf, "%s%s", buf, CRLF);
1030 if (verbose) printf ("%s\n", buf);
1031 gettimeofday (&tv_temp, NULL);
1032 my_send (buf, strlen (buf));
1033 microsec_headers = deltime (tv_temp);
1034 elapsed_time_headers = (double)microsec_headers / 1.0e6;
1036 /* fetch the page */
1037 full_page = strdup("");
1038 gettimeofday (&tv_temp, NULL);
1039 while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
1040 if ((i >= 1) && (elapsed_time_firstbyte <= 0.000001)) {
1041 microsec_firstbyte = deltime (tv_temp);
1042 elapsed_time_firstbyte = (double)microsec_firstbyte / 1.0e6;
1044 buffer[i] = '\0';
1045 xasprintf (&full_page_new, "%s%s", full_page, buffer);
1046 free (full_page);
1047 full_page = full_page_new;
1048 pagesize += i;
1050 if (no_body && document_headers_done (full_page)) {
1051 i = 0;
1052 break;
1055 microsec_transfer = deltime (tv_temp);
1056 elapsed_time_transfer = (double)microsec_transfer / 1.0e6;
1058 if (i < 0 && errno != ECONNRESET) {
1059 #ifdef HAVE_SSL
1061 if (use_ssl) {
1062 sslerr=SSL_get_error(ssl, i);
1063 if ( sslerr == SSL_ERROR_SSL ) {
1064 die (STATE_WARNING, _("HTTP WARNING - Client Certificate Required\n"));
1065 } else {
1066 die (STATE_CRITICAL, _("HTTP CRITICAL - Error on receive\n"));
1069 else {
1071 #endif
1072 die (STATE_CRITICAL, _("HTTP CRITICAL - Error on receive\n"));
1073 #ifdef HAVE_SSL
1074 /* XXX
1077 #endif
1080 /* return a CRITICAL status if we couldn't read any data */
1081 if (pagesize == (size_t) 0)
1082 die (STATE_CRITICAL, _("HTTP CRITICAL - No data received from host\n"));
1084 /* close the connection */
1085 #ifdef HAVE_SSL
1086 np_net_ssl_cleanup();
1087 #endif
1088 if (sd) close(sd);
1090 /* Save check time */
1091 microsec = deltime (tv);
1092 elapsed_time = (double)microsec / 1.0e6;
1094 /* leave full_page untouched so we can free it later */
1095 page = full_page;
1097 if (verbose)
1098 printf ("%s://%s:%d%s is %d characters\n",
1099 use_ssl ? "https" : "http", server_address,
1100 server_port, server_url, (int)pagesize);
1102 /* find status line and null-terminate it */
1103 status_line = page;
1104 page += (size_t) strcspn (page, "\r\n");
1105 pos = page;
1106 page += (size_t) strspn (page, "\r\n");
1107 status_line[strcspn(status_line, "\r\n")] = 0;
1108 strip (status_line);
1109 if (verbose)
1110 printf ("STATUS: %s\n", status_line);
1112 /* find header info and null-terminate it */
1113 header = page;
1114 while (strcspn (page, "\r\n") > 0) {
1115 page += (size_t) strcspn (page, "\r\n");
1116 pos = page;
1117 if ((strspn (page, "\r") == 1 && strspn (page, "\r\n") >= 2) ||
1118 (strspn (page, "\n") == 1 && strspn (page, "\r\n") >= 2))
1119 page += (size_t) 2;
1120 else
1121 page += (size_t) 1;
1123 page += (size_t) strspn (page, "\r\n");
1124 header[pos - header] = 0;
1125 if (verbose)
1126 printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header,
1127 (no_body ? " [[ skipped ]]" : page));
1129 /* make sure the status line matches the response we are looking for */
1130 if (!expected_statuscode (status_line, server_expect)) {
1131 if (server_port == HTTP_PORT)
1132 xasprintf (&msg,
1133 _("Invalid HTTP response received from host: %s\n"),
1134 status_line);
1135 else
1136 xasprintf (&msg,
1137 _("Invalid HTTP response received from host on port %d: %s\n"),
1138 server_port, status_line);
1139 die (STATE_CRITICAL, "HTTP CRITICAL - %s", msg);
1142 /* Bypass normal status line check if server_expect was set by user and not default */
1143 /* NOTE: After this if/else block msg *MUST* be an asprintf-allocated string */
1144 if ( server_expect_yn ) {
1145 xasprintf (&msg,
1146 _("Status line output matched \"%s\" - "), server_expect);
1147 if (verbose)
1148 printf ("%s\n",msg);
1150 else {
1151 /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
1152 /* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
1153 /* Status-Code = 3 DIGITS */
1155 status_code = strchr (status_line, ' ') + sizeof (char);
1156 if (strspn (status_code, "1234567890") != 3)
1157 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line);
1159 http_status = atoi (status_code);
1161 /* check the return code */
1163 if (http_status >= 600 || http_status < 100) {
1164 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
1166 /* server errors result in a critical state */
1167 else if (http_status >= 500) {
1168 xasprintf (&msg, _("%s - "), status_line);
1169 result = STATE_CRITICAL;
1171 /* client errors result in a warning state */
1172 else if (http_status >= 400) {
1173 xasprintf (&msg, _("%s - "), status_line);
1174 result = max_state_alt(STATE_WARNING, result);
1176 /* check redirected page if specified */
1177 else if (http_status >= 300) {
1179 if (onredirect == STATE_DEPENDENT)
1180 redir (header, status_line);
1181 else
1182 result = max_state_alt(onredirect, result);
1183 xasprintf (&msg, _("%s - "), status_line);
1184 } /* end if (http_status >= 300) */
1185 else {
1186 /* Print OK status anyway */
1187 xasprintf (&msg, _("%s - "), status_line);
1190 } /* end else (server_expect_yn) */
1192 /* reset the alarm - must be called *after* redir or we'll never die on redirects! */
1193 alarm (0);
1195 if (maximum_age >= 0) {
1196 result = max_state_alt(check_document_dates(header, &msg), result);
1199 /* Page and Header content checks go here */
1200 if (strlen (header_expect)) {
1201 if (!strstr (header, header_expect)) {
1202 strncpy(&output_header_search[0],header_expect,sizeof(output_header_search));
1203 if(output_header_search[sizeof(output_header_search)-1]!='\0') {
1204 bcopy("...",&output_header_search[sizeof(output_header_search)-4],4);
1206 xasprintf (&msg, _("%sheader '%s' not found on '%s://%s:%d%s', "), msg, output_header_search, use_ssl ? "https" : "http", host_name ? host_name : server_address, server_port, server_url);
1207 result = STATE_CRITICAL;
1212 if (strlen (string_expect)) {
1213 if (!strstr (page, string_expect)) {
1214 strncpy(&output_string_search[0],string_expect,sizeof(output_string_search));
1215 if(output_string_search[sizeof(output_string_search)-1]!='\0') {
1216 bcopy("...",&output_string_search[sizeof(output_string_search)-4],4);
1218 xasprintf (&msg, _("%sstring '%s' not found on '%s://%s:%d%s', "), msg, output_string_search, use_ssl ? "https" : "http", host_name ? host_name : server_address, server_port, server_url);
1219 result = STATE_CRITICAL;
1223 if (strlen (regexp)) {
1224 errcode = regexec (&preg, page, REGS, pmatch, 0);
1225 if ((errcode == 0 && invert_regex == 0) || (errcode == REG_NOMATCH && invert_regex == 1)) {
1226 /* OK - No-op to avoid changing the logic around it */
1227 result = max_state_alt(STATE_OK, result);
1229 else if ((errcode == REG_NOMATCH && invert_regex == 0) || (errcode == 0 && invert_regex == 1)) {
1230 if (invert_regex == 0)
1231 xasprintf (&msg, _("%spattern not found, "), msg);
1232 else
1233 xasprintf (&msg, _("%spattern found, "), msg);
1234 result = STATE_CRITICAL;
1236 else {
1237 /* FIXME: Shouldn't that be UNKNOWN? */
1238 regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
1239 xasprintf (&msg, _("%sExecute Error: %s, "), msg, errbuf);
1240 result = STATE_CRITICAL;
1244 /* make sure the page is of an appropriate size */
1245 /* page_len = get_content_length(header); */
1246 /* FIXME: Will this work with -N ? IMHO we should use
1247 * get_content_length(header) and always check if it's different than the
1248 * returned pagesize
1250 /* FIXME: IIRC pagesize returns headers - shouldn't we make
1251 * it == get_content_length(header) ??
1253 page_len = pagesize;
1254 if ((max_page_len > 0) && (page_len > max_page_len)) {
1255 xasprintf (&msg, _("%spage size %d too large, "), msg, page_len);
1256 result = max_state_alt(STATE_WARNING, result);
1257 } else if ((min_page_len > 0) && (page_len < min_page_len)) {
1258 xasprintf (&msg, _("%spage size %d too small, "), msg, page_len);
1259 result = max_state_alt(STATE_WARNING, result);
1262 /* Cut-off trailing characters */
1263 if(msg[strlen(msg)-2] == ',')
1264 msg[strlen(msg)-2] = '\0';
1265 else
1266 msg[strlen(msg)-3] = '\0';
1268 /* check elapsed time */
1269 if (show_extended_perfdata)
1270 xasprintf (&msg,
1271 _("%s - %d bytes in %.3f second response time %s|%s %s %s %s %s %s %s"),
1272 msg, page_len, elapsed_time,
1273 (display_html ? "</A>" : ""),
1274 perfd_time (elapsed_time),
1275 perfd_size (page_len),
1276 perfd_time_connect (elapsed_time_connect),
1277 use_ssl == TRUE ? perfd_time_ssl (elapsed_time_ssl) : "",
1278 perfd_time_headers (elapsed_time_headers),
1279 perfd_time_firstbyte (elapsed_time_firstbyte),
1280 perfd_time_transfer (elapsed_time_transfer));
1281 else
1282 xasprintf (&msg,
1283 _("%s - %d bytes in %.3f second response time %s|%s %s"),
1284 msg, page_len, elapsed_time,
1285 (display_html ? "</A>" : ""),
1286 perfd_time (elapsed_time),
1287 perfd_size (page_len));
1289 result = max_state_alt(get_status(elapsed_time, thlds), result);
1291 die (result, "HTTP %s: %s\n", state_text(result), msg);
1292 /* die failed? */
1293 return STATE_UNKNOWN;
1298 /* per RFC 2396 */
1299 #define URI_HTTP "%5[HTPShtps]"
1300 #define URI_HOST "%255[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1301 #define URI_PORT "%6d" /* MAX_PORT's width is 5 chars, 6 to detect overflow */
1302 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1303 #define HD1 URI_HTTP "://" URI_HOST ":" URI_PORT "/" URI_PATH
1304 #define HD2 URI_HTTP "://" URI_HOST "/" URI_PATH
1305 #define HD3 URI_HTTP "://" URI_HOST ":" URI_PORT
1306 #define HD4 URI_HTTP "://" URI_HOST
1307 #define HD5 URI_PATH
1309 void
1310 redir (char *pos, char *status_line)
1312 int i = 0;
1313 char *x;
1314 char xx[2];
1315 char type[6];
1316 char *addr;
1317 char *url;
1319 addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
1320 if (addr == NULL)
1321 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate addr\n"));
1323 memset(addr, 0, MAX_IPV4_HOSTLENGTH);
1324 url = malloc (strcspn (pos, "\r\n"));
1325 if (url == NULL)
1326 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n"));
1328 while (pos) {
1329 sscanf (pos, "%1[Ll]%*1[Oo]%*1[Cc]%*1[Aa]%*1[Tt]%*1[Ii]%*1[Oo]%*1[Nn]:%n", xx, &i);
1330 if (i == 0) {
1331 pos += (size_t) strcspn (pos, "\r\n");
1332 pos += (size_t) strspn (pos, "\r\n");
1333 if (strlen(pos) == 0)
1334 die (STATE_UNKNOWN,
1335 _("HTTP UNKNOWN - Could not find redirect location - %s%s\n"),
1336 status_line, (display_html ? "</A>" : ""));
1337 continue;
1340 pos += i;
1341 pos += strspn (pos, " \t");
1344 * RFC 2616 (4.2): ``Header fields can be extended over multiple lines by
1345 * preceding each extra line with at least one SP or HT.''
1347 for (; (i = strspn (pos, "\r\n")); pos += i) {
1348 pos += i;
1349 if (!(i = strspn (pos, " \t"))) {
1350 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Empty redirect location%s\n"),
1351 display_html ? "</A>" : "");
1355 url = realloc (url, strcspn (pos, "\r\n") + 1);
1356 if (url == NULL)
1357 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n"));
1359 /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
1360 if (sscanf (pos, HD1, type, addr, &i, url) == 4) {
1361 url = prepend_slash (url);
1362 use_ssl = server_type_check (type);
1365 /* URI_HTTP URI_HOST URI_PATH */
1366 else if (sscanf (pos, HD2, type, addr, url) == 3 ) {
1367 url = prepend_slash (url);
1368 use_ssl = server_type_check (type);
1369 i = server_port_check (use_ssl);
1372 /* URI_HTTP URI_HOST URI_PORT */
1373 else if (sscanf (pos, HD3, type, addr, &i) == 3) {
1374 strcpy (url, HTTP_URL);
1375 use_ssl = server_type_check (type);
1378 /* URI_HTTP URI_HOST */
1379 else if (sscanf (pos, HD4, type, addr) == 2) {
1380 strcpy (url, HTTP_URL);
1381 use_ssl = server_type_check (type);
1382 i = server_port_check (use_ssl);
1385 /* URI_PATH */
1386 else if (sscanf (pos, HD5, url) == 1) {
1387 /* relative url */
1388 if ((url[0] != '/')) {
1389 if ((x = strrchr(server_url, '/')))
1390 *x = '\0';
1391 xasprintf (&url, "%s/%s", server_url, url);
1393 i = server_port;
1394 strcpy (type, server_type);
1395 strcpy (addr, host_name ? host_name : server_address);
1398 else {
1399 die (STATE_UNKNOWN,
1400 _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"),
1401 pos, (display_html ? "</A>" : ""));
1404 break;
1406 } /* end while (pos) */
1408 if (++redir_depth > max_depth)
1409 die (STATE_WARNING,
1410 _("HTTP WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
1411 max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
1413 if (server_port==i &&
1414 !strncmp(server_address, addr, MAX_IPV4_HOSTLENGTH) &&
1415 (host_name && !strncmp(host_name, addr, MAX_IPV4_HOSTLENGTH)) &&
1416 !strcmp(server_url, url))
1417 die (STATE_WARNING,
1418 _("HTTP WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
1419 type, addr, i, url, (display_html ? "</A>" : ""));
1421 strcpy (server_type, type);
1423 free (host_name);
1424 host_name = strndup (addr, MAX_IPV4_HOSTLENGTH);
1426 if (!(followsticky & STICKY_HOST)) {
1427 free (server_address);
1428 server_address = strndup (addr, MAX_IPV4_HOSTLENGTH);
1430 if (!(followsticky & STICKY_PORT)) {
1431 server_port = i;
1434 free (server_url);
1435 server_url = url;
1437 if (server_port > MAX_PORT)
1438 die (STATE_UNKNOWN,
1439 _("HTTP UNKNOWN - Redirection to port above %d - %s://%s:%d%s%s\n"),
1440 MAX_PORT, server_type, server_address, server_port, server_url,
1441 display_html ? "</A>" : "");
1443 /* reset virtual port */
1444 virtual_port = server_port;
1446 if (verbose)
1447 printf (_("Redirection to %s://%s:%d%s\n"), server_type,
1448 host_name ? host_name : server_address, server_port, server_url);
1450 free(addr);
1451 check_http ();
1456 server_type_check (const char *type)
1458 if (strcmp (type, "https"))
1459 return FALSE;
1460 else
1461 return TRUE;
1465 server_port_check (int ssl_flag)
1467 if (ssl_flag)
1468 return HTTPS_PORT;
1469 else
1470 return HTTP_PORT;
1473 char *perfd_time (double elapsed_time)
1475 return fperfdata ("time", elapsed_time, "s",
1476 thlds->warning?TRUE:FALSE, thlds->warning?thlds->warning->end:0,
1477 thlds->critical?TRUE:FALSE, thlds->critical?thlds->critical->end:0,
1478 TRUE, 0, FALSE, 0);
1481 char *perfd_time_connect (double elapsed_time_connect)
1483 return fperfdata ("time_connect", elapsed_time_connect, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0);
1486 char *perfd_time_ssl (double elapsed_time_ssl)
1488 return fperfdata ("time_ssl", elapsed_time_ssl, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0);
1491 char *perfd_time_headers (double elapsed_time_headers)
1493 return fperfdata ("time_headers", elapsed_time_headers, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0);
1496 char *perfd_time_firstbyte (double elapsed_time_firstbyte)
1498 return fperfdata ("time_firstbyte", elapsed_time_firstbyte, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0);
1501 char *perfd_time_transfer (double elapsed_time_transfer)
1503 return fperfdata ("time_transfer", elapsed_time_transfer, "s", FALSE, 0, FALSE, 0, FALSE, 0, FALSE, 0);
1506 char *perfd_size (int page_len)
1508 return perfdata ("size", page_len, "B",
1509 (min_page_len>0?TRUE:FALSE), min_page_len,
1510 (min_page_len>0?TRUE:FALSE), 0,
1511 TRUE, 0, FALSE, 0);
1514 void
1515 print_help (void)
1517 print_revision (progname, NP_VERSION);
1519 printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
1520 printf (COPYRIGHT, copyright, email);
1522 printf ("%s\n", _("This plugin tests the HTTP service on the specified host. It can test"));
1523 printf ("%s\n", _("normal (http) and secure (https) servers, follow redirects, search for"));
1524 printf ("%s\n", _("strings and regular expressions, check connection times, and report on"));
1525 printf ("%s\n", _("certificate expiration times."));
1527 printf ("\n\n");
1529 print_usage ();
1531 printf (_("NOTE: One or both of -H and -I must be specified"));
1533 printf ("\n");
1535 printf (UT_HELP_VRSN);
1536 printf (UT_EXTRA_OPTS);
1538 printf (" %s\n", "-H, --hostname=ADDRESS");
1539 printf (" %s\n", _("Host name argument for servers using host headers (virtual host)"));
1540 printf (" %s\n", _("Append a port to include it in the header (eg: example.com:5000)"));
1541 printf (" %s\n", "-I, --IP-address=ADDRESS");
1542 printf (" %s\n", _("IP address or name (use numeric address if possible to bypass DNS lookup)."));
1543 printf (" %s\n", "-p, --port=INTEGER");
1544 printf (" %s", _("Port number (default: "));
1545 printf ("%d)\n", HTTP_PORT);
1547 printf (UT_IPv46);
1549 #ifdef HAVE_SSL
1550 printf (" %s\n", "-S, --ssl=VERSION[+]");
1551 printf (" %s\n", _("Connect via SSL. Port defaults to 443. VERSION is optional, and prevents"));
1552 printf (" %s\n", _("auto-negotiation (2 = SSLv2, 3 = SSLv3, 1 = TLSv1, 1.1 = TLSv1.1,"));
1553 printf (" %s\n", _("1.2 = TLSv1.2). With a '+' suffix, newer versions are also accepted."));
1554 printf (" %s\n", "--sni");
1555 printf (" %s\n", _("Enable SSL/TLS hostname extension support (SNI)"));
1556 printf (" %s\n", "-C, --certificate=INTEGER[,INTEGER]");
1557 printf (" %s\n", _("Minimum number of days a certificate has to be valid. Port defaults to 443"));
1558 printf (" %s\n", _("(when this option is used the URL is not checked.)"));
1559 printf (" %s\n", "-J, --client-cert=FILE");
1560 printf (" %s\n", _("Name of file that contains the client certificate (PEM format)"));
1561 printf (" %s\n", _("to be used in establishing the SSL session"));
1562 printf (" %s\n", "-K, --private-key=FILE");
1563 printf (" %s\n", _("Name of file containing the private key (PEM format)"));
1564 printf (" %s\n", _("matching the client certificate"));
1565 #endif
1567 printf (" %s\n", "-e, --expect=STRING");
1568 printf (" %s\n", _("Comma-delimited list of strings, at least one of them is expected in"));
1569 printf (" %s", _("the first (status) line of the server response (default: "));
1570 printf ("%s)\n", HTTP_EXPECT);
1571 printf (" %s\n", _("If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)"));
1572 printf (" %s\n", "-d, --header-string=STRING");
1573 printf (" %s\n", _("String to expect in the response headers"));
1574 printf (" %s\n", "-s, --string=STRING");
1575 printf (" %s\n", _("String to expect in the content"));
1576 printf (" %s\n", "-u, --url=PATH");
1577 printf (" %s\n", _("URL to GET or POST (default: /)"));
1578 printf (" %s\n", "-P, --post=STRING");
1579 printf (" %s\n", _("URL encoded http POST data"));
1580 printf (" %s\n", "-j, --method=STRING (for example: HEAD, OPTIONS, TRACE, PUT, DELETE, CONNECT)");
1581 printf (" %s\n", _("Set HTTP method."));
1582 printf (" %s\n", "-N, --no-body");
1583 printf (" %s\n", _("Don't wait for document body: stop reading after headers."));
1584 printf (" %s\n", _("(Note that this still does an HTTP GET or POST, not a HEAD.)"));
1585 printf (" %s\n", "-M, --max-age=SECONDS");
1586 printf (" %s\n", _("Warn if document is more than SECONDS old. the number can also be of"));
1587 printf (" %s\n", _("the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days."));
1588 printf (" %s\n", "-T, --content-type=STRING");
1589 printf (" %s\n", _("specify Content-Type header media type when POSTing\n"));
1591 printf (" %s\n", "-l, --linespan");
1592 printf (" %s\n", _("Allow regex to span newlines (must precede -r or -R)"));
1593 printf (" %s\n", "-r, --regex, --ereg=STRING");
1594 printf (" %s\n", _("Search page for regex STRING"));
1595 printf (" %s\n", "-R, --eregi=STRING");
1596 printf (" %s\n", _("Search page for case-insensitive regex STRING"));
1597 printf (" %s\n", "--invert-regex");
1598 printf (" %s\n", _("Return CRITICAL if found, OK if not\n"));
1600 printf (" %s\n", "-a, --authorization=AUTH_PAIR");
1601 printf (" %s\n", _("Username:password on sites with basic authentication"));
1602 printf (" %s\n", "-b, --proxy-authorization=AUTH_PAIR");
1603 printf (" %s\n", _("Username:password on proxy-servers with basic authentication"));
1604 printf (" %s\n", "-A, --useragent=STRING");
1605 printf (" %s\n", _("String to be sent in http header as \"User Agent\""));
1606 printf (" %s\n", "-k, --header=STRING");
1607 printf (" %s\n", _("Any other tags to be sent in http header. Use multiple times for additional headers"));
1608 printf (" %s\n", "-E, --extended-perfdata");
1609 printf (" %s\n", _("Print additional performance data"));
1610 printf (" %s\n", "-L, --link");
1611 printf (" %s\n", _("Wrap output in HTML link (obsoleted by urlize)"));
1612 printf (" %s\n", "-f, --onredirect=<ok|warning|critical|follow|sticky|stickyport>");
1613 printf (" %s\n", _("How to handle redirected pages. sticky is like follow but stick to the"));
1614 printf (" %s\n", _("specified IP address. stickyport also ensures port stays the same."));
1615 printf (" %s\n", "-m, --pagesize=INTEGER<:INTEGER>");
1616 printf (" %s\n", _("Minimum page size required (bytes) : Maximum page size required (bytes)"));
1618 printf (UT_WARN_CRIT);
1620 printf (UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
1622 printf (UT_VERBOSE);
1624 printf ("\n");
1625 printf ("%s\n", _("Notes:"));
1626 printf (" %s\n", _("This plugin will attempt to open an HTTP connection with the host."));
1627 printf (" %s\n", _("Successful connects return STATE_OK, refusals and timeouts return STATE_CRITICAL"));
1628 printf (" %s\n", _("other errors return STATE_UNKNOWN. Successful connects, but incorrect reponse"));
1629 printf (" %s\n", _("messages from the host result in STATE_WARNING return values. If you are"));
1630 printf (" %s\n", _("checking a virtual server that uses 'host headers' you must supply the FQDN"));
1631 printf (" %s\n", _("(fully qualified domain name) as the [host_name] argument."));
1633 #ifdef HAVE_SSL
1634 printf ("\n");
1635 printf (" %s\n", _("This plugin can also check whether an SSL enabled web server is able to"));
1636 printf (" %s\n", _("serve content (optionally within a specified time) or whether the X509 "));
1637 printf (" %s\n", _("certificate is still valid for the specified number of days."));
1638 printf ("\n");
1639 printf (" %s\n", _("Please note that this plugin does not check if the presented server"));
1640 printf (" %s\n", _("certificate matches the hostname of the server, or if the certificate"));
1641 printf (" %s\n", _("has a valid chain of trust to one of the locally installed CAs."));
1642 printf ("\n");
1643 printf ("%s\n", _("Examples:"));
1644 printf (" %s\n\n", "CHECK CONTENT: check_http -w 5 -c 10 --ssl -H www.verisign.com");
1645 printf (" %s\n", _("When the 'www.verisign.com' server returns its content within 5 seconds,"));
1646 printf (" %s\n", _("a STATE_OK will be returned. When the server returns its content but exceeds"));
1647 printf (" %s\n", _("the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,"));
1648 printf (" %s\n", _("a STATE_CRITICAL will be returned."));
1649 printf ("\n");
1650 printf (" %s\n\n", "CHECK CERTIFICATE: check_http -H www.verisign.com -C 14");
1651 printf (" %s\n", _("When the certificate of 'www.verisign.com' is valid for more than 14 days,"));
1652 printf (" %s\n", _("a STATE_OK is returned. When the certificate is still valid, but for less than"));
1653 printf (" %s\n", _("14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when"));
1654 printf (" %s\n\n", _("the certificate is expired."));
1655 printf ("\n");
1656 printf (" %s\n\n", "CHECK CERTIFICATE: check_http -H www.verisign.com -C 30,14");
1657 printf (" %s\n", _("When the certificate of 'www.verisign.com' is valid for more than 30 days,"));
1658 printf (" %s\n", _("a STATE_OK is returned. When the certificate is still valid, but for less than"));
1659 printf (" %s\n", _("30 days, but more than 14 days, a STATE_WARNING is returned."));
1660 printf (" %s\n", _("A STATE_CRITICAL will be returned when certificate expires in less than 14 days"));
1662 printf (" %s\n\n", "CHECK SSL WEBSERVER CONTENT VIA PROXY USING HTTP 1.1 CONNECT: ");
1663 printf (" %s\n", _("check_http -I 192.168.100.35 -p 80 -u https://www.verisign.com/ -S -j CONNECT -H www.verisign.com "));
1664 printf (" %s\n", _("all these options are needed: -I <proxy> -p <proxy-port> -u <check-url> -S(sl) -j CONNECT -H <webserver>"));
1665 printf (" %s\n", _("a STATE_OK will be returned. When the server returns its content but exceeds"));
1666 printf (" %s\n", _("the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,"));
1667 printf (" %s\n", _("a STATE_CRITICAL will be returned."));
1669 #endif
1671 printf (UT_SUPPORT);
1677 void
1678 print_usage (void)
1680 printf ("%s\n", _("Usage:"));
1681 printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
1682 printf (" [-J <client certificate file>] [-K <private key>]\n");
1683 printf (" [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L] [-E] [-a auth]\n");
1684 printf (" [-b proxy_auth] [-f <ok|warning|critcal|follow|sticky|stickyport>]\n");
1685 printf (" [-e <expect>] [-d string] [-s string] [-l] [-r <regex> | -R <case-insensitive regex>]\n");
1686 printf (" [-P string] [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>]\n");
1687 printf (" [-A string] [-k string] [-S <version>] [--sni] [-C <warn_age>[,<crit_age>]]\n");
1688 printf (" [-T <content-type>] [-j method]\n");