1 /*****************************************************************************
3 * Nagios check_http plugin
6 * Copyright (c) 1999-2008 Nagios Plugins Development Team
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-2008";
38 const char *email
= "nagiosplug-devel@lists.sourceforge.net";
46 #define INPUT_DELIMITER ";"
48 #define HTTP_EXPECT "HTTP/1."
50 MAX_IPV4_HOSTLENGTH
= 255,
57 int check_cert
= FALSE
;
61 # define my_recv(buf, len) ((use_ssl) ? np_net_ssl_read(buf, len) : read(sd, buf, len))
62 # define my_send(buf, len) ((use_ssl) ? np_net_ssl_write(buf, len) : send(sd, buf, len, 0))
63 #else /* ifndef HAVE_SSL */
64 # define my_recv(buf, len) read(sd, buf, len)
65 # define my_send(buf, len) send(sd, buf, len, 0)
76 regmatch_t pmatch
[REGS
];
77 char regexp
[MAX_RE_SIZE
];
78 char errbuf
[MAX_INPUT_BUFFER
];
79 int cflags
= REG_NOSUB
| REG_EXTENDED
| REG_NEWLINE
;
88 int specify_port
= FALSE
;
89 int server_port
= HTTP_PORT
;
90 char server_port_text
[6] = "";
91 char server_type
[6] = "http";
96 int server_url_length
;
97 int server_expect_yn
= 0;
98 char server_expect
[MAX_INPUT_BUFFER
] = HTTP_EXPECT
;
99 char string_expect
[MAX_INPUT_BUFFER
] = "";
100 double warning_time
= 0;
101 int check_warning_time
= FALSE
;
102 double critical_time
= 0;
103 int check_critical_time
= FALSE
;
104 char user_auth
[MAX_INPUT_BUFFER
] = "";
105 int display_html
= FALSE
;
106 char **http_opt_headers
;
107 int http_opt_headers_count
= 0;
108 int onredirect
= STATE_OK
;
109 int followsticky
= 0;
113 int min_page_len
= 0;
114 int max_page_len
= 0;
118 char *http_post_data
;
119 char *http_content_type
;
120 char buffer
[MAX_INPUT_BUFFER
];
122 int process_arguments (int, char **);
123 int check_http (void);
124 void redir (char *pos
, char *status_line
);
125 int server_type_check(const char *type
);
126 int server_port_check(int ssl_flag
);
127 char *perfd_time (double microsec
);
128 char *perfd_size (int page_len
);
129 void print_help (void);
130 void print_usage (void);
133 main (int argc
, char **argv
)
135 int result
= STATE_UNKNOWN
;
137 setlocale (LC_ALL
, "");
138 bindtextdomain (PACKAGE
, LOCALEDIR
);
139 textdomain (PACKAGE
);
141 /* Set default URL. Must be malloced for subsequent realloc if --onredirect=follow */
142 server_url
= strdup(HTTP_URL
);
143 server_url_length
= strlen(server_url
);
144 asprintf (&user_agent
, "User-Agent: check_http/v%s (nagios-plugins %s)",
145 NP_VERSION
, VERSION
);
147 /* Parse extra opts if any */
148 argv
=np_extra_opts (&argc
, argv
, progname
);
150 if (process_arguments (argc
, argv
) == ERROR
)
151 usage4 (_("Could not parse arguments"));
153 if (display_html
== TRUE
)
154 printf ("<A HREF=\"%s://%s:%d%s\" target=\"_blank\">",
155 use_ssl
? "https" : "http", host_name
? host_name
: server_address
,
156 server_port
, server_url
);
158 /* initialize alarm signal handling, set socket timeout, start timer */
159 (void) signal (SIGALRM
, socket_timeout_alarm_handler
);
160 (void) alarm (socket_timeout
);
161 gettimeofday (&tv
, NULL
);
163 result
= check_http ();
169 /* process command-line arguments */
171 process_arguments (int argc
, char **argv
)
177 INVERT_REGEX
= CHAR_MAX
+ 1
181 static struct option longopts
[] = {
183 {"link", no_argument
, 0, 'L'},
184 {"nohtml", no_argument
, 0, 'n'},
185 {"ssl", no_argument
, 0, 'S'},
186 {"post", required_argument
, 0, 'P'},
187 {"method", required_argument
, 0, 'j'},
188 {"IP-address", required_argument
, 0, 'I'},
189 {"url", required_argument
, 0, 'u'},
190 {"port", required_argument
, 0, 'p'},
191 {"authorization", required_argument
, 0, 'a'},
192 {"string", required_argument
, 0, 's'},
193 {"expect", required_argument
, 0, 'e'},
194 {"regex", required_argument
, 0, 'r'},
195 {"ereg", required_argument
, 0, 'r'},
196 {"eregi", required_argument
, 0, 'R'},
197 {"linespan", no_argument
, 0, 'l'},
198 {"onredirect", required_argument
, 0, 'f'},
199 {"certificate", required_argument
, 0, 'C'},
200 {"useragent", required_argument
, 0, 'A'},
201 {"header", required_argument
, 0, 'k'},
202 {"no-body", no_argument
, 0, 'N'},
203 {"max-age", required_argument
, 0, 'M'},
204 {"content-type", required_argument
, 0, 'T'},
205 {"pagesize", required_argument
, 0, 'm'},
206 {"invert-regex", no_argument
, NULL
, INVERT_REGEX
},
207 {"use-ipv4", no_argument
, 0, '4'},
208 {"use-ipv6", no_argument
, 0, '6'},
215 for (c
= 1; c
< argc
; c
++) {
216 if (strcmp ("-to", argv
[c
]) == 0)
217 strcpy (argv
[c
], "-t");
218 if (strcmp ("-hn", argv
[c
]) == 0)
219 strcpy (argv
[c
], "-H");
220 if (strcmp ("-wt", argv
[c
]) == 0)
221 strcpy (argv
[c
], "-w");
222 if (strcmp ("-ct", argv
[c
]) == 0)
223 strcpy (argv
[c
], "-c");
224 if (strcmp ("-nohtml", argv
[c
]) == 0)
225 strcpy (argv
[c
], "-n");
229 c
= getopt_long (argc
, argv
, "Vvh46t:c:w:A:k:H:P:j:T:I:a:e:p:s:R:r:u:f:C:nlLSm:M:N", longopts
, &option
);
230 if (c
== -1 || c
== EOF
)
234 case '?': /* usage */
241 case 'V': /* version */
242 print_revision (progname
, NP_VERSION
);
245 case 't': /* timeout period */
246 if (!is_intnonneg (optarg
))
247 usage2 (_("Timeout interval must be a positive integer"), optarg
);
249 socket_timeout
= atoi (optarg
);
251 case 'c': /* critical time threshold */
252 if (!is_nonnegative (optarg
))
253 usage2 (_("Critical threshold must be integer"), optarg
);
255 critical_time
= strtod (optarg
, NULL
);
256 check_critical_time
= TRUE
;
259 case 'w': /* warning time threshold */
260 if (!is_nonnegative (optarg
))
261 usage2 (_("Warning threshold must be integer"), optarg
);
263 warning_time
= strtod (optarg
, NULL
);
264 check_warning_time
= TRUE
;
267 case 'A': /* User Agent String */
268 asprintf (&user_agent
, "User-Agent: %s", optarg
);
270 case 'k': /* Additional headers */
271 if (http_opt_headers_count
== 0)
272 http_opt_headers
= malloc (sizeof (char *) * (++http_opt_headers_count
));
274 http_opt_headers
= realloc (http_opt_headers
, sizeof (char *) * (++http_opt_headers_count
));
275 http_opt_headers
[http_opt_headers_count
- 1] = optarg
;
276 /* asprintf (&http_opt_headers, "%s", optarg); */
278 case 'L': /* show html link */
281 case 'n': /* do not show html link */
282 display_html
= FALSE
;
284 case 'C': /* Check SSL cert validity */
286 if (!is_intnonneg (optarg
))
287 usage2 (_("Invalid certificate expiration period"), optarg
);
289 days_till_exp
= atoi (optarg
);
292 /* Fall through to -S option */
294 case 'S': /* use SSL */
296 usage4 (_("Invalid option - SSL is not available"));
299 if (specify_port
== FALSE
)
300 server_port
= HTTPS_PORT
;
302 case 'f': /* onredirect */
303 if (!strcmp (optarg
, "sticky"))
304 onredirect
= STATE_DEPENDENT
, followsticky
= 1;
305 if (!strcmp (optarg
, "follow"))
306 onredirect
= STATE_DEPENDENT
, followsticky
= 0;
307 if (!strcmp (optarg
, "unknown"))
308 onredirect
= STATE_UNKNOWN
;
309 if (!strcmp (optarg
, "ok"))
310 onredirect
= STATE_OK
;
311 if (!strcmp (optarg
, "warning"))
312 onredirect
= STATE_WARNING
;
313 if (!strcmp (optarg
, "critical"))
314 onredirect
= STATE_CRITICAL
;
316 printf(_("option f:%d \n"), onredirect
);
318 /* Note: H, I, and u must be malloc'd or will fail on redirects */
319 case 'H': /* Host Name (virtual host) */
320 host_name
= strdup (optarg
);
321 if (host_name
[0] == '[') {
322 if ((p
= strstr (host_name
, "]:")) != NULL
) /* [IPv6]:port */
323 server_port
= atoi (p
+ 2);
324 } else if ((p
= strchr (host_name
, ':')) != NULL
325 && strchr (++p
, ':') == NULL
) /* IPv4:port or host:port */
326 server_port
= atoi (p
);
328 case 'I': /* Server IP-address */
329 server_address
= strdup (optarg
);
331 case 'u': /* URL path */
332 server_url
= strdup (optarg
);
333 server_url_length
= strlen (server_url
);
335 case 'p': /* Server port */
336 if (!is_intnonneg (optarg
))
337 usage2 (_("Invalid port number"), optarg
);
339 server_port
= atoi (optarg
);
343 case 'a': /* authorization info */
344 strncpy (user_auth
, optarg
, MAX_INPUT_BUFFER
- 1);
345 user_auth
[MAX_INPUT_BUFFER
- 1] = 0;
347 case 'P': /* HTTP POST data in URL encoded format; ignored if settings already */
348 if (! http_post_data
)
349 http_post_data
= strdup (optarg
);
351 http_method
= strdup("POST");
353 case 'j': /* Set HTTP method */
356 http_method
= strdup (optarg
);
358 case 's': /* string or substring */
359 strncpy (string_expect
, optarg
, MAX_INPUT_BUFFER
- 1);
360 string_expect
[MAX_INPUT_BUFFER
- 1] = 0;
362 case 'e': /* string or substring */
363 strncpy (server_expect
, optarg
, MAX_INPUT_BUFFER
- 1);
364 server_expect
[MAX_INPUT_BUFFER
- 1] = 0;
365 server_expect_yn
= 1;
367 case 'T': /* Content-type */
368 asprintf (&http_content_type
, "%s", optarg
);
370 case 'l': /* linespan */
371 cflags
&= ~REG_NEWLINE
;
373 case 'R': /* regex */
375 case 'r': /* regex */
376 strncpy (regexp
, optarg
, MAX_RE_SIZE
- 1);
377 regexp
[MAX_RE_SIZE
- 1] = 0;
378 errcode
= regcomp (&preg
, regexp
, cflags
);
380 (void) regerror (errcode
, &preg
, errbuf
, MAX_INPUT_BUFFER
);
381 printf (_("Could Not Compile Regular Expression: %s"), errbuf
);
389 address_family
= AF_INET
;
393 address_family
= AF_INET6
;
395 usage4 (_("IPv6 support not available"));
398 case 'v': /* verbose */
401 case 'm': /* min_page_length */
404 if (strchr(optarg
, ':') != (char *)NULL
) {
405 /* range, so get two values, min:max */
406 tmp
= strtok(optarg
, ":");
408 printf("Bad format: try \"-m min:max\"\n");
409 exit (STATE_WARNING
);
411 min_page_len
= atoi(tmp
);
413 tmp
= strtok(NULL
, ":");
415 printf("Bad format: try \"-m min:max\"\n");
416 exit (STATE_WARNING
);
418 max_page_len
= atoi(tmp
);
420 min_page_len
= atoi (optarg
);
423 case 'N': /* no-body */
426 case 'M': /* max-age */
428 int L
= strlen(optarg
);
429 if (L
&& optarg
[L
-1] == 'm')
430 maximum_age
= atoi (optarg
) * 60;
431 else if (L
&& optarg
[L
-1] == 'h')
432 maximum_age
= atoi (optarg
) * 60 * 60;
433 else if (L
&& optarg
[L
-1] == 'd')
434 maximum_age
= atoi (optarg
) * 60 * 60 * 24;
435 else if (L
&& (optarg
[L
-1] == 's' ||
436 isdigit (optarg
[L
-1])))
437 maximum_age
= atoi (optarg
);
439 fprintf (stderr
, "unparsable max-age: %s\n", optarg
);
440 exit (STATE_WARNING
);
449 if (server_address
== NULL
&& c
< argc
)
450 server_address
= strdup (argv
[c
++]);
452 if (host_name
== NULL
&& c
< argc
)
453 host_name
= strdup (argv
[c
++]);
455 if (server_address
== NULL
) {
456 if (host_name
== NULL
)
457 usage4 (_("You must specify a server address or host name"));
459 server_address
= strdup (host_name
);
462 if (check_critical_time
&& critical_time
>(double)socket_timeout
)
463 socket_timeout
= (int)critical_time
+ 1;
465 if (http_method
== NULL
)
466 http_method
= strdup ("GET");
473 /* Returns 1 if we're done processing the document body; 0 to keep going */
475 document_headers_done (char *full_page
)
479 for (body
= full_page
; *body
; body
++) {
480 if (!strncmp (body
, "\n\n", 2) || !strncmp (body
, "\n\r\n", 3))
485 return 0; /* haven't read end of headers yet */
487 full_page
[body
- full_page
] = 0;
492 parse_time_string (const char *string
)
496 memset (&tm
, 0, sizeof(tm
));
498 /* Like this: Tue, 25 Dec 2001 02:59:03 GMT */
500 if (isupper (string
[0]) && /* Tue */
501 islower (string
[1]) &&
502 islower (string
[2]) &&
505 (isdigit(string
[5]) || string
[5] == ' ') && /* 25 */
506 isdigit (string
[6]) &&
508 isupper (string
[8]) && /* Dec */
509 islower (string
[9]) &&
510 islower (string
[10]) &&
512 isdigit (string
[12]) && /* 2001 */
513 isdigit (string
[13]) &&
514 isdigit (string
[14]) &&
515 isdigit (string
[15]) &&
517 isdigit (string
[17]) && /* 02: */
518 isdigit (string
[18]) &&
520 isdigit (string
[20]) && /* 59: */
521 isdigit (string
[21]) &&
523 isdigit (string
[23]) && /* 03 */
524 isdigit (string
[24]) &&
526 'G' == string
[26] && /* GMT */
527 'M' == string
[27] && /* GMT */
530 tm
.tm_sec
= 10 * (string
[23]-'0') + (string
[24]-'0');
531 tm
.tm_min
= 10 * (string
[20]-'0') + (string
[21]-'0');
532 tm
.tm_hour
= 10 * (string
[17]-'0') + (string
[18]-'0');
533 tm
.tm_mday
= 10 * (string
[5] == ' ' ? 0 : string
[5]-'0') + (string
[6]-'0');
534 tm
.tm_mon
= (!strncmp (string
+8, "Jan", 3) ? 0 :
535 !strncmp (string
+8, "Feb", 3) ? 1 :
536 !strncmp (string
+8, "Mar", 3) ? 2 :
537 !strncmp (string
+8, "Apr", 3) ? 3 :
538 !strncmp (string
+8, "May", 3) ? 4 :
539 !strncmp (string
+8, "Jun", 3) ? 5 :
540 !strncmp (string
+8, "Jul", 3) ? 6 :
541 !strncmp (string
+8, "Aug", 3) ? 7 :
542 !strncmp (string
+8, "Sep", 3) ? 8 :
543 !strncmp (string
+8, "Oct", 3) ? 9 :
544 !strncmp (string
+8, "Nov", 3) ? 10 :
545 !strncmp (string
+8, "Dec", 3) ? 11 :
547 tm
.tm_year
= ((1000 * (string
[12]-'0') +
548 100 * (string
[13]-'0') +
549 10 * (string
[14]-'0') +
553 tm
.tm_isdst
= 0; /* GMT is never in DST, right? */
555 if (tm
.tm_mon
< 0 || tm
.tm_mday
< 1 || tm
.tm_mday
> 31)
559 This is actually wrong: we need to subtract the local timezone
560 offset from GMT from this value. But, that's ok in this usage,
561 because we only comparing these two GMT dates against each other,
562 so it doesn't matter what time zone we parse them in.
566 if (t
== (time_t) -1) t
= 0;
569 const char *s
= string
;
570 while (*s
&& *s
!= '\r' && *s
!= '\n')
571 fputc (*s
++, stdout
);
572 printf (" ==> %lu\n", (unsigned long) t
);
582 /* Checks if the server 'reply' is one of the expected 'statuscodes' */
584 expected_statuscode (const char *reply
, const char *statuscodes
)
586 char *expected
, *code
;
589 if ((expected
= strdup (statuscodes
)) == NULL
)
590 die (STATE_UNKNOWN
, _("HTTP UNKNOWN - Memory allocation error\n"));
592 for (code
= strtok (expected
, ","); code
!= NULL
; code
= strtok (NULL
, ","))
593 if (strstr (reply
, code
) != NULL
) {
603 check_document_dates (const char *headers
, char **msg
)
606 char *server_date
= 0;
607 char *document_date
= 0;
608 int date_result
= STATE_OK
;
612 const char *field
= s
;
613 const char *value
= 0;
615 /* Find the end of the header field */
616 while (*s
&& !isspace(*s
) && *s
!= ':')
619 /* Remember the header value, if any. */
623 /* Skip to the end of the header, including continuation lines. */
624 while (*s
&& !(*s
== '\n' && (s
[1] != ' ' && s
[1] != '\t')))
627 /* Avoid stepping over end-of-string marker */
631 /* Process this header. */
632 if (value
&& value
> field
+2) {
633 char *ff
= (char *) malloc (value
-field
);
635 while (field
< value
-1)
636 *ss
++ = tolower(*field
++);
639 if (!strcmp (ff
, "date") || !strcmp (ff
, "last-modified")) {
641 while (*value
&& isspace (*value
))
643 for (e
= value
; *e
&& *e
!= '\r' && *e
!= '\n'; e
++)
645 ss
= (char *) malloc (e
- value
+ 1);
646 strncpy (ss
, value
, e
- value
);
648 if (!strcmp (ff
, "date")) {
649 if (server_date
) free (server_date
);
652 if (document_date
) free (document_date
);
660 /* Done parsing the body. Now check the dates we (hopefully) parsed. */
661 if (!server_date
|| !*server_date
) {
662 asprintf (msg
, _("%sServer date unknown, "), *msg
);
663 date_result
= max_state_alt(STATE_UNKNOWN
, date_result
);
664 } else if (!document_date
|| !*document_date
) {
665 asprintf (msg
, _("%sDocument modification date unknown, "), *msg
);
666 date_result
= max_state_alt(STATE_CRITICAL
, date_result
);
668 time_t srv_data
= parse_time_string (server_date
);
669 time_t doc_data
= parse_time_string (document_date
);
672 asprintf (msg
, _("%sServer date \"%100s\" unparsable, "), *msg
, server_date
);
673 date_result
= max_state_alt(STATE_CRITICAL
, date_result
);
674 } else if (doc_data
<= 0) {
675 asprintf (msg
, _("%sDocument date \"%100s\" unparsable, "), *msg
, document_date
);
676 date_result
= max_state_alt(STATE_CRITICAL
, date_result
);
677 } else if (doc_data
> srv_data
+ 30) {
678 asprintf (msg
, _("%sDocument is %d seconds in the future, "), *msg
, (int)doc_data
- (int)srv_data
);
679 date_result
= max_state_alt(STATE_CRITICAL
, date_result
);
680 } else if (doc_data
< srv_data
- maximum_age
) {
681 int n
= (srv_data
- doc_data
);
682 if (n
> (60 * 60 * 24 * 2)) {
683 asprintf (msg
, _("%sLast modified %.1f days ago, "), *msg
, ((float) n
) / (60 * 60 * 24));
684 date_result
= max_state_alt(STATE_CRITICAL
, date_result
);
686 asprintf (msg
, _("%sLast modified %d:%02d:%02d ago, "), *msg
, n
/ (60 * 60), (n
/ 60) % 60, n
% 60);
687 date_result
= max_state_alt(STATE_CRITICAL
, date_result
);
691 free (document_date
);
697 get_content_length (const char *headers
)
700 int content_length
= 0;
704 const char *field
= s
;
705 const char *value
= 0;
707 /* Find the end of the header field */
708 while (*s
&& !isspace(*s
) && *s
!= ':')
711 /* Remember the header value, if any. */
715 /* Skip to the end of the header, including continuation lines. */
716 while (*s
&& !(*s
== '\n' && (s
[1] != ' ' && s
[1] != '\t')))
720 /* Process this header. */
721 if (value
&& value
> field
+2) {
722 char *ff
= (char *) malloc (value
-field
);
724 while (field
< value
-1)
725 *ss
++ = tolower(*field
++);
728 if (!strcmp (ff
, "content-length")) {
730 while (*value
&& isspace (*value
))
732 for (e
= value
; *e
&& *e
!= '\r' && *e
!= '\n'; e
++)
734 ss
= (char *) malloc (e
- value
+ 1);
735 strncpy (ss
, value
, e
- value
);
737 content_length
= atoi(ss
);
743 return (content_length
);
747 prepend_slash (char *path
)
754 if ((newpath
= malloc (strlen(path
) + 2)) == NULL
)
755 die (STATE_UNKNOWN
, _("HTTP UNKNOWN - Memory allocation error\n"));
757 strcpy (newpath
+ 1, path
);
780 int result
= STATE_OK
;
782 /* try to connect to the host at the given port number */
783 if (my_tcp_connect (server_address
, server_port
, &sd
) != STATE_OK
)
784 die (STATE_CRITICAL
, _("HTTP CRITICAL - Unable to open TCP socket\n"));
786 if (use_ssl
== TRUE
) {
788 if (check_cert
== TRUE
) {
789 result
= np_net_ssl_check_cert(days_till_exp
);
790 np_net_ssl_cleanup();
795 #endif /* HAVE_SSL */
797 asprintf (&buf
, "%s %s %s\r\n%s\r\n", http_method
, server_url
, host_name
? "HTTP/1.1" : "HTTP/1.0", user_agent
);
799 /* tell HTTP/1.1 servers not to keep the connection alive */
800 asprintf (&buf
, "%sConnection: close\r\n", buf
);
802 /* optionally send the host header info */
805 * Specify the port only if we're using a non-default port (see RFC 2616,
806 * 14.23). Some server applications/configurations cause trouble if the
807 * (default) port is explicitly specified in the "Host:" header line.
809 if ((use_ssl
== FALSE
&& server_port
== HTTP_PORT
) ||
810 (use_ssl
== TRUE
&& server_port
== HTTPS_PORT
))
811 asprintf (&buf
, "%sHost: %s\r\n", buf
, host_name
);
813 asprintf (&buf
, "%sHost: %s:%d\r\n", buf
, host_name
, server_port
);
816 /* optionally send any other header tag */
817 if (http_opt_headers_count
) {
818 for (i
= 0; i
< http_opt_headers_count
; i
++) {
819 for ((pos
= strtok(http_opt_headers
[i
], INPUT_DELIMITER
)); pos
; (pos
= strtok(NULL
, INPUT_DELIMITER
)))
820 asprintf (&buf
, "%s%s\r\n", buf
, pos
);
822 /* This cannot be free'd here because a redirection will then try to access this and segfault */
823 /* Covered in a testcase in tests/check_http.t */
824 /* free(http_opt_headers); */
827 /* optionally send the authentication info */
828 if (strlen(user_auth
)) {
829 base64_encode_alloc (user_auth
, strlen (user_auth
), &auth
);
830 asprintf (&buf
, "%sAuthorization: Basic %s\r\n", buf
, auth
);
833 /* either send http POST data (any data, not only POST)*/
834 if (http_post_data
) {
835 if (http_content_type
) {
836 asprintf (&buf
, "%sContent-Type: %s\r\n", buf
, http_content_type
);
838 asprintf (&buf
, "%sContent-Type: application/x-www-form-urlencoded\r\n", buf
);
841 asprintf (&buf
, "%sContent-Length: %i\r\n\r\n", buf
, (int)strlen (http_post_data
));
842 asprintf (&buf
, "%s%s%s", buf
, http_post_data
, CRLF
);
845 /* or just a newline so the server knows we're done with the request */
846 asprintf (&buf
, "%s%s", buf
, CRLF
);
849 if (verbose
) printf ("%s\n", buf
);
850 my_send (buf
, strlen (buf
));
853 full_page
= strdup("");
854 while ((i
= my_recv (buffer
, MAX_INPUT_BUFFER
-1)) > 0) {
856 asprintf (&full_page
, "%s%s", full_page
, buffer
);
859 if (no_body
&& document_headers_done (full_page
)) {
865 if (i
< 0 && errno
!= ECONNRESET
) {
869 sslerr=SSL_get_error(ssl, i);
870 if ( sslerr == SSL_ERROR_SSL ) {
871 die (STATE_WARNING, _("HTTP WARNING - Client Certificate Required\n"));
873 die (STATE_CRITICAL, _("HTTP CRITICAL - Error on receive\n"));
879 die (STATE_CRITICAL
, _("HTTP CRITICAL - Error on receive\n"));
887 /* return a CRITICAL status if we couldn't read any data */
888 if (pagesize
== (size_t) 0)
889 die (STATE_CRITICAL
, _("HTTP CRITICAL - No data received from host\n"));
891 /* close the connection */
893 np_net_ssl_cleanup();
897 /* reset the alarm */
900 /* Save check time */
901 microsec
= deltime (tv
);
902 elapsed_time
= (double)microsec
/ 1.0e6
;
904 /* leave full_page untouched so we can free it later */
908 printf ("%s://%s:%d%s is %d characters\n",
909 use_ssl
? "https" : "http", server_address
,
910 server_port
, server_url
, (int)pagesize
);
912 /* find status line and null-terminate it */
914 page
+= (size_t) strcspn (page
, "\r\n");
916 page
+= (size_t) strspn (page
, "\r\n");
917 status_line
[strcspn(status_line
, "\r\n")] = 0;
920 printf ("STATUS: %s\n", status_line
);
922 /* find header info and null-terminate it */
924 while (strcspn (page
, "\r\n") > 0) {
925 page
+= (size_t) strcspn (page
, "\r\n");
927 if ((strspn (page
, "\r") == 1 && strspn (page
, "\r\n") >= 2) ||
928 (strspn (page
, "\n") == 1 && strspn (page
, "\r\n") >= 2))
933 page
+= (size_t) strspn (page
, "\r\n");
934 header
[pos
- header
] = 0;
936 printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header
,
937 (no_body
? " [[ skipped ]]" : page
));
939 /* make sure the status line matches the response we are looking for */
940 if (!expected_statuscode (status_line
, server_expect
)) {
941 if (server_port
== HTTP_PORT
)
943 _("Invalid HTTP response received from host: %s\n"),
947 _("Invalid HTTP response received from host on port %d: %s\n"),
948 server_port
, status_line
);
949 die (STATE_CRITICAL
, "HTTP CRITICAL - %s", msg
);
952 /* Bypass normal status line check if server_expect was set by user and not default */
953 /* NOTE: After this if/else block msg *MUST* be an asprintf-allocated string */
954 if ( server_expect_yn
) {
956 _("Status line output matched \"%s\" - "), server_expect
);
961 /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */
962 /* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT */
963 /* Status-Code = 3 DIGITS */
965 status_code
= strchr (status_line
, ' ') + sizeof (char);
966 if (strspn (status_code
, "1234567890") != 3)
967 die (STATE_CRITICAL
, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line
);
969 http_status
= atoi (status_code
);
971 /* check the return code */
973 if (http_status
>= 600 || http_status
< 100) {
974 die (STATE_CRITICAL
, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line
);
976 /* server errors result in a critical state */
977 else if (http_status
>= 500) {
978 asprintf (&msg
, _("%s - "), status_line
);
979 result
= STATE_CRITICAL
;
981 /* client errors result in a warning state */
982 else if (http_status
>= 400) {
983 asprintf (&msg
, _("%s - "), status_line
);
984 result
= max_state_alt(STATE_WARNING
, result
);
986 /* check redirected page if specified */
987 else if (http_status
>= 300) {
989 if (onredirect
== STATE_DEPENDENT
)
990 redir (header
, status_line
);
992 result
= max_state_alt(onredirect
, result
);
993 asprintf (&msg
, _("%s - "), status_line
);
994 } /* end if (http_status >= 300) */
996 /* Print OK status anyway */
997 asprintf (&msg
, _("%s - "), status_line
);
1000 } /* end else (server_expect_yn) */
1002 if (maximum_age
>= 0) {
1003 result
= max_state_alt(check_document_dates(header
, &msg
), result
);
1006 /* Page and Header content checks go here */
1008 if (strlen (string_expect
)) {
1009 if (!strstr (page
, string_expect
)) {
1010 asprintf (&msg
, _("%sstring not found, "), msg
);
1011 result
= STATE_CRITICAL
;
1015 if (strlen (regexp
)) {
1016 errcode
= regexec (&preg
, page
, REGS
, pmatch
, 0);
1017 if ((errcode
== 0 && invert_regex
== 0) || (errcode
== REG_NOMATCH
&& invert_regex
== 1)) {
1018 /* OK - No-op to avoid changing the logic around it */
1019 result
= max_state_alt(STATE_OK
, result
);
1021 else if ((errcode
== REG_NOMATCH
&& invert_regex
== 0) || (errcode
== 0 && invert_regex
== 1)) {
1022 if (invert_regex
== 0)
1023 asprintf (&msg
, _("%spattern not found, "), msg
);
1025 asprintf (&msg
, _("%spattern found, "), msg
);
1026 result
= STATE_CRITICAL
;
1029 /* FIXME: Shouldn't that be UNKNOWN? */
1030 regerror (errcode
, &preg
, errbuf
, MAX_INPUT_BUFFER
);
1031 asprintf (&msg
, _("%sExecute Error: %s, "), msg
, errbuf
);
1032 result
= STATE_CRITICAL
;
1036 /* make sure the page is of an appropriate size */
1037 /* page_len = get_content_length(header); */
1038 /* FIXME: Will this work with -N ? IMHO we should use
1039 * get_content_length(header) and always check if it's different than the
1042 /* FIXME: IIRC pagesize returns headers - shouldn't we make
1043 * it == get_content_length(header) ??
1045 page_len
= pagesize
;
1046 if ((max_page_len
> 0) && (page_len
> max_page_len
)) {
1047 asprintf (&msg
, _("%spage size %d too large, "), msg
, page_len
);
1048 result
= max_state_alt(STATE_WARNING
, result
);
1049 } else if ((min_page_len
> 0) && (page_len
< min_page_len
)) {
1050 asprintf (&msg
, _("%spage size %d too small, "), msg
, page_len
);
1051 result
= max_state_alt(STATE_WARNING
, result
);
1054 /* Cut-off trailing characters */
1055 if(msg
[strlen(msg
)-2] == ',')
1056 msg
[strlen(msg
)-2] = '\0';
1058 msg
[strlen(msg
)-3] = '\0';
1060 /* check elapsed time */
1062 _("%s - %d bytes in %.3f second response time %s|%s %s"),
1063 msg
, page_len
, elapsed_time
,
1064 (display_html
? "</A>" : ""),
1065 perfd_time (elapsed_time
), perfd_size (page_len
));
1067 if (check_critical_time
== TRUE
&& elapsed_time
> critical_time
)
1068 result
= STATE_CRITICAL
;
1069 if (check_warning_time
== TRUE
&& elapsed_time
> warning_time
)
1070 result
= max_state_alt(STATE_WARNING
, result
);
1072 die (result
, "HTTP %s: %s\n", state_text(result
), msg
);
1074 return STATE_UNKNOWN
;
1080 #define URI_HTTP "%5[HTPShtps]"
1081 #define URI_HOST "%255[-.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1082 #define URI_PORT "%6d" /* MAX_PORT's width is 5 chars, 6 to detect overflow */
1083 #define URI_PATH "%[-_.!~*'();/?:@&=+$,%#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]"
1084 #define HD1 URI_HTTP "://" URI_HOST ":" URI_PORT "/" URI_PATH
1085 #define HD2 URI_HTTP "://" URI_HOST "/" URI_PATH
1086 #define HD3 URI_HTTP "://" URI_HOST ":" URI_PORT
1087 #define HD4 URI_HTTP "://" URI_HOST
1088 #define HD5 URI_PATH
1091 redir (char *pos
, char *status_line
)
1100 addr
= malloc (MAX_IPV4_HOSTLENGTH
+ 1);
1102 die (STATE_UNKNOWN
, _("HTTP UNKNOWN - Could not allocate addr\n"));
1104 url
= malloc (strcspn (pos
, "\r\n"));
1106 die (STATE_UNKNOWN
, _("HTTP UNKNOWN - Could not allocate URL\n"));
1109 sscanf (pos
, "%1[Ll]%*1[Oo]%*1[Cc]%*1[Aa]%*1[Tt]%*1[Ii]%*1[Oo]%*1[Nn]:%n", xx
, &i
);
1111 pos
+= (size_t) strcspn (pos
, "\r\n");
1112 pos
+= (size_t) strspn (pos
, "\r\n");
1113 if (strlen(pos
) == 0)
1115 _("HTTP UNKNOWN - Could not find redirect location - %s%s\n"),
1116 status_line
, (display_html
? "</A>" : ""));
1121 pos
+= strspn (pos
, " \t");
1124 * RFC 2616 (4.2): ``Header fields can be extended over multiple lines by
1125 * preceding each extra line with at least one SP or HT.''
1127 for (; (i
= strspn (pos
, "\r\n")); pos
+= i
) {
1129 if (!(i
= strspn (pos
, " \t"))) {
1130 die (STATE_UNKNOWN
, _("HTTP UNKNOWN - Empty redirect location%s\n"),
1131 display_html
? "</A>" : "");
1135 url
= realloc (url
, strcspn (pos
, "\r\n") + 1);
1137 die (STATE_UNKNOWN
, _("HTTP UNKNOWN - Could not allocate URL\n"));
1139 /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */
1140 if (sscanf (pos
, HD1
, type
, addr
, &i
, url
) == 4) {
1141 url
= prepend_slash (url
);
1142 use_ssl
= server_type_check (type
);
1145 /* URI_HTTP URI_HOST URI_PATH */
1146 else if (sscanf (pos
, HD2
, type
, addr
, url
) == 3 ) {
1147 url
= prepend_slash (url
);
1148 use_ssl
= server_type_check (type
);
1149 i
= server_port_check (use_ssl
);
1152 /* URI_HTTP URI_HOST URI_PORT */
1153 else if (sscanf (pos
, HD3
, type
, addr
, &i
) == 3) {
1154 strcpy (url
, HTTP_URL
);
1155 use_ssl
= server_type_check (type
);
1158 /* URI_HTTP URI_HOST */
1159 else if (sscanf (pos
, HD4
, type
, addr
) == 2) {
1160 strcpy (url
, HTTP_URL
);
1161 use_ssl
= server_type_check (type
);
1162 i
= server_port_check (use_ssl
);
1166 else if (sscanf (pos
, HD5
, url
) == 1) {
1168 if ((url
[0] != '/')) {
1169 if ((x
= strrchr(server_url
, '/')))
1171 asprintf (&url
, "%s/%s", server_url
, url
);
1174 strcpy (type
, server_type
);
1175 strcpy (addr
, host_name
? host_name
: server_address
);
1180 _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"),
1181 pos
, (display_html
? "</A>" : ""));
1186 } /* end while (pos) */
1188 if (++redir_depth
> max_depth
)
1190 _("HTTP WARNING - maximum redirection depth %d exceeded - %s://%s:%d%s%s\n"),
1191 max_depth
, type
, addr
, i
, url
, (display_html
? "</A>" : ""));
1193 if (server_port
==i
&&
1194 !strcmp(server_address
, addr
) &&
1195 (host_name
&& !strcmp(host_name
, addr
)) &&
1196 !strcmp(server_url
, url
))
1198 _("HTTP WARNING - redirection creates an infinite loop - %s://%s:%d%s%s\n"),
1199 type
, addr
, i
, url
, (display_html
? "</A>" : ""));
1201 strcpy (server_type
, type
);
1204 host_name
= strdup (addr
);
1206 if (followsticky
== 0) {
1207 free (server_address
);
1208 server_address
= strdup (addr
);
1214 if ((server_port
= i
) > MAX_PORT
)
1216 _("HTTP UNKNOWN - Redirection to port above %d - %s://%s:%d%s%s\n"),
1217 MAX_PORT
, server_type
, server_address
, server_port
, server_url
,
1218 display_html
? "</A>" : "");
1221 printf (_("Redirection to %s://%s:%d%s\n"), server_type
,
1222 host_name
? host_name
: server_address
, server_port
, server_url
);
1229 server_type_check (const char *type
)
1231 if (strcmp (type
, "https"))
1238 server_port_check (int ssl_flag
)
1246 char *perfd_time (double elapsed_time
)
1248 return fperfdata ("time", elapsed_time
, "s",
1249 check_warning_time
, warning_time
,
1250 check_critical_time
, critical_time
,
1256 char *perfd_size (int page_len
)
1258 return perfdata ("size", page_len
, "B",
1259 (min_page_len
>0?TRUE
:FALSE
), min_page_len
,
1260 (min_page_len
>0?TRUE
:FALSE
), 0,
1267 print_revision (progname
, NP_VERSION
);
1269 printf ("Copyright (c) 1999 Ethan Galstad <nagios@nagios.org>\n");
1270 printf (COPYRIGHT
, copyright
, email
);
1272 printf ("%s\n", _("This plugin tests the HTTP service on the specified host. It can test"));
1273 printf ("%s\n", _("normal (http) and secure (https) servers, follow redirects, search for"));
1274 printf ("%s\n", _("strings and regular expressions, check connection times, and report on"));
1275 printf ("%s\n", _("certificate expiration times."));
1281 printf (_("NOTE: One or both of -H and -I must be specified"));
1285 printf (_(UT_HELP_VRSN
));
1286 printf (_(UT_EXTRA_OPTS
));
1288 printf (" %s\n", "-H, --hostname=ADDRESS");
1289 printf (" %s\n", _("Host name argument for servers using host headers (virtual host)"));
1290 printf (" %s\n", _("Append a port to include it in the header (eg: example.com:5000)"));
1291 printf (" %s\n", "-I, --IP-address=ADDRESS");
1292 printf (" %s\n", _("IP address or name (use numeric address if possible to bypass DNS lookup)."));
1293 printf (" %s\n", "-p, --port=INTEGER");
1294 printf (" %s", _("Port number (default: "));
1295 printf ("%d)\n", HTTP_PORT
);
1297 printf (_(UT_IPv46
));
1300 printf (" %s\n", "-S, --ssl");
1301 printf (" %s\n", _("Connect via SSL. Port defaults to 443"));
1302 printf (" %s\n", "-C, --certificate=INTEGER");
1303 printf (" %s\n", _("Minimum number of days a certificate has to be valid. Port defaults to 443"));
1304 printf (" %s\n", _("(when this option is used the URL is not checked.)\n"));
1307 printf (" %s\n", "-e, --expect=STRING");
1308 printf (" %s\n", _("Comma-delimited list of strings, at least one of them is expected in"));
1309 printf (" %s", _("the first (status) line of the server response (default: "));
1310 printf ("%s)\n", HTTP_EXPECT
);
1311 printf (" %s\n", _("If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)"));
1312 printf (" %s\n", "-s, --string=STRING");
1313 printf (" %s\n", _("String to expect in the content"));
1314 printf (" %s\n", "-u, --url=PATH");
1315 printf (" %s\n", _("URL to GET or POST (default: /)"));
1316 printf (" %s\n", "-P, --post=STRING");
1317 printf (" %s\n", _("URL encoded http POST data"));
1318 printf (" %s\n", "-j, --method=STRING (for example: HEAD, OPTIONS, TRACE, PUT, DELETE)");
1319 printf (" %s\n", _("Set HTTP method."));
1320 printf (" %s\n", "-N, --no-body");
1321 printf (" %s\n", _("Don't wait for document body: stop reading after headers."));
1322 printf (" %s\n", _("(Note that this still does an HTTP GET or POST, not a HEAD.)"));
1323 printf (" %s\n", "-M, --max-age=SECONDS");
1324 printf (" %s\n", _("Warn if document is more than SECONDS old. the number can also be of"));
1325 printf (" %s\n", _("the form \"10m\" for minutes, \"10h\" for hours, or \"10d\" for days."));
1326 printf (" %s\n", "-T, --content-type=STRING");
1327 printf (" %s\n", _("specify Content-Type header media type when POSTing\n"));
1329 printf (" %s\n", "-l, --linespan");
1330 printf (" %s\n", _("Allow regex to span newlines (must precede -r or -R)"));
1331 printf (" %s\n", "-r, --regex, --ereg=STRING");
1332 printf (" %s\n", _("Search page for regex STRING"));
1333 printf (" %s\n", "-R, --eregi=STRING");
1334 printf (" %s\n", _("Search page for case-insensitive regex STRING"));
1335 printf (" %s\n", "--invert-regex");
1336 printf (" %s\n", _("Return CRITICAL if found, OK if not\n"));
1338 printf (" %s\n", "-a, --authorization=AUTH_PAIR");
1339 printf (" %s\n", _("Username:password on sites with basic authentication"));
1340 printf (" %s\n", "-A, --useragent=STRING");
1341 printf (" %s\n", _("String to be sent in http header as \"User Agent\""));
1342 printf (" %s\n", "-k, --header=STRING");
1343 printf (" %s\n", _(" Any other tags to be sent in http header. Use multiple times for additional headers"));
1344 printf (" %s\n", "-L, --link");
1345 printf (" %s\n", _("Wrap output in HTML link (obsoleted by urlize)"));
1346 printf (" %s\n", "-f, --onredirect=<ok|warning|critical|follow|sticky>");
1347 printf (" %s\n", _("How to handle redirected pages. sticky is like follow but stick to the"));
1348 printf (" %s\n", _("specified IP address"));
1349 printf (" %s\n", "-m, --pagesize=INTEGER<:INTEGER>");
1350 printf (" %s\n", _("Minimum page size required (bytes) : Maximum page size required (bytes)"));
1352 printf (_(UT_WARN_CRIT
));
1354 printf (_(UT_TIMEOUT
), DEFAULT_SOCKET_TIMEOUT
);
1356 printf (_(UT_VERBOSE
));
1359 printf ("%s\n", _("Notes:"));
1360 printf (" %s\n", _("This plugin will attempt to open an HTTP connection with the host."));
1361 printf (" %s\n", _("Successful connects return STATE_OK, refusals and timeouts return STATE_CRITICAL"));
1362 printf (" %s\n", _("other errors return STATE_UNKNOWN. Successful connects, but incorrect reponse"));
1363 printf (" %s\n", _("messages from the host result in STATE_WARNING return values. If you are"));
1364 printf (" %s\n", _("checking a virtual server that uses 'host headers' you must supply the FQDN"));
1365 printf (" %s\n", _("(fully qualified domain name) as the [host_name] argument."));
1367 printf (_(UT_EXTRA_OPTS_NOTES
));
1371 printf (" %s\n", _("This plugin can also check whether an SSL enabled web server is able to"));
1372 printf (" %s\n", _("serve content (optionally within a specified time) or whether the X509 "));
1373 printf (" %s\n", _("certificate is still valid for the specified number of days."));
1375 printf ("%s\n", _("Examples:"));
1376 printf (" %s\n\n", "CHECK CONTENT: check_http -w 5 -c 10 --ssl -H www.verisign.com");
1377 printf (" %s\n", _("When the 'www.verisign.com' server returns its content within 5 seconds,"));
1378 printf (" %s\n", _("a STATE_OK will be returned. When the server returns its content but exceeds"));
1379 printf (" %s\n", _("the 5-second threshold, a STATE_WARNING will be returned. When an error occurs,"));
1380 printf (" %s\n\n", _("a STATE_CRITICAL will be returned."));
1382 printf (" %s\n\n", "CHECK CERTIFICATE: check_http -H www.verisign.com -C 14");
1383 printf (" %s\n", _("When the certificate of 'www.verisign.com' is valid for more than 14 days,"));
1384 printf (" %s\n", _("a STATE_OK is returned. When the certificate is still valid, but for less than"));
1385 printf (" %s\n", _("14 days, a STATE_WARNING is returned. A STATE_CRITICAL will be returned when"));
1386 printf (" %s\n", _("the certificate is expired."));
1389 printf (_(UT_SUPPORT
));
1398 printf (_("Usage:"));
1399 printf (" %s -H <vhost> | -I <IP-address> [-u <uri>] [-p <port>]\n",progname
);
1400 printf (" [-w <warn time>] [-c <critical time>] [-t <timeout>] [-L]\n");
1401 printf (" [-a auth] [-f <ok | warn | critcal | follow>] [-e <expect>]\n");
1402 printf (" [-s string] [-l] [-r <regex> | -R <case-insensitive regex>] [-P string]\n");
1403 printf (" [-m <min_pg_size>:<max_pg_size>] [-4|-6] [-N] [-M <age>] [-A string]\n");
1404 printf (" [-k string] [-S] [-C <age>] [-T <content-type>] [-j method]\n");