From 256d8d15acf98ee405f79b75a52692531d173f49 Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Wed, 2 Nov 2011 10:03:38 +0100 Subject: [PATCH 0/9] check_http: add support for chunked encoding via http-parser Hi, Here is an attempt to add support for chunked encoding in check_http by switching to the third-party HTTP parser from Joyent hosted at https://github.com/joyent/http-parser. The switch makes much of the in-house ad-hoc parsing code redundant, and induces some changes in the semantics of the -e switch, but that should be regarded as a step forward, since the role of that switch was to verify HTTP correctness (at least in part) and now we have a much better mechanism for that: the parser itself. The pagesize limits (-m switch) also gained a clearer semantics: they check the content size now, as that may be available even when -N is used, and does not depend on the transfer encoding. Ferenc Wagner (9): check_http: do not print page length if body was not read Add lib/http-parser submodule and adjust Makefile.am to use it in check_http check_http: move status code and redirect location parsing to http-parser check_http: whitespace change to fix up indentation by removing dummy block check_http: constify and rename the location argument for redir() check_http: make list of parsed headers configurable check_http: use header parsing machinery in check_document_dates() check_http: also read the response content via http-parse check_http: check content length instead of response length .gitmodules | 3 + lib/Makefile.am | 10 +- lib/http-parser | 1 + plugins/Makefile.am | 9 +- plugins/check_http.c | 596 ++++++++++++++++++++------------------------------ 5 files changed, 256 insertions(+), 363 deletions(-) create mode 100644 .gitmodules create mode 160000 lib/http-parser -- 1.7.2.5 From abc1d1c539376cb5531a9686e581ff62acf421e5 Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Mon, 31 Oct 2011 15:10:54 +0100 Subject: [PATCH 1/9] check_http: do not print page length if body was not read Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index 433c28e..6df9d23 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -923,7 +923,7 @@ check_http (void) /* leave full_page untouched so we can free it later */ page = full_page; - if (verbose) + if (verbose && !no_body) printf ("%s://%s:%d%s is %d characters\n", use_ssl ? "https" : "http", server_address, server_port, server_url, (int)pagesize); -- 1.7.2.5 From 27536d84b73489308f129ff5623ffd1445dfc5fb Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Mon, 31 Oct 2011 15:14:38 +0100 Subject: [PATCH 2/9] Add lib/http-parser submodule and adjust Makefile.am to use it in check_http Signed-off-by: Ferenc Wagner --- .gitmodules | 3 +++ lib/Makefile.am | 10 +++++++++- lib/http-parser | 1 + plugins/Makefile.am | 9 +++++---- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 .gitmodules create mode 160000 lib/http-parser diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5270743 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/http-parser"] + path = lib/http-parser + url = git://github.com/joyent/http-parser.git diff --git a/lib/Makefile.am b/lib/Makefile.am index 99fa591..561b7cf 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -2,7 +2,7 @@ SUBDIRS = . tests -noinst_LIBRARIES = libnagiosplug.a +noinst_LIBRARIES = libnagiosplug.a http-parser/libhttp_parser.a AM_CPPFLAGS = -DNP_STATE_DIR_PREFIX=\"$(localstatedir)\" @@ -18,3 +18,11 @@ INCLUDES = -I$(srcdir) -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/p test test-debug: cd tests && make $@ +http-parser/libhttp_parser.a: + cd http-parser && $(MAKE) package + +mostlyclean-local: + cd http-parser && $(MAKE) clean + +check-local: + cd http-parser && $(MAKE) test diff --git a/lib/http-parser b/lib/http-parser new file mode 160000 index 0000000..f1d48aa --- /dev/null +++ b/lib/http-parser @@ -0,0 +1 @@ +Subproject commit f1d48aa31c932f80a64122a75a87bc909b4073f9 diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 36a28b0..2df105e 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -13,7 +13,7 @@ AM_CFLAGS = -DNP_VERSION='"$(NP_VERSION)"' VPATH = $(top_srcdir) $(top_srcdir)/lib $(top_srcdir)/plugins $(top_srcdir)/plugins/t -INCLUDES = -I.. -I$(top_srcdir)/lib -I$(top_srcdir)/gl -I$(top_srcdir)/intl @LDAPINCLUDE@ @PGINCLUDE@ @SSLINCLUDE@ +INCLUDES = -I.. -I$(top_srcdir)/lib -I$(top_srcdir)/lib/http-parser -I$(top_srcdir)/gl -I$(top_srcdir)/intl @LDAPINCLUDE@ @PGINCLUDE@ @SSLINCLUDE@ localedir = $(datadir)/locale # gettext docs say to use AM_CPPFLAGS, but per module_CPPFLAGS override this @@ -48,6 +48,7 @@ BASEOBJS = utils.o ../lib/libnagiosplug.a ../gl/libgnu.a NETOBJS = netutils.o $(BASEOBJS) $(EXTRA_NETOBJS) SSLOBJS = sslutils.o NETLIBS = $(NETOBJS) $(SOCKETLIBS) +HTTPPARSER = ../lib/http-parser/libhttp_parser.a TESTS_ENVIRONMENT = perl -I $(top_builddir) -I $(top_srcdir) @@ -70,7 +71,7 @@ check_dns_LDADD = $(NETLIBS) runcmd.o check_dummy_LDADD = $(BASEOBJS) check_fping_LDADD = $(NETLIBS) popen.o check_game_LDADD = $(BASEOBJS) runcmd.o -check_http_LDADD = $(SSLOBJS) $(NETLIBS) $(SSLLIBS) +check_http_LDADD = $(SSLOBJS) $(NETLIBS) $(SSLLIBS) $(HTTPPARSER) check_hpjd_LDADD = $(NETLIBS) popen.o check_ldap_LDADD = $(NETLIBS) $(LDAPLIBS) check_load_LDADD = $(BASEOBJS) popen.o @@ -115,7 +116,7 @@ check_dns_DEPENDENCIES = check_dns.c $(NETOBJS) runcmd.o $(DEPLIBS) check_dummy_DEPENDENCIES = check_dummy.c $(DEPLIBS) check_fping_DEPENDENCIES = check_fping.c $(NETOBJS) popen.o $(DEPLIBS) check_game_DEPENDENCIES = check_game.c $(DEPLIBS) runcmd.o -check_http_DEPENDENCIES = check_http.c $(SSLOBJS) $(NETOBJS) $(DEPLIBS) +check_http_DEPENDENCIES = check_http.c $(SSLOBJS) $(NETOBJS) $(DEPLIBS) $(HTTPPARSER) check_hpjd_DEPENDENCIES = check_hpjd.c $(NETOBJS) popen.o $(DEPLIBS) check_ide_smart_DEPENDENCIES = check_ide_smart.c $(BASEOBJS) $(DEPLIBS) check_ldap_DEPENDENCIES = check_ldap.c $(NETOBJS) $(DEPLIBS) @@ -170,7 +171,7 @@ install-exec-hook: cd $(DESTDIR)$(libexecdir) && \ for i in $(check_tcp_programs) ; do rm -f $$i; ln -s check_tcp $$i ; done ;\ if [ -x check_ldap ] ; then rm -f check_ldaps ; ln -s check_ldap check_ldaps ; fi - + clean-local: rm -f $(check_tcp_programs) rm -f NP-VERSION-FILE -- 1.7.2.5 From e8b35f17bf23515dc43233ebbde1c0ccb11d5ecb Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Mon, 31 Oct 2011 15:20:48 +0100 Subject: [PATCH 3/9] check_http: move status code and redirect location parsing to http-parser This changes the semantics of the -e switch, as http-parser doesn't make the status string available. But it does substantial HTTP verification, so much of the original functionality will be regained once HTTP parsing errors become CRITICAL. This introduces some new gettext strings. Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 264 +++++++++++++++++++++++++------------------------- 1 files changed, 131 insertions(+), 133 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index 6df9d23..ada7a95 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -41,6 +41,7 @@ const char *email = "nagiosplug-devel@lists.sourceforge.net"; #include "netutils.h" #include "utils.h" #include "base64.h" +#include "http_parser.h" #include #define INPUT_DELIMITER ";" @@ -48,7 +49,15 @@ const char *email = "nagiosplug-devel@lists.sourceforge.net"; #define STICKY_HOST 1 #define STICKY_PORT 2 -#define HTTP_EXPECT "HTTP/1." +struct parser_data_t { + enum http_errno prev_callback; /* use HPE_CB_ macros for identifying callbacks */ + char *current_header; + size_t current_header_length; + int parsing_location; + char *location; + size_t location_length; +}; + enum { MAX_IPV4_HOSTLENGTH = 255, HTTP_PORT = 80, @@ -97,8 +106,7 @@ char *host_name; char *server_url; char *user_agent; int server_url_length; -int server_expect_yn = 0; -char server_expect[MAX_INPUT_BUFFER] = HTTP_EXPECT; +int status_expect = -1; char string_expect[MAX_INPUT_BUFFER] = ""; char output_string_search[30] = ""; char *warning_thresholds = NULL; @@ -126,7 +134,7 @@ char buffer[MAX_INPUT_BUFFER]; int process_arguments (int, char **); int check_http (void); -void redir (char *pos, char *status_line); +void redir (char *pos); int server_type_check(const char *type); int server_port_check(int ssl_flag); char *perfd_time (double microsec); @@ -367,10 +375,8 @@ process_arguments (int argc, char **argv) strncpy (string_expect, optarg, MAX_INPUT_BUFFER - 1); string_expect[MAX_INPUT_BUFFER - 1] = 0; break; - case 'e': /* string or substring */ - strncpy (server_expect, optarg, MAX_INPUT_BUFFER - 1); - server_expect[MAX_INPUT_BUFFER - 1] = 0; - server_expect_yn = 1; + case 'e': /* expected HTTP response code */ + status_expect = atoi (optarg); break; case 'T': /* Content-type */ asprintf (&http_content_type, "%s", optarg); @@ -589,26 +595,6 @@ parse_time_string (const char *string) } } -/* Checks if the server 'reply' is one of the expected 'statuscodes' */ -static int -expected_statuscode (const char *reply, const char *statuscodes) -{ - char *expected, *code; - int result = 0; - - if ((expected = strdup (statuscodes)) == NULL) - die (STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n")); - - for (code = strtok (expected, ","); code != NULL; code = strtok (NULL, ",")) - if (strstr (reply, code) != NULL) { - result = 1; - break; - } - - free (expected); - return result; -} - static int check_document_dates (const char *headers, char **msg) { @@ -772,16 +758,78 @@ prepend_slash (char *path) return newpath; } +/* +Returns 0 on success, 1 if allocation fails. +Works on binary data, but also zero-terminates the result +for easier string handling. +*/ +int +append (char **orig, size_t *orig_len, const char *extra, size_t len) +{ + *orig = realloc (*orig, *orig_len + len + 1); + if (!*orig) return 1; + memcpy (*orig + *orig_len, extra, len); + *orig_len += len; + (*orig)[*orig_len] = 0; + return 0; +} + +int +header_field_callback (http_parser *parser, const char *at, size_t length) +{ + struct parser_data_t *data = parser->data; + + switch (data->prev_callback) { + case HPE_CB_header_value: + data->current_header_length = 0; + /* fall through */ + case 0: + data->prev_callback = HPE_CB_header_field; + /* fall through */ + case HPE_CB_header_field: + return append (&data->current_header, &data->current_header_length, at, length); + default: + return 1; + } +} + +int +header_value_callback (http_parser *parser, const char *at, size_t length) +{ + struct parser_data_t *data = parser->data; + + switch (data->prev_callback) { + case HPE_CB_header_field: + data->parsing_location = !strcasecmp (data->current_header, "location"); + data->prev_callback = HPE_CB_header_value; + /* fall through */ + case HPE_CB_header_value: + return data->parsing_location && + append (&data->location, &data->location_length, at, length); + default: + return 1; + } +} + +int +headers_complete_callback (http_parser *parser) +{ + struct parser_data_t *data = parser->data; + + data->prev_callback = HPE_CB_headers_complete; + + if (no_body) /* Terminate parsing (thus reading) if the -N option is set */ + return 2; /* 1 means don't expect body in this callback */ + return 0; +} + int check_http (void) { char *msg; - char *status_line; - char *status_code; char *header; char *page; char *auth; - int http_status; int i = 0; size_t pagesize = 0; char *full_page; @@ -792,6 +840,9 @@ check_http (void) double elapsed_time; int page_len = 0; int result = STATE_OK; + http_parser_settings settings; + http_parser parser; + struct parser_data_t parser_data; /* try to connect to the host at the given port number */ if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK) @@ -869,9 +920,29 @@ check_http (void) if (verbose) printf ("%s\n", buf); my_send (buf, strlen (buf)); + /* Initialize the HTTP parser */ + http_parser_init (&parser, HTTP_RESPONSE); + memset (&parser_data, 0, sizeof parser_data); + parser.data = &parser_data; + memset (&settings, 0, sizeof settings); + settings.on_header_field = header_field_callback; + settings.on_header_value = header_value_callback; + settings.on_headers_complete = headers_complete_callback; + /* fetch the page */ full_page = strdup(""); - while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) > 0) { + while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) >= 0) { + int nparsed = http_parser_execute(&parser, &settings, buffer, i); + if (nparsed != i) { + enum http_errno code = HTTP_PARSER_ERRNO (&parser); + if (code == HPE_CB_headers_complete) { /* the -N check fired */ + /* break; FIXME this would break the current header code */ + } else { + printf ("http_parser_execute returned %d instead of %i: %s\n", + nparsed, i, http_errno_description (code)); + } + } + if (i == 0) break; buffer[i] = '\0'; asprintf (&full_page_new, "%s%s", full_page, buffer); free (full_page); @@ -928,15 +999,9 @@ check_http (void) use_ssl ? "https" : "http", server_address, server_port, server_url, (int)pagesize); - /* find status line and null-terminate it */ - status_line = page; + /* skip status line */ page += (size_t) strcspn (page, "\r\n"); - pos = page; page += (size_t) strspn (page, "\r\n"); - status_line[strcspn(status_line, "\r\n")] = 0; - strip (status_line); - if (verbose) - printf ("STATUS: %s\n", status_line); /* find header info and null-terminate it */ header = page; @@ -955,68 +1020,38 @@ check_http (void) printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header, (no_body ? " [[ skipped ]]" : page)); - /* make sure the status line matches the response we are looking for */ - if (!expected_statuscode (status_line, server_expect)) { - if (server_port == HTTP_PORT) - asprintf (&msg, - _("Invalid HTTP response received from host: %s\n"), - status_line); - else - asprintf (&msg, - _("Invalid HTTP response received from host on port %d: %s\n"), - server_port, status_line); - die (STATE_CRITICAL, "HTTP CRITICAL - %s", msg); - } - - /* Bypass normal status line check if server_expect was set by user and not default */ - /* NOTE: After this if/else block msg *MUST* be an asprintf-allocated string */ - if ( server_expect_yn ) { - asprintf (&msg, - _("Status line output matched \"%s\" - "), server_expect); - if (verbose) - printf ("%s\n",msg); - } - else { - /* Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ - /* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT */ - /* Status-Code = 3 DIGITS */ - - status_code = strchr (status_line, ' ') + sizeof (char); - if (strspn (status_code, "1234567890") != 3) - die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status Line (%s)\n"), status_line); - - http_status = atoi (status_code); - - /* check the return code */ - - if (http_status >= 600 || http_status < 100) { - die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%s)\n"), status_line); + if (status_expect != -1) { + if (parser.status_code == status_expect) + asprintf (&msg, _("Got expected code %d - "), status_expect); + else { + asprintf (&msg, _("Invalid HTTP status received from host: %d\n"), + parser.status_code); + die (STATE_CRITICAL, "HTTP CRITICAL - %s", msg); + } + } else { + if (parser.status_code >= 600 || parser.status_code < 100) { + die (STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%d)\n"), parser.status_code); } /* server errors result in a critical state */ - else if (http_status >= 500) { - asprintf (&msg, _("%s - "), status_line); + else if (parser.status_code >= 500) { result = STATE_CRITICAL; } /* client errors result in a warning state */ - else if (http_status >= 400) { - asprintf (&msg, _("%s - "), status_line); + else if (parser.status_code >= 400) { result = max_state_alt(STATE_WARNING, result); } /* check redirected page if specified */ - else if (http_status >= 300) { - - if (onredirect == STATE_DEPENDENT) - redir (header, status_line); + else if (parser.status_code >= 300) { + if (onredirect == STATE_DEPENDENT) { + if (parser_data.location) + redir (parser_data.location); + else die (STATE_CRITICAL, _("Redirect without Location header\n")); + } else result = max_state_alt(onredirect, result); - asprintf (&msg, _("%s - "), status_line); - } /* end if (http_status >= 300) */ - else { - /* Print OK status anyway */ - asprintf (&msg, _("%s - "), status_line); } - - } /* end else (server_expect_yn) */ + asprintf (&msg, _("%d - "), parser.status_code); + } /* reset the alarm - must be called *after* redir or we'll never die on redirects! */ alarm (0); @@ -1111,11 +1146,10 @@ check_http (void) #define HD5 URI_PATH void -redir (char *pos, char *status_line) +redir (char *pos) { int i = 0; char *x; - char xx[2]; char type[6]; char *addr; char *url; @@ -1124,41 +1158,11 @@ redir (char *pos, char *status_line) if (addr == NULL) die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate addr\n")); - url = malloc (strcspn (pos, "\r\n")); + url = malloc (strlen (pos) + 1); if (url == NULL) die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n")); - while (pos) { - sscanf (pos, "%1[Ll]%*1[Oo]%*1[Cc]%*1[Aa]%*1[Tt]%*1[Ii]%*1[Oo]%*1[Nn]:%n", xx, &i); - if (i == 0) { - pos += (size_t) strcspn (pos, "\r\n"); - pos += (size_t) strspn (pos, "\r\n"); - if (strlen(pos) == 0) - die (STATE_UNKNOWN, - _("HTTP UNKNOWN - Could not find redirect location - %s%s\n"), - status_line, (display_html ? "" : "")); - continue; - } - - pos += i; - pos += strspn (pos, " \t"); - - /* - * RFC 2616 (4.2): ``Header fields can be extended over multiple lines by - * preceding each extra line with at least one SP or HT.'' - */ - for (; (i = strspn (pos, "\r\n")); pos += i) { - pos += i; - if (!(i = strspn (pos, " \t"))) { - die (STATE_UNKNOWN, _("HTTP UNKNOWN - Empty redirect location%s\n"), - display_html ? "" : ""); - } - } - - url = realloc (url, strcspn (pos, "\r\n") + 1); - if (url == NULL) - die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n")); - + { /* dummy block to reduce patch diff */ /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */ if (sscanf (pos, HD1, type, addr, &i, url) == 4) { url = prepend_slash (url); @@ -1203,10 +1207,7 @@ redir (char *pos, char *status_line) _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"), pos, (display_html ? "" : "")); } - - break; - - } /* end while (pos) */ + } if (++redir_depth > max_depth) die (STATE_WARNING, @@ -1332,11 +1333,8 @@ print_help (void) printf (" %s\n", _("(when this option is used the URL is not checked.)\n")); #endif - printf (" %s\n", "-e, --expect=STRING"); - printf (" %s\n", _("Comma-delimited list of strings, at least one of them is expected in")); - printf (" %s", _("the first (status) line of the server response (default: ")); - printf ("%s)\n", HTTP_EXPECT); - printf (" %s\n", _("If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)")); + printf (" %s\n", "-e, --expect=INTEGER"); + printf (" %s\n", _("Expected HTTP status code, overriding the default logic")); printf (" %s\n", "-s, --string=STRING"); printf (" %s\n", _("String to expect in the content")); printf (" %s\n", "-u, --url=PATH"); -- 1.7.2.5 From a35b05d935f975478c90360ab56fb54d2d82e9c6 Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Wed, 2 Nov 2011 09:52:19 +0100 Subject: [PATCH 4/9] check_http: whitespace change to fix up indentation by removing dummy block Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 76 ++++++++++++++++++++++++------------------------- 1 files changed, 37 insertions(+), 39 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index ada7a95..3966ab4 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -1162,51 +1162,49 @@ redir (char *pos) if (url == NULL) die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n")); - { /* dummy block to reduce patch diff */ - /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */ - if (sscanf (pos, HD1, type, addr, &i, url) == 4) { - url = prepend_slash (url); - use_ssl = server_type_check (type); - } + /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */ + if (sscanf (pos, HD1, type, addr, &i, url) == 4) { + url = prepend_slash (url); + use_ssl = server_type_check (type); + } - /* URI_HTTP URI_HOST URI_PATH */ - else if (sscanf (pos, HD2, type, addr, url) == 3 ) { - url = prepend_slash (url); - use_ssl = server_type_check (type); - i = server_port_check (use_ssl); - } + /* URI_HTTP URI_HOST URI_PATH */ + else if (sscanf (pos, HD2, type, addr, url) == 3 ) { + url = prepend_slash (url); + use_ssl = server_type_check (type); + i = server_port_check (use_ssl); + } - /* URI_HTTP URI_HOST URI_PORT */ - else if (sscanf (pos, HD3, type, addr, &i) == 3) { - strcpy (url, HTTP_URL); - use_ssl = server_type_check (type); - } + /* URI_HTTP URI_HOST URI_PORT */ + else if (sscanf (pos, HD3, type, addr, &i) == 3) { + strcpy (url, HTTP_URL); + use_ssl = server_type_check (type); + } - /* URI_HTTP URI_HOST */ - else if (sscanf (pos, HD4, type, addr) == 2) { - strcpy (url, HTTP_URL); - use_ssl = server_type_check (type); - i = server_port_check (use_ssl); - } + /* URI_HTTP URI_HOST */ + else if (sscanf (pos, HD4, type, addr) == 2) { + strcpy (url, HTTP_URL); + use_ssl = server_type_check (type); + i = server_port_check (use_ssl); + } - /* URI_PATH */ - else if (sscanf (pos, HD5, url) == 1) { - /* relative url */ - if ((url[0] != '/')) { - if ((x = strrchr(server_url, '/'))) - *x = '\0'; - asprintf (&url, "%s/%s", server_url, url); - } - i = server_port; - strcpy (type, server_type); - strcpy (addr, host_name ? host_name : server_address); + /* URI_PATH */ + else if (sscanf (pos, HD5, url) == 1) { + /* relative url */ + if ((url[0] != '/')) { + if ((x = strrchr(server_url, '/'))) + *x = '\0'; + asprintf (&url, "%s/%s", server_url, url); } + i = server_port; + strcpy (type, server_type); + strcpy (addr, host_name ? host_name : server_address); + } - else { - die (STATE_UNKNOWN, - _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"), - pos, (display_html ? "" : "")); - } + else { + die (STATE_UNKNOWN, + _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"), + pos, (display_html ? "" : "")); } if (++redir_depth > max_depth) -- 1.7.2.5 From 3f410b149eb7cd112466bbb147525c1d497adaa2 Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Mon, 31 Oct 2011 17:45:49 +0100 Subject: [PATCH 5/9] check_http: constify and rename the location argument for redir() Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 18 +++++++++--------- 1 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index 3966ab4..c4586d5 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -134,7 +134,7 @@ char buffer[MAX_INPUT_BUFFER]; int process_arguments (int, char **); int check_http (void); -void redir (char *pos); +void redir (const char *location); int server_type_check(const char *type); int server_port_check(int ssl_flag); char *perfd_time (double microsec); @@ -1146,7 +1146,7 @@ check_http (void) #define HD5 URI_PATH void -redir (char *pos) +redir (const char *location) { int i = 0; char *x; @@ -1158,38 +1158,38 @@ redir (char *pos) if (addr == NULL) die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate addr\n")); - url = malloc (strlen (pos) + 1); + url = malloc (strlen (location) + 1); if (url == NULL) die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not allocate URL\n")); /* URI_HTTP, URI_HOST, URI_PORT, URI_PATH */ - if (sscanf (pos, HD1, type, addr, &i, url) == 4) { + if (sscanf (location, HD1, type, addr, &i, url) == 4) { url = prepend_slash (url); use_ssl = server_type_check (type); } /* URI_HTTP URI_HOST URI_PATH */ - else if (sscanf (pos, HD2, type, addr, url) == 3 ) { + else if (sscanf (location, HD2, type, addr, url) == 3 ) { url = prepend_slash (url); use_ssl = server_type_check (type); i = server_port_check (use_ssl); } /* URI_HTTP URI_HOST URI_PORT */ - else if (sscanf (pos, HD3, type, addr, &i) == 3) { + else if (sscanf (location, HD3, type, addr, &i) == 3) { strcpy (url, HTTP_URL); use_ssl = server_type_check (type); } /* URI_HTTP URI_HOST */ - else if (sscanf (pos, HD4, type, addr) == 2) { + else if (sscanf (location, HD4, type, addr) == 2) { strcpy (url, HTTP_URL); use_ssl = server_type_check (type); i = server_port_check (use_ssl); } /* URI_PATH */ - else if (sscanf (pos, HD5, url) == 1) { + else if (sscanf (location, HD5, url) == 1) { /* relative url */ if ((url[0] != '/')) { if ((x = strrchr(server_url, '/'))) @@ -1204,7 +1204,7 @@ redir (char *pos) else { die (STATE_UNKNOWN, _("HTTP UNKNOWN - Could not parse redirect location - %s%s\n"), - pos, (display_html ? "" : "")); + location, (display_html ? "" : "")); } if (++redir_depth > max_depth) -- 1.7.2.5 From d1b806941bf6bf94754e86e832a11eabc003a656 Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Mon, 31 Oct 2011 17:47:47 +0100 Subject: [PATCH 6/9] check_http: make list of parsed headers configurable Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 58 +++++++++++++++++++++++++++++++++++++++---------- 1 files changed, 46 insertions(+), 12 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index c4586d5..5466e06 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -49,13 +49,17 @@ const char *email = "nagiosplug-devel@lists.sourceforge.net"; #define STICKY_HOST 1 #define STICKY_PORT 2 +struct header_data_t { + const char *field; + char *value; +}; + struct parser_data_t { enum http_errno prev_callback; /* use HPE_CB_ macros for identifying callbacks */ - char *current_header; - size_t current_header_length; - int parsing_location; - char *location; - size_t location_length; + char *current_field; + size_t current_length; + struct header_data_t *current_header; + struct header_data_t *headers; }; enum { @@ -758,6 +762,17 @@ prepend_slash (char *path) return newpath; } +const char * +get_value (const struct header_data_t *header, const char *field) +{ + while (header->field) { + if (!strcasecmp (header->field, field)) + return header->value; + header++; + } + return 0; +} + /* Returns 0 on success, 1 if allocation fails. Works on binary data, but also zero-terminates the result @@ -781,13 +796,13 @@ header_field_callback (http_parser *parser, const char *at, size_t length) switch (data->prev_callback) { case HPE_CB_header_value: - data->current_header_length = 0; + data->current_length = 0; /* fall through */ case 0: data->prev_callback = HPE_CB_header_field; /* fall through */ case HPE_CB_header_field: - return append (&data->current_header, &data->current_header_length, at, length); + return append (&data->current_field, &data->current_length, at, length); default: return 1; } @@ -797,15 +812,23 @@ int header_value_callback (http_parser *parser, const char *at, size_t length) { struct parser_data_t *data = parser->data; + struct header_data_t *header; switch (data->prev_callback) { case HPE_CB_header_field: - data->parsing_location = !strcasecmp (data->current_header, "location"); + data->current_header = 0; + data->current_length = 0; + for (header = data->headers; header->field; header++) + if (!strcasecmp (data->current_field, header->field)) { + data->current_header = header; + break; + } data->prev_callback = HPE_CB_header_value; /* fall through */ case HPE_CB_header_value: - return data->parsing_location && - append (&data->location, &data->location_length, at, length); + return data->current_header && + append (&(data->current_header->value), + &data->current_length, at, length); default: return 1; } @@ -816,6 +839,13 @@ headers_complete_callback (http_parser *parser) { struct parser_data_t *data = parser->data; + if (verbose) { + const struct header_data_t *header; + printf ("Parsed headers:\n"); + for (header = data->headers; header->field; header++) + printf ("%s: %s\n", header->field, header->value); + } + data->prev_callback = HPE_CB_headers_complete; if (no_body) /* Terminate parsing (thus reading) if the -N option is set */ @@ -843,6 +873,8 @@ check_http (void) http_parser_settings settings; http_parser parser; struct parser_data_t parser_data; + struct header_data_t interesting_headers[] = + {{"location", 0}, {"date", 0}, {"last-modified", 0}, {0, 0}}; /* try to connect to the host at the given port number */ if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK) @@ -923,6 +955,7 @@ check_http (void) /* Initialize the HTTP parser */ http_parser_init (&parser, HTTP_RESPONSE); memset (&parser_data, 0, sizeof parser_data); + parser_data.headers = interesting_headers; parser.data = &parser_data; memset (&settings, 0, sizeof settings); settings.on_header_field = header_field_callback; @@ -1043,8 +1076,9 @@ check_http (void) /* check redirected page if specified */ else if (parser.status_code >= 300) { if (onredirect == STATE_DEPENDENT) { - if (parser_data.location) - redir (parser_data.location); + const char *location = get_value (parser_data.headers, "location"); + if (location) + redir (location); else die (STATE_CRITICAL, _("Redirect without Location header\n")); } else -- 1.7.2.5 From 62e8868bb24c2161ccb7748dba82c6a90618aac3 Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Mon, 31 Oct 2011 17:57:11 +0100 Subject: [PATCH 7/9] check_http: use header parsing machinery in check_document_dates() Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 61 +++---------------------------------------------- 1 files changed, 4 insertions(+), 57 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index 5466e06..423bb22 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -600,63 +600,10 @@ parse_time_string (const char *string) } static int -check_document_dates (const char *headers, char **msg) +check_document_dates (const char *server_date, const char *document_date, char **msg) { - const char *s; - char *server_date = 0; - char *document_date = 0; int date_result = STATE_OK; - s = headers; - while (*s) { - const char *field = s; - const char *value = 0; - - /* Find the end of the header field */ - while (*s && !isspace(*s) && *s != ':') - s++; - - /* Remember the header value, if any. */ - if (*s == ':') - value = ++s; - - /* Skip to the end of the header, including continuation lines. */ - while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t'))) - s++; - - /* Avoid stepping over end-of-string marker */ - if (*s) - s++; - - /* Process this header. */ - if (value && value > field+2) { - char *ff = (char *) malloc (value-field); - char *ss = ff; - while (field < value-1) - *ss++ = tolower(*field++); - *ss++ = 0; - - if (!strcmp (ff, "date") || !strcmp (ff, "last-modified")) { - const char *e; - while (*value && isspace (*value)) - value++; - for (e = value; *e && *e != '\r' && *e != '\n'; e++) - ; - ss = (char *) malloc (e - value + 1); - strncpy (ss, value, e - value); - ss[e - value] = 0; - if (!strcmp (ff, "date")) { - if (server_date) free (server_date); - server_date = ss; - } else { - if (document_date) free (document_date); - document_date = ss; - } - } - free (ff); - } - } - /* Done parsing the body. Now check the dates we (hopefully) parsed. */ if (!server_date || !*server_date) { asprintf (msg, _("%sServer date unknown, "), *msg); @@ -687,8 +634,6 @@ check_document_dates (const char *headers, char **msg) date_result = max_state_alt(STATE_CRITICAL, date_result); } } - free (server_date); - free (document_date); } return date_result; } @@ -1091,7 +1036,9 @@ check_http (void) alarm (0); if (maximum_age >= 0) { - result = max_state_alt(check_document_dates(header, &msg), result); + result = max_state_alt(check_document_dates(get_value (parser_data.headers, "date"), + get_value (parser_data.headers, "last-modified"), &msg), + result); } /* Page and Header content checks go here */ -- 1.7.2.5 From efe4e949ae4ba64f333f402d0f4b54f07b18164c Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Tue, 1 Nov 2011 16:17:00 +0100 Subject: [PATCH 8/9] check_http: also read the response content via http-parse This gets rid of good a bunch of code, and also gives free decoding of Transfer-Encoding: chunked, which all HTTP/1.1 clients must support. Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 86 ++++++++++++------------------------------------- 1 files changed, 21 insertions(+), 65 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index 423bb22..deea38c 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -60,6 +60,7 @@ struct parser_data_t { size_t current_length; struct header_data_t *current_header; struct header_data_t *headers; + char *content; }; enum { @@ -488,26 +489,6 @@ process_arguments (int argc, char **argv) return TRUE; } - - -/* Returns 1 if we're done processing the document body; 0 to keep going */ -static int -document_headers_done (char *full_page) -{ - const char *body; - - for (body = full_page; *body; body++) { - if (!strncmp (body, "\n\n", 2) || !strncmp (body, "\n\r\n", 3)) - break; - } - - if (!*body) - return 0; /* haven't read end of headers yet */ - - full_page[body - full_page] = 0; - return 1; -} - static time_t parse_time_string (const char *string) { @@ -792,6 +773,7 @@ headers_complete_callback (http_parser *parser) } data->prev_callback = HPE_CB_headers_complete; + data->current_length = 0; /* prepare for the body */ if (no_body) /* Terminate parsing (thus reading) if the -N option is set */ return 2; /* 1 means don't expect body in this callback */ @@ -799,16 +781,20 @@ headers_complete_callback (http_parser *parser) } int +body_callback (http_parser *parser, const char *at, size_t length) +{ + struct parser_data_t *data = parser->data; + + return append (&data->content, &data->current_length, at, length); +} + +int check_http (void) { char *msg; - char *header; - char *page; char *auth; int i = 0; size_t pagesize = 0; - char *full_page; - char *full_page_new; char *buf; char *pos; long microsec; @@ -906,31 +892,19 @@ check_http (void) settings.on_header_field = header_field_callback; settings.on_header_value = header_value_callback; settings.on_headers_complete = headers_complete_callback; + settings.on_body = body_callback; /* fetch the page */ - full_page = strdup(""); while ((i = my_recv (buffer, MAX_INPUT_BUFFER-1)) >= 0) { int nparsed = http_parser_execute(&parser, &settings, buffer, i); + pagesize += i; if (nparsed != i) { enum http_errno code = HTTP_PARSER_ERRNO (&parser); - if (code == HPE_CB_headers_complete) { /* the -N check fired */ - /* break; FIXME this would break the current header code */ - } else { - printf ("http_parser_execute returned %d instead of %i: %s\n", - nparsed, i, http_errno_description (code)); - } + if (code == HPE_CB_headers_complete) break; /* the -N check fired */ + else die (STATE_CRITICAL, _("HTTP CRITICAL - error parsing response: %s"), + http_errno_description (code)); } if (i == 0) break; - buffer[i] = '\0'; - asprintf (&full_page_new, "%s%s", full_page, buffer); - free (full_page); - full_page = full_page_new; - pagesize += i; - - if (no_body && document_headers_done (full_page)) { - i = 0; - break; - } } if (i < 0 && errno != ECONNRESET) { @@ -969,34 +943,16 @@ check_http (void) microsec = deltime (tv); elapsed_time = (double)microsec / 1.0e6; - /* leave full_page untouched so we can free it later */ - page = full_page; - if (verbose && !no_body) printf ("%s://%s:%d%s is %d characters\n", use_ssl ? "https" : "http", server_address, server_port, server_url, (int)pagesize); - /* skip status line */ - page += (size_t) strcspn (page, "\r\n"); - page += (size_t) strspn (page, "\r\n"); - - /* find header info and null-terminate it */ - header = page; - while (strcspn (page, "\r\n") > 0) { - page += (size_t) strcspn (page, "\r\n"); - pos = page; - if ((strspn (page, "\r") == 1 && strspn (page, "\r\n") >= 2) || - (strspn (page, "\n") == 1 && strspn (page, "\r\n") >= 2)) - page += (size_t) 2; - else - page += (size_t) 1; + if (verbose && !no_body) { + puts ("**** CONTENT ****"); + fwrite (parser_data.content, parser_data.current_length, 1, stdout); + putchar ('\n'); } - page += (size_t) strspn (page, "\r\n"); - header[pos - header] = 0; - if (verbose) - printf ("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", header, - (no_body ? " [[ skipped ]]" : page)); if (status_expect != -1) { if (parser.status_code == status_expect) @@ -1044,7 +1000,7 @@ check_http (void) /* Page and Header content checks go here */ if (strlen (string_expect)) { - if (!strstr (page, string_expect)) { + if (!strstr (parser_data.content, string_expect)) { strncpy(&output_string_search[0],string_expect,sizeof(output_string_search)); if(output_string_search[sizeof(output_string_search)-1]!='\0') { bcopy("...",&output_string_search[sizeof(output_string_search)-4],4); @@ -1055,7 +1011,7 @@ check_http (void) } if (strlen (regexp)) { - errcode = regexec (&preg, page, REGS, pmatch, 0); + errcode = regexec (&preg, parser_data.content, REGS, pmatch, 0); if ((errcode == 0 && invert_regex == 0) || (errcode == REG_NOMATCH && invert_regex == 1)) { /* OK - No-op to avoid changing the logic around it */ result = max_state_alt(STATE_OK, result); -- 1.7.2.5 From 256d8d15acf98ee405f79b75a52692531d173f49 Mon Sep 17 00:00:00 2001 From: Ferenc Wagner Date: Wed, 2 Nov 2011 07:57:30 +0100 Subject: [PATCH 9/9] check_http: check content length instead of response length The content length does not depend on the transfer encoding and may be possible to derive without fetching the entire body (cf. -N option). Signed-off-by: Ferenc Wagner --- plugins/check_http.c | 93 +++++++++++--------------------------------------- 1 files changed, 20 insertions(+), 73 deletions(-) diff --git a/plugins/check_http.c b/plugins/check_http.c index deea38c..27f5cd3 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -619,59 +619,6 @@ check_document_dates (const char *server_date, const char *document_date, char * return date_result; } -int -get_content_length (const char *headers) -{ - const char *s; - int content_length = 0; - - s = headers; - while (*s) { - const char *field = s; - const char *value = 0; - - /* Find the end of the header field */ - while (*s && !isspace(*s) && *s != ':') - s++; - - /* Remember the header value, if any. */ - if (*s == ':') - value = ++s; - - /* Skip to the end of the header, including continuation lines. */ - while (*s && !(*s == '\n' && (s[1] != ' ' && s[1] != '\t'))) - s++; - - /* Avoid stepping over end-of-string marker */ - if (*s) - s++; - - /* Process this header. */ - if (value && value > field+2) { - char *ff = (char *) malloc (value-field); - char *ss = ff; - while (field < value-1) - *ss++ = tolower(*field++); - *ss++ = 0; - - if (!strcmp (ff, "content-length")) { - const char *e; - while (*value && isspace (*value)) - value++; - for (e = value; *e && *e != '\r' && *e != '\n'; e++) - ; - ss = (char *) malloc (e - value + 1); - strncpy (ss, value, e - value); - ss[e - value] = 0; - content_length = atoi(ss); - free (ss); - } - free (ff); - } - } - return (content_length); -} - char * prepend_slash (char *path) { @@ -799,13 +746,13 @@ check_http (void) char *pos; long microsec; double elapsed_time; - int page_len = 0; + int page_len = -1; int result = STATE_OK; http_parser_settings settings; http_parser parser; struct parser_data_t parser_data; struct header_data_t interesting_headers[] = - {{"location", 0}, {"date", 0}, {"last-modified", 0}, {0, 0}}; + {{"location", 0}, {"date", 0}, {"last-modified", 0}, {"content-length", 0}, {0, 0}}; /* try to connect to the host at the given port number */ if (my_tcp_connect (server_address, server_port, &sd) != STATE_OK) @@ -1031,21 +978,21 @@ check_http (void) } } - /* make sure the page is of an appropriate size */ - /* page_len = get_content_length(header); */ - /* FIXME: Will this work with -N ? IMHO we should use - * get_content_length(header) and always check if it's different than the - * returned pagesize - */ - /* FIXME: IIRC pagesize returns headers - shouldn't we make - * it == get_content_length(header) ?? - */ - page_len = pagesize; - if ((max_page_len > 0) && (page_len > max_page_len)) { - asprintf (&msg, _("%spage size %d too large, "), msg, page_len); - result = max_state_alt(STATE_WARNING, result); - } else if ((min_page_len > 0) && (page_len < min_page_len)) { - asprintf (&msg, _("%spage size %d too small, "), msg, page_len); + /* make sure the content is of an appropriate size */ + if (no_body) { + const char *content_length = get_value (parser_data.headers, "content-length"); + if (content_length) page_len = atol (content_length); + } else page_len = parser_data.current_length; + if (page_len != -1) { + if ((max_page_len > 0) && (page_len > max_page_len)) { + asprintf (&msg, _("%spage size %d too large, "), msg, page_len); + result = max_state_alt(STATE_WARNING, result); + } else if ((min_page_len > 0) && (page_len < min_page_len)) { + asprintf (&msg, _("%spage size %d too small, "), msg, page_len); + result = max_state_alt(STATE_WARNING, result); + } + } else if (max_page_len > 0 || min_page_len > 0) { + asprintf (&msg, _("%spage size unknown, "), msg); result = max_state_alt(STATE_WARNING, result); } @@ -1058,7 +1005,7 @@ check_http (void) /* check elapsed time */ asprintf (&msg, _("%s - %d bytes in %.3f second response time %s|%s %s"), - msg, page_len, elapsed_time, + msg, pagesize, elapsed_time, (display_html ? "" : ""), perfd_time (elapsed_time), perfd_size (page_len)); @@ -1310,7 +1257,7 @@ print_help (void) printf (" %s\n", _("How to handle redirected pages. sticky is like follow but stick to the")); printf (" %s\n", _("specified IP address. stickyport also ensures port stays the same.")); printf (" %s\n", "-m, --pagesize=INTEGER<:INTEGER>"); - printf (" %s\n", _("Minimum page size required (bytes) : Maximum page size required (bytes)")); + printf (" %s\n", _("Minimum content size required (bytes) : Maximum content size required (bytes)")); printf (UT_WARN_CRIT); @@ -1361,7 +1308,7 @@ print_usage (void) printf (" [-w ] [-c ] [-t ] [-L] [-a auth]\n"); printf (" [-b proxy_auth] [-f ]\n"); printf (" [-e ] [-s string] [-l] [-r | -R ]\n"); - printf (" [-P string] [-m :] [-4|-6] [-N] [-M ]\n"); + printf (" [-P string] [-m :] [-4|-6] [-N] [-M ]\n"); printf (" [-A string] [-k string] [-S] [--sni] [-C ] [-T ]\n"); printf (" [-j method]\n"); } -- 1.7.2.5