Merge pull request #1563 from jacobbaungard/ipv6_check_icmp
[monitoring-plugins.git] / plugins / check_http.c
blobde59a06840a21bd3b13930496b2f1f6d1a4363b4
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 show_body = FALSE;
124 int sd;
125 int min_page_len = 0;
126 int max_page_len = 0;
127 int redir_depth = 0;
128 int max_depth = 15;
129 char *http_method;
130 char *http_method_proxy;
131 char *http_post_data;
132 char *http_content_type;
133 char buffer[MAX_INPUT_BUFFER];
134 char *client_cert = NULL;
135 char *client_privkey = NULL;
137 int process_arguments (int, char **);
138 int check_http (void);
139 void redir (char *pos, char *status_line);
140 int server_type_check(const char *type);
141 int server_port_check(int ssl_flag);
142 char *perfd_time (double microsec);
143 char *perfd_time_connect (double microsec);
144 char *perfd_time_ssl (double microsec);
145 char *perfd_time_firstbyte (double microsec);
146 char *perfd_time_headers (double microsec);
147 char *perfd_time_transfer (double microsec);
148 char *perfd_size (int page_len);
149 void print_help (void);
150 void print_usage (void);
153 main (int argc, char **argv)
155 int result = STATE_UNKNOWN;
157 setlocale (LC_ALL, "");
158 bindtextdomain (PACKAGE, LOCALEDIR);
159 textdomain (PACKAGE);
161 /* Set default URL. Must be malloced for subsequent realloc if --onredirect=follow */
162 server_url = strdup(HTTP_URL);
163 server_url_length = strlen(server_url);
164 xasprintf (&user_agent, "User-Agent: check_http/v%s (monitoring-plugins %s)",
165 NP_VERSION, VERSION);
167 /* Parse extra opts if any */
168 argv=np_extra_opts (&argc, argv, progname);
170 if (process_arguments (argc, argv) == ERROR)
171 usage4 (_("Could not parse arguments"));
173 if (display_html == TRUE)
174 printf ("<A HREF=\"%s://%s:%d%s\" target=\"_blank\">",
175 use_ssl ? "https" : "http", host_name ? host_name : server_address,
176 server_port, server_url);
178 /* initialize alarm signal handling, set socket timeout, start timer */
179 (void) signal (SIGALRM, socket_timeout_alarm_handler);
180 (void) alarm (socket_timeout);
181 gettimeofday (&tv, NULL);
183 result = check_http ();
184 return result;
187 /* check whether a file exists */
188 void
189 test_file (char *path)
191 if (access(path, R_OK) == 0)
192 return;
193 usage2 (_("file does not exist or is not readable"), path);
196 /* process command-line arguments */
198 process_arguments (int argc, char **argv)
200 int c = 1;
201 char *p;
202 char *temp;
204 enum {
205 INVERT_REGEX = CHAR_MAX + 1,
206 SNI_OPTION
209 int option = 0;
210 static struct option longopts[] = {
211 STD_LONG_OPTS,
212 {"link", no_argument, 0, 'L'},
213 {"nohtml", no_argument, 0, 'n'},
214 {"ssl", optional_argument, 0, 'S'},
215 {"sni", no_argument, 0, SNI_OPTION},
216 {"post", required_argument, 0, 'P'},
217 {"method", required_argument, 0, 'j'},
218 {"IP-address", required_argument, 0, 'I'},
219 {"url", required_argument, 0, 'u'},
220 {"port", required_argument, 0, 'p'},
221 {"authorization", required_argument, 0, 'a'},
222 {"proxy-authorization", required_argument, 0, 'b'},
223 {"header-string", required_argument, 0, 'd'},
224 {"string", required_argument, 0, 's'},
225 {"expect", required_argument, 0, 'e'},
226 {"regex", required_argument, 0, 'r'},
227 {"ereg", required_argument, 0, 'r'},
228 {"eregi", required_argument, 0, 'R'},
229 {"linespan", no_argument, 0, 'l'},
230 {"onredirect", required_argument, 0, 'f'},
231 {"certificate", required_argument, 0, 'C'},
232 {"client-cert", required_argument, 0, 'J'},
233 {"private-key", required_argument, 0, 'K'},
234 {"useragent", required_argument, 0, 'A'},
235 {"header", required_argument, 0, 'k'},
236 {"no-body", no_argument, 0, 'N'},
237 {"max-age", required_argument, 0, 'M'},
238 {"content-type", required_argument, 0, 'T'},
239 {"pagesize", required_argument, 0, 'm'},
240 {"invert-regex", no_argument, NULL, INVERT_REGEX},
241 {"use-ipv4", no_argument, 0, '4'},
242 {"use-ipv6", no_argument, 0, '6'},
243 {"extended-perfdata", no_argument, 0, 'E'},
244 {"show-body", no_argument, 0, 'B'},
245 {0, 0, 0, 0}
248 if (argc < 2)
249 return ERROR;
251 for (c = 1; c < argc; c++) {
252 if (strcmp ("-to", argv[c]) == 0)
253 strcpy (argv[c], "-t");
254 if (strcmp ("-hn", argv[c]) == 0)
255 strcpy (argv[c], "-H");
256 if (strcmp ("-wt", argv[c]) == 0)
257 strcpy (argv[c], "-w");
258 if (strcmp ("-ct", argv[c]) == 0)
259 strcpy (argv[c], "-c");
260 if (strcmp ("-nohtml", argv[c]) == 0)
261 strcpy (argv[c], "-n");
264 while (1) {
265 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:NEB", longopts, &option);
266 if (c == -1 || c == EOF)
267 break;
269 switch (c) {
270 case '?': /* usage */
271 usage5 ();
272 break;
273 case 'h': /* help */
274 print_help ();
275 exit (STATE_UNKNOWN);
276 break;
277 case 'V': /* version */
278 print_revision (progname, NP_VERSION);
279 exit (STATE_UNKNOWN);
280 break;
281 case 't': /* timeout period */
282 if (!is_intnonneg (optarg))
283 usage2 (_("Timeout interval must be a positive integer"), optarg);
284 else
285 socket_timeout = atoi (optarg);
286 break;
287 case 'c': /* critical time threshold */
288 critical_thresholds = optarg;
289 break;
290 case 'w': /* warning time threshold */
291 warning_thresholds = optarg;
292 break;
293 case 'A': /* User Agent String */
294 xasprintf (&user_agent, "User-Agent: %s", optarg);
295 break;
296 case 'k': /* Additional headers */
297 if (http_opt_headers_count == 0)
298 http_opt_headers = malloc (sizeof (char *) * (++http_opt_headers_count));
299 else
300 http_opt_headers = realloc (http_opt_headers, sizeof (char *) * (++http_opt_headers_count));
301 http_opt_headers[http_opt_headers_count - 1] = optarg;
302 /* xasprintf (&http_opt_headers, "%s", optarg); */
303 break;
304 case 'L': /* show html link */
305 display_html = TRUE;
306 break;
307 case 'n': /* do not show html link */
308 display_html = FALSE;
309 break;
310 case 'C': /* Check SSL cert validity */
311 #ifdef HAVE_SSL
312 if ((temp=strchr(optarg,','))!=NULL) {
313 *temp='\0';
314 if (!is_intnonneg (optarg))
315 usage2 (_("Invalid certificate expiration period"), optarg);
316 days_till_exp_warn = atoi(optarg);
317 *temp=',';
318 temp++;
319 if (!is_intnonneg (temp))
320 usage2 (_("Invalid certificate expiration period"), temp);
321 days_till_exp_crit = atoi (temp);
323 else {
324 days_till_exp_crit=0;
325 if (!is_intnonneg (optarg))
326 usage2 (_("Invalid certificate expiration period"), optarg);
327 days_till_exp_warn = atoi (optarg);
329 check_cert = TRUE;
330 goto enable_ssl;
331 #endif
332 case 'J': /* use client certificate */
333 #ifdef HAVE_SSL
334 test_file(optarg);
335 client_cert = optarg;
336 goto enable_ssl;
337 #endif
338 case 'K': /* use client private key */
339 #ifdef HAVE_SSL
340 test_file(optarg);
341 client_privkey = optarg;
342 goto enable_ssl;
343 #endif
344 case 'S': /* use SSL */
345 #ifdef HAVE_SSL
346 enable_ssl:
347 /* ssl_version initialized to 0 as a default. Only set if it's non-zero. This helps when we include multiple
348 parameters, like -S and -C combinations */
349 use_ssl = TRUE;
350 if (c=='S' && optarg != NULL) {
351 int got_plus = strchr(optarg, '+') != NULL;
353 if (!strncmp (optarg, "1.2", 3))
354 ssl_version = got_plus ? MP_TLSv1_2_OR_NEWER : MP_TLSv1_2;
355 else if (!strncmp (optarg, "1.1", 3))
356 ssl_version = got_plus ? MP_TLSv1_1_OR_NEWER : MP_TLSv1_1;
357 else if (optarg[0] == '1')
358 ssl_version = got_plus ? MP_TLSv1_OR_NEWER : MP_TLSv1;
359 else if (optarg[0] == '3')
360 ssl_version = got_plus ? MP_SSLv3_OR_NEWER : MP_SSLv3;
361 else if (optarg[0] == '2')
362 ssl_version = got_plus ? MP_SSLv2_OR_NEWER : MP_SSLv2;
363 else
364 usage4 (_("Invalid option - Valid SSL/TLS versions: 2, 3, 1, 1.1, 1.2 (with optional '+' suffix)"));
366 if (specify_port == FALSE)
367 server_port = HTTPS_PORT;
368 #else
369 /* -C -J and -K fall through to here without SSL */
370 usage4 (_("Invalid option - SSL is not available"));
371 #endif
372 break;
373 case SNI_OPTION:
374 use_sni = TRUE;
375 break;
376 case 'f': /* onredirect */
377 if (!strcmp (optarg, "stickyport"))
378 onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST|STICKY_PORT;
379 else if (!strcmp (optarg, "sticky"))
380 onredirect = STATE_DEPENDENT, followsticky = STICKY_HOST;
381 else if (!strcmp (optarg, "follow"))
382 onredirect = STATE_DEPENDENT, followsticky = STICKY_NONE;
383 else if (!strcmp (optarg, "unknown"))
384 onredirect = STATE_UNKNOWN;
385 else if (!strcmp (optarg, "ok"))
386 onredirect = STATE_OK;
387 else if (!strcmp (optarg, "warning"))
388 onredirect = STATE_WARNING;
389 else if (!strcmp (optarg, "critical"))
390 onredirect = STATE_CRITICAL;
391 else usage2 (_("Invalid onredirect option"), optarg);
392 if (verbose)
393 printf(_("option f:%d \n"), onredirect);
394 break;
395 /* Note: H, I, and u must be malloc'd or will fail on redirects */
396 case 'H': /* Host Name (virtual host) */
397 host_name = strdup (optarg);
398 if (host_name[0] == '[') {
399 if ((p = strstr (host_name, "]:")) != NULL) { /* [IPv6]:port */
400 virtual_port = atoi (p + 2);
401 /* cut off the port */
402 host_name_length = strlen (host_name) - strlen (p) - 1;
403 free (host_name);
404 host_name = strndup (optarg, host_name_length);
405 if (specify_port == FALSE)
406 server_port = virtual_port;
408 } else if ((p = strchr (host_name, ':')) != NULL
409 && strchr (++p, ':') == NULL) { /* IPv4:port or host:port */
410 virtual_port = atoi (p);
411 /* cut off the port */
412 host_name_length = strlen (host_name) - strlen (p) - 1;
413 free (host_name);
414 host_name = strndup (optarg, host_name_length);
415 if (specify_port == FALSE)
416 server_port = virtual_port;
418 break;
419 case 'I': /* Server IP-address */
420 server_address = strdup (optarg);
421 break;
422 case 'u': /* URL path */
423 server_url = strdup (optarg);
424 server_url_length = strlen (server_url);
425 break;
426 case 'p': /* Server port */
427 if (!is_intnonneg (optarg))
428 usage2 (_("Invalid port number"), optarg);
429 else {
430 server_port = atoi (optarg);
431 specify_port = TRUE;
433 break;
434 case 'a': /* authorization info */
435 strncpy (user_auth, optarg, MAX_INPUT_BUFFER - 1);
436 user_auth[MAX_INPUT_BUFFER - 1] = 0;
437 break;
438 case 'b': /* proxy-authorization info */
439 strncpy (proxy_auth, optarg, MAX_INPUT_BUFFER - 1);
440 proxy_auth[MAX_INPUT_BUFFER - 1] = 0;
441 break;
442 case 'P': /* HTTP POST data in URL encoded format; ignored if settings already */
443 if (! http_post_data)
444 http_post_data = strdup (optarg);
445 if (! http_method)
446 http_method = strdup("POST");
447 break;
448 case 'j': /* Set HTTP method */
449 if (http_method)
450 free(http_method);
451 http_method = strdup (optarg);
452 char *tmp;
453 if ((tmp = strstr(http_method, ":")) > 0) {
454 tmp[0] = '\0';
455 http_method = http_method;
456 http_method_proxy = ++tmp;
458 break;
459 case 'd': /* string or substring */
460 strncpy (header_expect, optarg, MAX_INPUT_BUFFER - 1);
461 header_expect[MAX_INPUT_BUFFER - 1] = 0;
462 break;
463 case 's': /* string or substring */
464 strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1);
465 string_expect[MAX_INPUT_BUFFER - 1] = 0;
466 break;
467 case 'e': /* string or substring */
468 strncpy (server_expect, optarg, MAX_INPUT_BUFFER - 1);
469 server_expect[MAX_INPUT_BUFFER - 1] = 0;
470 server_expect_yn = 1;
471 break;
472 case 'T': /* Content-type */
473 xasprintf (&http_content_type, "%s", optarg);
474 break;
475 case 'l': /* linespan */
476 cflags &= ~REG_NEWLINE;
477 break;
478 case 'R': /* regex */
479 cflags |= REG_ICASE;
480 case 'r': /* regex */
481 strncpy (regexp, optarg, MAX_RE_SIZE - 1);
482 regexp[MAX_RE_SIZE - 1] = 0;
483 errcode = regcomp (&preg, regexp, cflags);
484 if (errcode != 0) {
485 (void) regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
486 printf (_("Could Not Compile Regular Expression: %s"), errbuf);
487 return ERROR;
489 break;
490 case INVERT_REGEX:
491 invert_regex = 1;
492 break;
493 case '4':
494 address_family = AF_INET;
495 break;
496 case '6':
497 #ifdef USE_IPV6
498 address_family = AF_INET6;
499 #else
500 usage4 (_("IPv6 support not available"));
501 #endif
502 break;
503 case 'v': /* verbose */
504 verbose = TRUE;
505 break;
506 case 'm': /* min_page_length */
508 char *tmp;
509 if (strchr(optarg, ':') != (char *)NULL) {
510 /* range, so get two values, min:max */
511 tmp = strtok(optarg, ":");
512 if (tmp == NULL) {
513 printf("Bad format: try \"-m min:max\"\n");
514 exit (STATE_WARNING);
515 } else
516 min_page_len = atoi(tmp);
518 tmp = strtok(NULL, ":");
519 if (tmp == NULL) {
520 printf("Bad format: try \"-m min:max\"\n");
521 exit (STATE_WARNING);
522 } else
523 max_page_len = atoi(tmp);
524 } else
525 min_page_len = atoi (optarg);
526 break;
528 case 'N': /* no-body */
529 no_body = TRUE;
530 break;
531 case 'M': /* max-age */
533 int L = strlen(optarg);
534 if (L && optarg[L-1] == 'm')
535 maximum_age = atoi (optarg) * 60;
536 else if (L && optarg[L-1] == 'h')
537 maximum_age = atoi (optarg) * 60 * 60;
538 else if (L && optarg[L-1] == 'd')
539 maximum_age = atoi (optarg) * 60 * 60 * 24;
540 else if (L && (optarg[L-1] == 's' ||
541 isdigit (optarg[L-1])))
542 maximum_age = atoi (optarg);
543 else {
544 fprintf (stderr, "unparsable max-age: %s\n", optarg);
545 exit (STATE_WARNING);
548 break;
549 case 'E': /* show extended perfdata */
550 show_extended_perfdata = TRUE;
551 break;
552 case 'B': /* print body content after status line */
553 show_body = TRUE;
554 break;
558 c = optind;
560 if (server_address == NULL && c < argc)
561 server_address = strdup (argv[c++]);
563 if (host_name == NULL && c < argc)
564 host_name = strdup (argv[c++]);
566 if (server_address == NULL) {
567 if (host_name == NULL)
568 usage4 (_("You must specify a server address or host name"));
569 else
570 server_address = strdup (host_name);
573 set_thresholds(&thlds, warning_thresholds, critical_thresholds);
575 if (critical_thresholds && thlds->critical->end>(double)socket_timeout)
576 socket_timeout = (int)thlds->critical->end + 1;
578 if (http_method == NULL)
579 http_method = strdup ("GET");
581 if (http_method_proxy == NULL)
582 http_method_proxy = strdup ("GET");
584 if (client_cert && !client_privkey)
585 usage4 (_("If you use a client certificate you must also specify a private key file"));
587 if (virtual_port == 0)
588 virtual_port = server_port;
590 return TRUE;
595 /* Returns 1 if we're done processing the document body; 0 to keep going */
596 static int
597 document_headers_done (char *full_page)
599 const char *body;
601 for (body = full_page; *body; body++) {
602 if (!strncmp (body, "\n\n", 2) || !strncmp (body, "\n\r\n", 3))
603 break;
606 if (!*body)
607 return 0; /* haven't read end of headers yet */
609 full_page[body - full_page] = 0;
610 return 1;
613 static time_t
614 parse_time_string (const char *string)
616 struct tm tm;
617 time_t t;
618 memset (&tm, 0, sizeof(tm));
620 /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
622 if (isupper (string[0]) && /* Tue */
623 islower (string[1]) &&
624 islower (string[2]) &&
625 ',' == string[3] &&
626 ' ' == string[4] &&
627 (isdigit(string[5]) || string[5] == ' ') && /* 25 */
628 isdigit (string[6]) &&
629 ' ' == string[7] &&
630 isupper (string[8]) && /* Dec */
631 islower (string[9]) &&
632 islower (string[10]) &&
633 ' ' == string[11] &&
634 isdigit (string[12]) && /* 2001 */
635 isdigit (string[13]) &&
636 isdigit (string[14]) &&
637 isdigit (string[15]) &&
638 ' ' == string[16] &&
639 isdigit (string[17]) && /* 02: */
640 isdigit (string[18]) &&
641 ':' == string[19] &&
642 isdigit (string[20]) && /* 59: */
643 isdigit (string[21]) &&
644 ':' == string[22] &&
645 isdigit (string[23]) && /* 03 */
646 isdigit (string[24]) &&
647 ' ' == string[25] &&
648 'G' == string[26] && /* GMT */
649 'M' == string[27] && /* GMT */
650 'T' == string[28]) {
652 tm.tm_sec = 10 * (string[23]-'0') + (string[24]-'0');
653 tm.tm_min = 10 * (string[20]-'0') + (string[21]-'0');
654 tm.tm_hour = 10 * (string[17]-'0') + (string[18]-'0');
655 tm.tm_mday = 10 * (string[5] == ' ' ? 0 : string[5]-'0') + (string[6]-'0');
656 tm.tm_mon = (!strncmp (string+8, "Jan", 3) ? 0 :
657 !strncmp (string+8, "Feb", 3) ? 1 :
658 !strncmp (string+8, "Mar", 3) ? 2 :
659 !strncmp (string+8, "Apr", 3) ? 3 :
660 !strncmp (string+8, "May", 3) ? 4 :
661 !strncmp (string+8, "Jun", 3) ? 5 :
662 !strncmp (string+8, "Jul", 3) ? 6 :
663 !strncmp (string+8, "Aug", 3) ? 7 :
664 !strncmp (string+8, "Sep", 3) ? 8 :
665 !strncmp (string+8, "Oct", 3) ? 9 :
666 !strncmp (string+8, "Nov", 3) ? 10 :
667 !strncmp (string+8, "Dec", 3) ? 11 :
668 -1);
669 tm.tm_year = ((1000 * (string[12]-'0') +
670 100 * (string[13]-'0') +
671 10 * (string[14]-'0') +
672 (string[15]-'0'))
673 - 1900);
675 tm.tm_isdst = 0; /* GMT is never in DST, right? */
677 if (tm.tm_mon < 0 || tm.tm_mday < 1 || tm.tm_mday > 31)
678 return 0;
681 This is actually wrong: we need to subtract the local timezone
682 offset from GMT from this value. But, that's ok in this usage,
683 because we only comparing these two GMT dates against each other,
684 so it doesn't matter what time zone we parse them in.
687 t = mktime (&tm);
688 if (t == (time_t) -1) t = 0;
690 if (verbose) {
691 const char *s = string;
692 while (*s && *s != '\r' && *s != '\n')
693 fputc (*s++, stdout);
694 printf (" ==> %lu\n", (unsigned long) t);
697 return t;
699 } else {
700 return 0;
704 /* Checks if the server 'reply' is one of the expected 'statuscodes' */
705 static int
706 expected_statuscode (const char *reply, const char *statuscodes)
708 char *expected, *code;
709 int result = 0;
711 if ((expected = strdup (statuscodes)) == NULL)
712 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
714 for (code = strtok (expected, ","); code != NULL; code = strtok (NULL, ","))
715 if (strstr (reply, code) != NULL) {
716 result = 1;
717 break;
720 free (expected);
721 return result;
724 static int
725 check_document_dates (const char *headers, char **msg)
727 const char *s;
728 char *server_date = 0;
729 char *document_date = 0;
730 int date_result = STATE_OK;
732 s = headers;
733 while (*s) {
734 const char *field = s;
735 const char *value = 0;
737 /* Find the end of the header field */
738 while (*s && !isspace(*s) && *s != ':')
739 s++;
741 /* Remember the header value, if any. */
742 if (*s == ':')
743 value = ++s;
745 /* Skip to the end of the header, including continuation lines. */
746 while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
747 s++;
749 /* Avoid stepping over end-of-string marker */
750 if (*s)
751 s++;
753 /* Process this header. */
754 if (value && value > field+2) {
755 char *ff = (char *) malloc (value-field);
756 char *ss = ff;
757 while (field < value-1)
758 *ss++ = tolower(*field++);
759 *ss++ = 0;
761 if (!strcmp (ff, "date") || !strcmp (ff, "last-modified")) {
762 const char *e;
763 while (*value && isspace (*value))
764 value++;
765 for (e = value; *e && *e != '\r' && *e != '\n'; e++)
767 ss = (char *) malloc (e - value + 1);
768 strncpy (ss, value, e - value);
769 ss[e - value] = 0;
770 if (!strcmp (ff, "date")) {
771 if (server_date) free (server_date);
772 server_date = ss;
773 } else {
774 if (document_date) free (document_date);
775 document_date = ss;
778 free (ff);
782 /* Done parsing the body. Now check the dates we (hopefully) parsed. */
783 if (!server_date || !*server_date) {
784 xasprintf (msg, _("%sServer date unknown, "), *msg);
785 date_result = max_state_alt(STATE_UNKNOWN, date_result);
786 } else if (!document_date || !*document_date) {
787 xasprintf (msg, _("%sDocument modification date unknown, "), *msg);
788 date_result = max_state_alt(STATE_CRITICAL, date_result);
789 } else {
790 time_t srv_data = parse_time_string (server_date);
791 time_t doc_data = parse_time_string (document_date);
793 if (srv_data <= 0) {
794 xasprintf (msg, _("%sServer date \"%100s\" unparsable, "), *msg, server_date);
795 date_result = max_state_alt(STATE_CRITICAL, date_result);
796 } else if (doc_data <= 0) {
797 xasprintf (msg, _("%sDocument date \"%100s\" unparsable, "), *msg, document_date);
798 date_result = max_state_alt(STATE_CRITICAL, date_result);
799 } else if (doc_data > srv_data + 30) {
800 xasprintf (msg, _("%sDocument is %d seconds in the future, "), *msg, (int)doc_data - (int)srv_data);
801 date_result = max_state_alt(STATE_CRITICAL, date_result);
802 } else if (doc_data < srv_data - maximum_age) {
803 int n = (srv_data - doc_data);
804 if (n > (60 * 60 * 24 * 2)) {
805 xasprintf (msg, _("%sLast modified %.1f days ago, "), *msg, ((float) n) / (60 * 60 * 24));
806 date_result = max_state_alt(STATE_CRITICAL, date_result);
807 } else {
808 xasprintf (msg, _("%sLast modified %d:%02d:%02d ago, "), *msg, n / (60 * 60), (n / 60) % 60, n % 60);
809 date_result = max_state_alt(STATE_CRITICAL, date_result);
812 free (server_date);
813 free (document_date);
815 return date_result;
819 get_content_length (const char *headers)
821 const char *s;
822 int content_length = 0;
824 s = headers;
825 while (*s) {
826 const char *field = s;
827 const char *value = 0;
829 /* Find the end of the header field */
830 while (*s && !isspace(*s) && *s != ':')
831 s++;
833 /* Remember the header value, if any. */
834 if (*s == ':')
835 value = ++s;
837 /* Skip to the end of the header, including continuation lines. */
838 while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t')))
839 s++;
841 /* Avoid stepping over end-of-string marker */
842 if (*s)
843 s++;
845 /* Process this header. */
846 if (value && value > field+2) {
847 char *ff = (char *) malloc (value-field);
848 char *ss = ff;
849 while (field < value-1)
850 *ss++ = tolower(*field++);
851 *ss++ = 0;
853 if (!strcmp (ff, "content-length")) {
854 const char *e;
855 while (*value && isspace (*value))
856 value++;
857 for (e = value; *e && *e != '\r' && *e != '\n'; e++)
859 ss = (char *) malloc (e - value + 1);
860 strncpy (ss, value, e - value);
861 ss[e - value] = 0;
862 content_length = atoi(ss);
863 free (ss);
865 free (ff);
868 return (content_length);
871 char *
872 prepend_slash (char *path)
874 char *newpath;
876 if (path[0] == '/')
877 return path;
879 if ((newpath = malloc (strlen(path) + 2)) == NULL)
880 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
881 newpath[0] = '/';
882 strcpy (newpath + 1, path);
883 free (path);
884 return newpath;
888 check_http (void)
890 char *msg;
891 char *status_line;
892 char *status_code;
893 char *header;
894 char *page;
895 char *auth;
896 int http_status;
897 int i = 0;
898 size_t pagesize = 0;
899 char *full_page;
900 char *full_page_new;
901 char *buf;
902 char *pos;
903 long microsec = 0L;
904 double elapsed_time = 0.0;
905 long microsec_connect = 0L;
906 double elapsed_time_connect = 0.0;
907 long microsec_ssl = 0L;
908 double elapsed_time_ssl = 0.0;
909 long microsec_firstbyte = 0L;
910 double elapsed_time_firstbyte = 0.0;
911 long microsec_headers = 0L;
912 double elapsed_time_headers = 0.0;
913 long microsec_transfer = 0L;
914 double elapsed_time_transfer = 0.0;
915 int page_len = 0;
916 int result = STATE_OK;
917 char *force_host_header = NULL;
919 /* try to connect to the host at the given port number */
920 gettimeofday (&tv_temp, NULL);
921 if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK)
922 die (STATE_CRITICAL, _("HTTP CRITICAL - Unable to open TCP socket\n"));
923 microsec_connect = deltime (tv_temp);
925 /* if we are called with the -I option, the -j method is CONNECT and */
926 /* we received -S for SSL, then we tunnel the request through a proxy*/
927 /* @20100414, public[at]frank4dd.com, http://www.frank4dd.com/howto */
929 if ( server_address != NULL && strcmp(http_method, "CONNECT") == 0
930 && host_name != NULL && use_ssl == TRUE) {
932 if (verbose) printf ("Entering CONNECT tunnel mode with proxy %s:%d to dst %s:%d\n", server_address, server_port, host_name, HTTPS_PORT);
933 asprintf (&buf, "%s %s:%d HTTP/1.1\r\n%s\r\n", http_method, host_name, HTTPS_PORT, user_agent);
934 asprintf (&buf, "%sProxy-Connection: keep-alive\r\n", buf);
935 asprintf (&buf, "%sHost: %s\r\n", buf, host_name);
936 /* we finished our request, send empty line with CRLF */
937 asprintf (&buf, "%s%s", buf, CRLF);
938 if (verbose) printf ("%s\n", buf);
939 send(sd, buf, strlen (buf), 0);
940 buf[0]='\0';
942 if (verbose) printf ("Receive response from proxy\n");
943 read (sd, buffer, MAX_INPUT_BUFFER-1);
944 if (verbose) printf ("%s", buffer);
945 /* Here we should check if we got HTTP/1.1 200 Connection established */
947 #ifdef HAVE_SSL
948 elapsed_time_connect = (double)microsec_connect / 1.0e6;
949 if (use_ssl == TRUE) {
950 gettimeofday (&tv_temp, NULL);
951 result = np_net_ssl_init_with_hostname_version_and_cert(sd, (use_sni ? host_name : NULL), ssl_version, client_cert, client_privkey);
952 if (verbose) printf ("SSL initialized\n");
953 if (result != STATE_OK)
954 die (STATE_CRITICAL, NULL);
955 microsec_ssl = deltime (tv_temp);
956 elapsed_time_ssl = (double)microsec_ssl / 1.0e6;
957 if (check_cert == TRUE) {
958 result = np_net_ssl_check_cert(days_till_exp_warn, days_till_exp_crit);
959 if (sd) close(sd);
960 np_net_ssl_cleanup();
961 return result;
964 #endif /* HAVE_SSL */
966 if ( server_address != NULL && strcmp(http_method, "CONNECT") == 0
967 && host_name != NULL && use_ssl == TRUE)
968 asprintf (&buf, "%s %s %s\r\n%s\r\n", http_method_proxy, server_url, host_name ? "HTTP/1.1" : "HTTP/1.0", user_agent);
969 else
970 asprintf (&buf, "%s %s %s\r\n%s\r\n", http_method, server_url, host_name ? "HTTP/1.1" : "HTTP/1.0", user_agent);
972 /* tell HTTP/1.1 servers not to keep the connection alive */
973 xasprintf (&buf, "%sConnection: close\r\n", buf);
975 /* check if Host header is explicitly set in options */
976 if (http_opt_headers_count) {
977 for (i = 0; i < http_opt_headers_count ; i++) {
978 if (strncmp(http_opt_headers[i], "Host:", 5) == 0) {
979 force_host_header = http_opt_headers[i];
984 /* optionally send the host header info */
985 if (host_name) {
986 if (force_host_header) {
987 xasprintf (&buf, "%s%s\r\n", buf, force_host_header);
989 else {
991 * Specify the port only if we're using a non-default port (see RFC 2616,
992 * 14.23). Some server applications/configurations cause trouble if the
993 * (default) port is explicitly specified in the "Host:" header line.
995 if ((use_ssl == FALSE && virtual_port == HTTP_PORT) ||
996 (use_ssl == TRUE && virtual_port == HTTPS_PORT) ||
997 (server_address != NULL && strcmp(http_method, "CONNECT") == 0
998 && host_name != NULL && use_ssl == TRUE))
999 xasprintf (&buf, "%sHost: %s\r\n", buf, host_name);
1000 else
1001 xasprintf (&buf, "%sHost: %s:%d\r\n", buf, host_name, virtual_port);
1005 /* optionally send any other header tag */
1006 if (http_opt_headers_count) {
1007 for (i = 0; i < http_opt_headers_count ; i++) {
1008 if (force_host_header != http_opt_headers[i]) {
1009 xasprintf (&buf, "%s%s\r\n", buf, http_opt_headers[i]);
1012 /* This cannot be free'd here because a redirection will then try to access this and segfault */
1013 /* Covered in a testcase in tests/check_http.t */
1014 /* free(http_opt_headers); */
1017 /* optionally send the authentication info */
1018 if (strlen(user_auth)) {
1019 base64_encode_alloc (user_auth, strlen (user_auth), &auth);
1020 xasprintf (&buf, "%sAuthorization: Basic %s\r\n", buf, auth);
1023 /* optionally send the proxy authentication info */
1024 if (strlen(proxy_auth)) {
1025 base64_encode_alloc (proxy_auth, strlen (proxy_auth), &auth);
1026 xasprintf (&buf, "%sProxy-Authorization: Basic %s\r\n", buf, auth);
1029 /* either send http POST data (any data, not only POST)*/
1030 if (http_post_data) {
1031 if (http_content_type) {
1032 xasprintf (&buf, "%sContent-Type: %s\r\n", buf, http_content_type);
1033 } else {
1034 xasprintf (&buf, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf);
1037 xasprintf (&buf, "%sContent-Length: %i\r\n\r\n", buf, (int)strlen (http_post_data));
1038 xasprintf (&buf, "%s%s%s", buf, http_post_data, CRLF);
1040 else {
1041 /* or just a newline so the server knows we're done with the request */
1042 xasprintf (&buf, "%s%s", buf, CRLF);
1045 if (verbose) printf ("%s\n", buf);
1046 gettimeofday (&tv_temp, NULL);
1047 my_send (buf, strlen (buf));
1048 microsec_headers = deltime (tv_temp);
1049 elapsed_time_headers = (double)microsec_headers / 1.0e6;
1051 /* fetch the page */
1052 full_page = strdup("");
1053 gettimeofday (&tv_temp, NULL);
1054 while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) {
1055 if ((i >= 1) && (elapsed_time_firstbyte <= 0.000001)) {
1056 microsec_firstbyte = deltime (tv_temp);
1057 elapsed_time_firstbyte = (double)microsec_firstbyte / 1.0e6;
1059 while (pos = memchr(buffer, '\0', i)) {
1060 /* replace nul character with a blank */
1061 *pos = ' ';
1063 buffer[i] = '\0';
1064 xasprintf (&full_page_new, "%s%s", full_page, buffer);
1065 free (full_page);
1066 full_page = full_page_new;
1067 pagesize += i;
1069 if (no_body && document_headers_done (full_page)) {
1070 i = 0;
1071 break;
1074 microsec_transfer = deltime (tv_temp);
1075 elapsed_time_transfer = (double)microsec_transfer / 1.0e6;
1077 if (i < 0 && errno != ECONNRESET) {
1078 #ifdef HAVE_SSL
1080 if (use_ssl) {
1081 sslerr=SSL_get_error(ssl, i);
1082 if ( sslerr == SSL_ERROR_SSL ) {
1083 die (STATE_WARNING, _("HTTP WARNING - Client Certificate Required\n"));
1084 } else {
1085 die (STATE_CRITICAL, _("HTTP CRITICAL - Error on receive\n"));
1088 else {
1090 #endif
1091 die (STATE_CRITICAL, _("HTTP CRITICAL - Error on receive\n"));
1092 #ifdef HAVE_SSL
1093 /* XXX
1096 #endif
1099 /* return a CRITICAL status if we couldn't read any data */
1100 if (pagesize == (size_t) 0)
1101 die (STATE_CRITICAL, _("HTTP CRITICAL - No data received from host\n"));
1103 /* close the connection */
1104 if (sd) close(sd);
1105 #ifdef HAVE_SSL
1106 np_net_ssl_cleanup();
1107 #endif
1109 /* Save check time */
1110 microsec = deltime (tv);
1111 elapsed_time = (double)microsec / 1.0e6;
1113 /* leave full_page untouched so we can free it later */
1114 page = full_page;
1116 if (verbose)
1117 printf ("%s://%s:%d%s is %d characters\n",
1118 use_ssl ? "https" : "http", server_address,
1119 server_port, server_url, (int)pagesize);
1121 /* find status line and null-terminate it */
1122 status_line = page;
1123 page += (size_t) strcspn (page, "\r\n");
1124 pos = page;
1125 page += (size_t) strspn (page, "\r\n");
1126 status_line[strcspn(status_line, "\r\n")] = 0;
1127 strip (status_line);
1128 if (verbose)
1129 printf ("STATUS: %s\n", status_line);
1131 /* find header info and null-terminate it */
1132 header = page;
1133 while (strcspn (page, "\r\n") > 0) {
1134 page += (size_t) strcspn (page, "\r\n");
1135 pos = page;
1136 if ((strspn (page, "\r") == 1 && strspn (page, "\r\n") >= 2) ||
1137 (strspn (page, "\n") == 1 && strspn (page, "\r\n") >= 2))
1138 page += (size_t) 2;
1139 else
1140 page += (size_t) 1;
1142 page += (size_t) strspn (page, "\r\n");
1143 header[pos - header] = 0;
1144 if (verbose)
1145 printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header,
1146 (no_body ? " [[ skipped ]]" : page));
1148 /* make sure the status line matches the response we are looking for */
1149 if (!expected_statuscode (status_line, server_expect)) {
1150 if (server_port == HTTP_PORT)
1151 xasprintf (&msg,
1152 _("Invalid HTTP response received from host: %s\n"),
1153 status_line);
1154 else
1155 xasprintf (&msg,
1156 _("Invalid HTTP response received from host on port %d: %s\n"),
1157 server_port, status_line);
1158 if (show_body)
1159 xasprintf (&msg, _("%s\n%s"), msg, page);
1160 die (STATE_CRITICAL, "HTTP CRITICAL - %s", msg);
1163 /* Bypass normal status line check if server_expect was set by user and not default */
1164 /* NOTE: After this if/else block msg *MUST* be an asprintf-allocated string */
1165 if ( server_expect_yn ) {
1166 xasprintf (&msg,
1167 _("Status line output matched \"%s\" - "), server_expect);
1168 if (verbose)
1169 printf ("%s\n",msg);
1171 else {
1172 /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
1173 /* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
1174 /* Status-Code = 3 DIGITS */
1176 status_code = strchr (status_line, ' ') + sizeof (char);
1177 if (strspn (status_code, "1234567890") != 3)
1178 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line);
1180 http_status = atoi (status_code);
1182 /* check the return code */
1184 if (http_status >= 600 || http_status < 100) {
1185 die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line);
1187 /* server errors result in a critical state */
1188 else if (http_status >= 500) {
1189 xasprintf (&msg, _("%s - "), status_line);
1190 result = STATE_CRITICAL;
1192 /* client errors result in a warning state */
1193 else if (http_status >= 400) {
1194 xasprintf (&msg, _("%s - "), status_line);
1195 result = max_state_alt(STATE_WARNING, result);
1197 /* check redirected page if specified */
1198 else if (http_status >= 300) {
1200 if (onredirect == STATE_DEPENDENT)
1201 redir (header, status_line);
1202 else
1203 result = max_state_alt(onredirect, result);
1204 xasprintf (&msg, _("%s - "), status_line);
1205 } /* end if (http_status >= 300) */
1206 else {
1207 /* Print OK status anyway */
1208 xasprintf (&msg, _("%s - "), status_line);
1211 } /* end else (server_expect_yn) */
1213 /* reset the alarm - must be called *after* redir or we'll never die on redirects! */
1214 alarm (0);
1216 if (maximum_age >= 0) {
1217 result = max_state_alt(check_document_dates(header, &msg), result);
1220 /* Page and Header content checks go here */
1221 if (strlen (header_expect)) {
1222 if (!strstr (header, header_expect)) {
1223 strncpy(&output_header_search[0],header_expect,sizeof(output_header_search));
1224 if(output_header_search[sizeof(output_header_search)-1]!='\0') {
1225 bcopy("...",&output_header_search[sizeof(output_header_search)-4],4);
1227 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);
1228 result = STATE_CRITICAL;
1233 if (strlen (string_expect)) {
1234 if (!strstr (page, string_expect)) {
1235 strncpy(&output_string_search[0],string_expect,sizeof(output_string_search));
1236 if(output_string_search[sizeof(output_string_search)-1]!='\0') {
1237 bcopy("...",&output_string_search[sizeof(output_string_search)-4],4);
1239 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);
1240 result = STATE_CRITICAL;
1244 if (strlen (regexp)) {
1245 errcode = regexec (&preg, page, REGS, pmatch, 0);
1246 if ((errcode == 0 && invert_regex == 0) || (errcode == REG_NOMATCH && invert_regex == 1)) {
1247 /* OK - No-op to avoid changing the logic around it */
1248 result = max_state_alt(STATE_OK, result);
1250 else if ((errcode == REG_NOMATCH && invert_regex == 0) || (errcode == 0 && invert_regex == 1)) {
1251 if (invert_regex == 0)
1252 xasprintf (&msg, _("%spattern not found, "), msg);
1253 else
1254 xasprintf (&msg, _("%spattern found, "), msg);
1255 result = STATE_CRITICAL;
1257 else {
1258 /* FIXME: Shouldn't that be UNKNOWN? */
1259 regerror (errcode, &preg, errbuf, MAX_INPUT_BUFFER);
1260 xasprintf (&msg, _("%sExecute Error: %s, "), msg, errbuf);
1261 result = STATE_CRITICAL;
1265 /* make sure the page is of an appropriate size */
1266 /* page_len = get_content_length(header); */
1267 /* FIXME: Will this work with -N ? IMHO we should use
1268 * get_content_length(header) and always check if it's different than the
1269 * returned pagesize
1271 /* FIXME: IIRC pagesize returns headers - shouldn't we make
1272 * it == get_content_length(header) ??
1274 page_len = pagesize;
1275 if ((max_page_len > 0) && (page_len > max_page_len)) {
1276 xasprintf (&msg, _("%spage size %d too large, "), msg, page_len);
1277 result = max_state_alt(STATE_WARNING, result);
1278 } else if ((min_page_len > 0) && (page_len < min_page_len)) {
1279 xasprintf (&msg, _("%spage size %d too small, "), msg, page_len);
1280 result = max_state_alt(STATE_WARNING, result);
1283 /* Cut-off trailing characters */
1284 if(msg[strlen(msg)-2] == ',')
1285 msg[strlen(msg)-2] = '\0';
1286 else
1287 msg[strlen(msg)-3] = '\0';
1289 /* check elapsed time */
1290 if (show_extended_perfdata)
1291 xasprintf (&msg,
1292 _("%s - %d bytes in %.3f second response time %s|%s %s %s %s %s %s %s"),
1293 msg, page_len, elapsed_time,
1294 (display_html ? "</A>" : ""),
1295 perfd_time (elapsed_time),
1296 perfd_size (page_len),
1297 perfd_time_connect (elapsed_time_connect),
1298 use_ssl == TRUE ? perfd_time_ssl (elapsed_time_ssl) : "",
1299 perfd_time_headers (elapsed_time_headers),
1300 perfd_time_firstbyte (elapsed_time_firstbyte),
1301 perfd_time_transfer (elapsed_time_transfer));
1302 else
1303 xasprintf (&msg,
1304 _("%s - %d bytes in %.3f second response time %s|%s %s"),
1305 msg, page_len, elapsed_time,
1306 (display_html ? "</A>" : ""),
1307 perfd_time (elapsed_time),
1308 perfd_size (page_len));
1310 if (show_body)
1311 xasprintf (&msg, _("%s\n%s"), msg, page);
1313 result = max_state_alt(get_status(elapsed_time, thlds), result);
1315 die (result, "HTTP %s: %s\n", state_text(result), msg);
1316 /* die failed? */
1317 return STATE_UNKNOWN;
1322 /* per RFC 2396 */
1323 #define URI_HTTP "%5[HTPShtps]"
1324 #define URI_HOST "%255[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1325 #define URI_PORT "%6d" /* MAX_PORT's width is 5 chars, 6 to detect overflow */
1326 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1327 #define HD1 URI_HTTP "://" URI_HOST ":" URI_PORT "/" URI_PATH
1328 #define HD2 URI_HTTP "://" URI_HOST "/" URI_PATH
1329 #define HD3 URI_HTTP "://" URI_HOST ":" URI_PORT
1330 #define HD4 URI_HTTP "://" URI_HOST
1331 #define HD5 URI_PATH
1333 void
1334 redir (char *pos, char *status_line)
1336 int i = 0;
1337 char *x;
1338 char xx[2];
1339 char type[6];
1340 char *addr;
1341 char *url;
1343 addr = malloc (MAX_IPV4_HOSTLENGTH + 1);
1344 if (addr == NULL)
1345 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate addr\n"));
1347 memset(addr, 0, MAX_IPV4_HOSTLENGTH);
1348 url = malloc (strcspn (pos, "\r\n"));
1349 if (url == NULL)
1350 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n"));
1352 while (pos) {
1353 sscanf (pos, "%1[Ll]%*1[Oo]%*1[Cc]%*1[Aa]%*1[Tt]%*1[Ii]%*1[Oo]%*1[Nn]:%n", xx, &i);
1354 if (i == 0) {
1355 pos += (size_t) strcspn (pos, "\r\n");
1356 pos += (size_t) strspn (pos, "\r\n");
1357 if (strlen(pos) == 0)
1358 die (STATE_UNKNOWN,
1359 _("HTTP UNKNOWN - Could not find redirect location - %s%s\n"),
1360 status_line, (display_html ? "</A>" : ""));
1361 continue;
1364 pos += i;
1365 pos += strspn (pos, " \t");
1368 * RFC 2616 (4.2): ``Header fields can be extended over multiple lines by
1369 * preceding each extra line with at least one SP or HT.''
1371 for (; (i = strspn (pos, "\r\n")); pos += i) {
1372 pos += i;
1373 if (!(i = strspn (pos, " \t"))) {
1374 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Empty redirect location%s\n"),
1375 display_html ? "</A>" : "");
1379 url = realloc (url, strcspn (pos, "\r\n") + 1);
1380 if (url == NULL)
1381 die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n"));
1383 /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
1384 if (sscanf (pos, HD1, type, addr, &i, url) == 4) {
1385 url = prepend_slash (url);
1386 use_ssl = server_type_check (type);
1389 /* URI_HTTP URI_HOST URI_PATH */
1390 else if (sscanf (pos, HD2, type, addr, url) == 3 ) {
1391 url = prepend_slash (url);
1392 use_ssl = server_type_check (type);
1393 i = server_port_check (use_ssl);
1396 /* URI_HTTP URI_HOST URI_PORT */
1397 else if (sscanf (pos, HD3, type, addr, &i) == 3) {
1398 strcpy (url, HTTP_URL);
1399 use_ssl = server_type_check (type);
1402 /* URI_HTTP URI_HOST */
1403 else if (sscanf (pos, HD4, type, addr) == 2) {
1404 strcpy (url, HTTP_URL);
1405 use_ssl = server_type_check (type);
1406 i = server_port_check (use_ssl);
1409 /* URI_PATH */
1410 else if (sscanf (pos, HD5, url) == 1) {
1411 /* relative url */
1412 if ((url[0] != '/')) {
1413 if ((x = strrchr(server_url, '/')))
1414 *x = '\0';
1415 xasprintf (&url, "%s/%s", server_url, url);
1417 i = server_port;
1418 strcpy (type, server_type);
1419 strcpy (addr, host_name ? host_name : server_address);
1422 else {
1423 die (STATE_UNKNOWN,
1424 _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"),
1425 pos, (display_html ? "</A>" : ""));
1428 break;
1430 } /* end while (pos) */
1432 if (++redir_depth > max_depth)
1433 die (STATE_WARNING,
1434 _("HTTP WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
1435 max_depth, type, addr, i, url, (display_html ? "</A>" : ""));
1437 if (server_port==i &&
1438 !strncmp(server_address, addr, MAX_IPV4_HOSTLENGTH) &&
1439 (host_name && !strncmp(host_name, addr, MAX_IPV4_HOSTLENGTH)) &&
1440 !strcmp(server_url, url))
1441 die (STATE_WARNING,
1442 _("HTTP WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
1443 type, addr, i, url, (display_html ? "</A>" : ""));
1445 strcpy (server_type, type);
1447 free (host_name);
1448 host_name = strndup (addr, MAX_IPV4_HOSTLENGTH);
1450 if (!(followsticky & STICKY_HOST)) {
1451 free (server_address);
1452 server_address = strndup (addr, MAX_IPV4_HOSTLENGTH);
1454 if (!(followsticky & STICKY_PORT)) {
1455 server_port = i;
1458 free (server_url);
1459 server_url = url;
1461 if (server_port > MAX_PORT)
1462 die (STATE_UNKNOWN,
1463 _("HTTP UNKNOWN - Redirection to port above %d - %s://%s:%d%s%s\n"),
1464 MAX_PORT, server_type, server_address, server_port, server_url,
1465 display_html ? "</A>" : "");
1467 /* reset virtual port */
1468 virtual_port = server_port;
1470 if (verbose)
1471 printf (_("Redirection to %s://%s:%d%s\n"), server_type,
1472 host_name ? host_name : server_address, server_port, server_url);
1474 free(addr);
1475 check_http ();
1480 server_type_check (const char *type)
1482 if (strcmp (type, "https"))
1483 return FALSE;
1484 else
1485 return TRUE;
1489 server_port_check (int ssl_flag)
1491 if (ssl_flag)
1492 return HTTPS_PORT;
1493 else
1494 return HTTP_PORT;
1497 char *perfd_time (double elapsed_time)
1499 return fperfdata ("time", elapsed_time, "s",
1500 thlds->warning?TRUE:FALSE, thlds->warning?thlds->warning->end:0,
1501 thlds->critical?TRUE:FALSE, thlds->critical?thlds->critical->end:0,
1502 TRUE, 0, TRUE, socket_timeout);
1505 char *perfd_time_connect (double elapsed_time_connect)
1507 return fperfdata ("time_connect", elapsed_time_connect, "s", FALSE, 0, FALSE, 0, FALSE, 0, TRUE, socket_timeout);
1510 char *perfd_time_ssl (double elapsed_time_ssl)
1512 return fperfdata ("time_ssl", elapsed_time_ssl, "s", FALSE, 0, FALSE, 0, FALSE, 0, TRUE, socket_timeout);
1515 char *perfd_time_headers (double elapsed_time_headers)
1517 return fperfdata ("time_headers", elapsed_time_headers, "s", FALSE, 0, FALSE, 0, FALSE, 0, TRUE, socket_timeout);
1520 char *perfd_time_firstbyte (double elapsed_time_firstbyte)
1522 return fperfdata ("time_firstbyte", elapsed_time_firstbyte, "s", FALSE, 0, FALSE, 0, FALSE, 0, TRUE, socket_timeout);
1525 char *perfd_time_transfer (double elapsed_time_transfer)
1527 return fperfdata ("time_transfer", elapsed_time_transfer, "s", FALSE, 0, FALSE, 0, FALSE, 0, TRUE, socket_timeout);
1530 char *perfd_size (int page_len)
1532 return perfdata ("size", page_len, "B",
1533 (min_page_len>0?TRUE:FALSE), min_page_len,
1534 (min_page_len>0?TRUE:FALSE), 0,
1535 TRUE, 0, FALSE, 0);
1538 void
1539 print_help (void)
1541 print_revision (progname, NP_VERSION);
1543 printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
1544 printf (COPYRIGHT, copyright, email);
1546 printf ("%s\n", _("This plugin tests the HTTP service on the specified host. It can test"));
1547 printf ("%s\n", _("normal (http) and secure (https) servers, follow redirects, search for"));
1548 printf ("%s\n", _("strings and regular expressions, check connection times, and report on"));
1549 printf ("%s\n", _("certificate expiration times."));
1551 printf ("\n\n");
1553 print_usage ();
1555 printf (_("NOTE: One or both of -H and -I must be specified"));
1557 printf ("\n");
1559 printf (UT_HELP_VRSN);
1560 printf (UT_EXTRA_OPTS);
1562 printf (" %s\n", "-H, --hostname=ADDRESS");
1563 printf (" %s\n", _("Host name argument for servers using host headers (virtual host)"));
1564 printf (" %s\n", _("Append a port to include it in the header (eg: example.com:5000)"));
1565 printf (" %s\n", "-I, --IP-address=ADDRESS");
1566 printf (" %s\n", _("IP address or name (use numeric address if possible to bypass DNS lookup)."));
1567 printf (" %s\n", "-p, --port=INTEGER");
1568 printf (" %s", _("Port number (default: "));
1569 printf ("%d)\n", HTTP_PORT);
1571 printf (UT_IPv46);
1573 #ifdef HAVE_SSL
1574 printf (" %s\n", "-S, --ssl=VERSION[+]");
1575 printf (" %s\n", _("Connect via SSL. Port defaults to 443. VERSION is optional, and prevents"));
1576 printf (" %s\n", _("auto-negotiation (2 = SSLv2, 3 = SSLv3, 1 = TLSv1, 1.1 = TLSv1.1,"));
1577 printf (" %s\n", _("1.2 = TLSv1.2). With a '+' suffix, newer versions are also accepted."));
1578 printf (" %s\n", "--sni");
1579 printf (" %s\n", _("Enable SSL/TLS hostname extension support (SNI)"));
1580 printf (" %s\n", "-C, --certificate=INTEGER[,INTEGER]");
1581 printf (" %s\n", _("Minimum number of days a certificate has to be valid. Port defaults to 443"));
1582 printf (" %s\n", _("(when this option is used the URL is not checked.)"));
1583 printf (" %s\n", "-J, --client-cert=FILE");
1584 printf (" %s\n", _("Name of file that contains the client certificate (PEM format)"));
1585 printf (" %s\n", _("to be used in establishing the SSL session"));
1586 printf (" %s\n", "-K, --private-key=FILE");
1587 printf (" %s\n", _("Name of file containing the private key (PEM format)"));
1588 printf (" %s\n", _("matching the client certificate"));
1589 #endif
1591 printf (" %s\n", "-e, --expect=STRING");
1592 printf (" %s\n", _("Comma-delimited list of strings, at least one of them is expected in"));
1593 printf (" %s", _("the first (status) line of the server response (default: "));
1594 printf ("%s)\n", HTTP_EXPECT);
1595 printf (" %s\n", _("If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)"));
1596 printf (" %s\n", "-d, --header-string=STRING");
1597 printf (" %s\n", _("String to expect in the response headers"));
1598 printf (" %s\n", "-s, --string=STRING");
1599 printf (" %s\n", _("String to expect in the content"));
1600 printf (" %s\n", "-u, --url=PATH");
1601 printf (" %s\n", _("URL to GET or POST (default: /)"));
1602 printf (" %s\n", "-P, --post=STRING");
1603 printf (" %s\n", _("URL encoded http POST data"));
1604 printf (" %s\n", "-j, --method=STRING (for example: HEAD, OPTIONS, TRACE, PUT, DELETE, CONNECT, CONNECT:POST)");
1605 printf (" %s\n", _("Set HTTP method."));
1606 printf (" %s\n", "-N, --no-body");
1607 printf (" %s\n", _("Don't wait for document body: stop reading after headers."));
1608 printf (" %s\n", _("(Note that this still does an HTTP GET or POST, not a HEAD.)"));
1609 printf (" %s\n", "-M, --max-age=SECONDS");
1610 printf (" %s\n", _("Warn if document is more than SECONDS old. the number can also be of"));
1611 printf (" %s\n", _("the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days."));
1612 printf (" %s\n", "-T, --content-type=STRING");
1613 printf (" %s\n", _("specify Content-Type header media type when POSTing\n"));
1615 printf (" %s\n", "-l, --linespan");
1616 printf (" %s\n", _("Allow regex to span newlines (must precede -r or -R)"));
1617 printf (" %s\n", "-r, --regex, --ereg=STRING");
1618 printf (" %s\n", _("Search page for regex STRING"));
1619 printf (" %s\n", "-R, --eregi=STRING");
1620 printf (" %s\n", _("Search page for case-insensitive regex STRING"));
1621 printf (" %s\n", "--invert-regex");
1622 printf (" %s\n", _("Return CRITICAL if found, OK if not\n"));
1624 printf (" %s\n", "-a, --authorization=AUTH_PAIR");
1625 printf (" %s\n", _("Username:password on sites with basic authentication"));
1626 printf (" %s\n", "-b, --proxy-authorization=AUTH_PAIR");
1627 printf (" %s\n", _("Username:password on proxy-servers with basic authentication"));
1628 printf (" %s\n", "-A, --useragent=STRING");
1629 printf (" %s\n", _("String to be sent in http header as \"User Agent\""));
1630 printf (" %s\n", "-k, --header=STRING");
1631 printf (" %s\n", _("Any other tags to be sent in http header. Use multiple times for additional headers"));
1632 printf (" %s\n", "-E, --extended-perfdata");
1633 printf (" %s\n", _("Print additional performance data"));
1634 printf (" %s\n", "-B, --show-body");
1635 printf (" %s\n", _("Print body content below status line"));
1636 printf (" %s\n", "-L, --link");
1637 printf (" %s\n", _("Wrap output in HTML link (obsoleted by urlize)"));
1638 printf (" %s\n", "-f, --onredirect=<ok|warning|critical|follow|sticky|stickyport>");
1639 printf (" %s\n", _("How to handle redirected pages. sticky is like follow but stick to the"));
1640 printf (" %s\n", _("specified IP address. stickyport also ensures port stays the same."));
1641 printf (" %s\n", "-m, --pagesize=INTEGER<:INTEGER>");
1642 printf (" %s\n", _("Minimum page size required (bytes) : Maximum page size required (bytes)"));
1644 printf (UT_WARN_CRIT);
1646 printf (UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
1648 printf (UT_VERBOSE);
1650 printf ("\n");
1651 printf ("%s\n", _("Notes:"));
1652 printf (" %s\n", _("This plugin will attempt to open an HTTP connection with the host."));
1653 printf (" %s\n", _("Successful connects return STATE_OK, refusals and timeouts return STATE_CRITICAL"));
1654 printf (" %s\n", _("other errors return STATE_UNKNOWN. Successful connects, but incorrect response"));
1655 printf (" %s\n", _("messages from the host result in STATE_WARNING return values. If you are"));
1656 printf (" %s\n", _("checking a virtual server that uses 'host headers' you must supply the FQDN"));
1657 printf (" %s\n", _("(fully qualified domain name) as the [host_name] argument."));
1659 #ifdef HAVE_SSL
1660 printf ("\n");
1661 printf (" %s\n", _("This plugin can also check whether an SSL enabled web server is able to"));
1662 printf (" %s\n", _("serve content (optionally within a specified time) or whether the X509 "));
1663 printf (" %s\n", _("certificate is still valid for the specified number of days."));
1664 printf ("\n");
1665 printf (" %s\n", _("Please note that this plugin does not check if the presented server"));
1666 printf (" %s\n", _("certificate matches the hostname of the server, or if the certificate"));
1667 printf (" %s\n", _("has a valid chain of trust to one of the locally installed CAs."));
1668 printf ("\n");
1669 printf ("%s\n", _("Examples:"));
1670 printf (" %s\n\n", "CHECK CONTENT: check_http -w 5 -c 10 --ssl -H www.verisign.com");
1671 printf (" %s\n", _("When the 'www.verisign.com' server returns its content within 5 seconds,"));
1672 printf (" %s\n", _("a STATE_OK will be returned. When the server returns its content but exceeds"));
1673 printf (" %s\n", _("the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,"));
1674 printf (" %s\n", _("a STATE_CRITICAL will be returned."));
1675 printf ("\n");
1676 printf (" %s\n\n", "CHECK CERTIFICATE: check_http -H www.verisign.com -C 14");
1677 printf (" %s\n", _("When the certificate of 'www.verisign.com' is valid for more than 14 days,"));
1678 printf (" %s\n", _("a STATE_OK is returned. When the certificate is still valid, but for less than"));
1679 printf (" %s\n", _("14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when"));
1680 printf (" %s\n\n", _("the certificate is expired."));
1681 printf ("\n");
1682 printf (" %s\n\n", "CHECK CERTIFICATE: check_http -H www.verisign.com -C 30,14");
1683 printf (" %s\n", _("When the certificate of 'www.verisign.com' is valid for more than 30 days,"));
1684 printf (" %s\n", _("a STATE_OK is returned. When the certificate is still valid, but for less than"));
1685 printf (" %s\n", _("30 days, but more than 14 days, a STATE_WARNING is returned."));
1686 printf (" %s\n", _("A STATE_CRITICAL will be returned when certificate expires in less than 14 days"));
1688 printf (" %s\n\n", "CHECK SSL WEBSERVER CONTENT VIA PROXY USING HTTP 1.1 CONNECT: ");
1689 printf (" %s\n", _("check_http -I 192.168.100.35 -p 80 -u https://www.verisign.com/ -S -j CONNECT -H www.verisign.com "));
1690 printf (" %s\n", _("all these options are needed: -I <proxy> -p <proxy-port> -u <check-url> -S(sl) -j CONNECT -H <webserver>"));
1691 printf (" %s\n", _("a STATE_OK will be returned. When the server returns its content but exceeds"));
1692 printf (" %s\n", _("the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,"));
1693 printf (" %s\n", _("a STATE_CRITICAL will be returned. By adding a colon to the method you can set the method used"));
1694 printf (" %s\n", _("inside the proxied connection: -j CONNECT:POST"));
1696 #endif
1698 printf (UT_SUPPORT);
1704 void
1705 print_usage (void)
1707 printf ("%s\n", _("Usage:"));
1708 printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname);
1709 printf (" [-J <client certificate file>] [-K <private key>]\n");
1710 printf (" [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L] [-E] [-a auth]\n");
1711 printf (" [-b proxy_auth] [-f <ok|warning|critcal|follow|sticky|stickyport>]\n");
1712 printf (" [-e <expect>] [-d string] [-s string] [-l] [-r <regex> | -R <case-insensitive regex>]\n");
1713 printf (" [-P string] [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>]\n");
1714 printf (" [-A string] [-k string] [-S <version>] [--sni] [-C <warn_age>[,<crit_age>]]\n");
1715 printf (" [-T <content-type>] [-j method]\n");