diff --git a/.github/NPTest.cache b/.github/NPTest.cache index 6b463e74..3f6d4da6 100644 --- a/.github/NPTest.cache +++ b/.github/NPTest.cache @@ -19,6 +19,13 @@ 'NP_HOST_TCP_HPJD' => '', 'NP_HOST_TCP_HTTP2' => 'test.monitoring-plugins.org', 'NP_HOST_TCP_HTTP' => 'localhost', + 'NP_HOST_TCP_HTTP_IPV4' => '127.0.0.1', + 'NP_HOST_TCP_HTTP_IPV4_CIDR_1' => '127.0.0.0/28', + 'NP_HOST_TCP_HTTP_IPV4_CIDR_2' => '127.0.0.1/32', + 'NP_HOST_TCP_HTTP_IPV6' => '::1', + 'NP_HOST_TCP_HTTP_IPV6_CIDR_1' => '0000:0000:0000::0000:0000:0000/16', + 'NP_HOST_TCP_HTTP_IPV6_CIDR_2' => '::1234:5678/16', + 'NP_HOST_TCP_HTTP_SUBDOMAIN' => 'subdomain1.localhost', 'NP_HOST_TCP_IMAP' => 'imap.web.de', 'NP_HOST_TCP_JABBER' => 'jabber.org', 'NP_HOST_TCP_LDAP' => 'localhost', diff --git a/.github/prepare_debian.sh b/.github/prepare_debian.sh index cffe98c5..15c2286c 100755 --- a/.github/prepare_debian.sh +++ b/.github/prepare_debian.sh @@ -67,10 +67,10 @@ apt-get -y install perl \ libjson-perl # remove ipv6 interface from hosts -sed '/^::1/d' /etc/hosts > /tmp/hosts -cp -f /tmp/hosts /etc/hosts -ip addr show -cat /etc/hosts +# sed '/^::1/d' /etc/hosts > /tmp/hosts +# cp -f /tmp/hosts /etc/hosts +# ip addr show +# cat /etc/hosts # apache a2enmod ssl @@ -80,6 +80,19 @@ a2ensite default-ssl rm /etc/ssl/certs/ssl-cert-snakeoil.pem rm /etc/ssl/private/ssl-cert-snakeoil.key openssl req -nodes -newkey rsa:2048 -x509 -sha256 -days 365 -nodes -keyout /etc/ssl/private/ssl-cert-snakeoil.key -out /etc/ssl/certs/ssl-cert-snakeoil.pem -subj "/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=$(hostname)" +# add a subdomain for testing +cp tools/subdomain1/subdomain1.conf /etc/apache2/sites-available/ +mkdir -p /var/www/subdomain1 +cp tools/subdomain1/index.php /var/www/subdomain1/ +echo '127.0.0.1 subdomain1.localhost' >> /etc/hosts +echo '127.0.0.1 subdomain1.localhost.com' >> /etc/hosts +apache2ctl configtest +a2ensite subdomain1.conf + +# Make it listen to both IPv4 on IPv6 on localhost +sed -i 's/^Listen 80/Listen 0.0.0.0:80\nListen [::1]:80/' /etc/apache2/ports.conf +sed -i 's/^[[:space:]]*Listen 443/Listen 0.0.0.0:443\nListen [::1]:443/' /etc/apache2/ports.conf + service apache2 restart # squid diff --git a/plugins/check_curl.c b/plugins/check_curl.c index d7d68de5..4a1fb647 100644 --- a/plugins/check_curl.c +++ b/plugins/check_curl.c @@ -874,7 +874,8 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { COOKIE_JAR, HAPROXY_PROTOCOL, STATE_REGEX, - OUTPUT_FORMAT + OUTPUT_FORMAT, + NO_PROXY, }; static struct option longopts[] = { @@ -889,6 +890,8 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { {"url", required_argument, 0, 'u'}, {"port", required_argument, 0, 'p'}, {"authorization", required_argument, 0, 'a'}, + {"proxy", required_argument, 0, 'x'}, + {"noproxy", required_argument, 0, NO_PROXY}, {"proxy-authorization", required_argument, 0, 'b'}, {"header-string", required_argument, 0, 'd'}, {"string", required_argument, 0, 's'}, @@ -961,7 +964,7 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { while (true) { int option_index = 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:DnlLS::m:M:NEB", + argc, argv, "Vvh46t:c:w:A:k:H:P:j:T:I:a:x:b:d:e:p:s:R:r:u:f:C:J:K:DnlLS::m:M:NEB", longopts, &option); if (option_index == -1 || option_index == EOF || option_index == 1) { break; @@ -1049,6 +1052,10 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { strncpy(result.config.curl_config.user_auth, optarg, MAX_INPUT_BUFFER - 1); result.config.curl_config.user_auth[MAX_INPUT_BUFFER - 1] = 0; break; + case 'x': /* proxy info */ + strncpy(result.config.curl_config.proxy, optarg, DEFAULT_BUFFER_SIZE - 1); + result.config.curl_config.proxy[DEFAULT_BUFFER_SIZE - 1] = 0; + break; case 'b': /* proxy-authorization info */ strncpy(result.config.curl_config.proxy_auth, optarg, MAX_INPUT_BUFFER - 1); result.config.curl_config.proxy_auth[MAX_INPUT_BUFFER - 1] = 0; @@ -1344,6 +1351,10 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { case HAPROXY_PROTOCOL: result.config.curl_config.haproxy_protocol = true; break; + case NO_PROXY: + strncpy(result.config.curl_config.no_proxy, optarg, DEFAULT_BUFFER_SIZE - 1); + result.config.curl_config.no_proxy[DEFAULT_BUFFER_SIZE - 1] = 0; + break; case '?': /* print short usage statement if args not parsable */ usage5(); @@ -1371,35 +1382,35 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { * parameters, like -S and -C combinations */ result.config.curl_config.ssl_version = CURL_SSLVERSION_DEFAULT; if (tls_option_optarg != NULL) { - char *plus_ptr = strchr(optarg, '+'); + char *plus_ptr = strchr(tls_option_optarg, '+'); if (plus_ptr) { got_plus = true; *plus_ptr = '\0'; } - if (optarg[0] == '2') { + if (tls_option_optarg[0] == '2') { result.config.curl_config.ssl_version = CURL_SSLVERSION_SSLv2; - } else if (optarg[0] == '3') { + } else if (tls_option_optarg[0] == '3') { result.config.curl_config.ssl_version = CURL_SSLVERSION_SSLv3; - } else if (!strcmp(optarg, "1") || !strcmp(optarg, "1.0")) { + } else if (!strcmp(tls_option_optarg, "1") || !strcmp(tls_option_optarg, "1.0")) { #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) result.config.curl_config.ssl_version = CURL_SSLVERSION_TLSv1_0; #else result.config.ssl_version = CURL_SSLVERSION_DEFAULT; #endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */ - } else if (!strcmp(optarg, "1.1")) { + } else if (!strcmp(tls_option_optarg, "1.1")) { #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) result.config.curl_config.ssl_version = CURL_SSLVERSION_TLSv1_1; #else result.config.ssl_version = CURL_SSLVERSION_DEFAULT; #endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */ - } else if (!strcmp(optarg, "1.2")) { + } else if (!strcmp(tls_option_optarg, "1.2")) { #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) result.config.curl_config.ssl_version = CURL_SSLVERSION_TLSv1_2; #else result.config.ssl_version = CURL_SSLVERSION_DEFAULT; #endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */ - } else if (!strcmp(optarg, "1.3")) { + } else if (!strcmp(tls_option_optarg, "1.3")) { #if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 52, 0) result.config.curl_config.ssl_version = CURL_SSLVERSION_TLSv1_3; #else @@ -1522,8 +1533,8 @@ void print_help(void) { printf(" %s\n", "-I, --IP-address=ADDRESS"); printf(" %s\n", "IP address or name (use numeric address if possible to bypass DNS lookup)."); - printf(" %s\n", "This overwrites the network address of the target while leaving everything " - "else (HTTP headers) as they are"); + printf(" %s\n", + "This overwrites the network address of the target while leaving everything else (HTTP headers) as they are"); printf(" %s\n", "-p, --port=INTEGER"); printf(" %s", _("Port number (default: ")); printf("%d)\n", HTTP_PORT); @@ -1587,8 +1598,7 @@ void print_help(void) { printf(" %s\n", _("String to expect in the content")); printf(" %s\n", "-u, --url=PATH"); printf(" %s\n", _("URL to GET or POST (default: /)")); - printf(" %s\n", _("This is the part after the address in a URL, so for " - "\"https://example.com/index.html\" it would be '-u /index.html'")); + printf(" %s\n", _("This is the part after the address in a URL, so for \"https://example.com/index.html\" it would be '-u /index.html'")); printf(" %s\n", "-P, --post=STRING"); printf(" %s\n", _("URL decoded http POST data")); printf(" %s\n", @@ -1614,6 +1624,18 @@ void print_help(void) { printf(" %s\n", "--state-regex=STATE"); printf(" %s\n", _("Return STATE if regex is found, OK if not. STATE can be one of " "\"critical\",\"warning\"")); + printf(" %s\n", "-x, --proxy=PROXY_SERVER"); + printf(" %s\n", _("Specify the proxy in form of ://:")); + printf(" %s\n", _("Available schemes are http, https, socks4, socks4a, socks5, socks5h")); + printf(" %s\n", _("If port is not specified, libcurl defaults to 1080")); + printf(" %s\n", _("This value will be set as CURLOPT_PROXY")); + printf(" %s\n", "--noproxy=COMMA_SEPARATED_LIST"); + printf(" %s\n", _("Specify hostnames, addresses and subnets where proxy should not be used")); + printf(" %s\n", _("Example usage: \"example.com,::1,1.1.1.1,localhost,192.168.0.0/16\"")); + printf(" %s\n", _("Do not use brackets when specifying IPv6 addresses")); + printf(" %s\n", _("Special case when an item is '*' : matches all hosts/addresses " + "and effectively disables proxy.")); + printf(" %s\n", _("This value will be set as CURLOPT_NOPROXY")); printf(" %s\n", "-a, --authorization=AUTH_PAIR"); printf(" %s\n", _("Username:password on sites with basic authentication")); printf(" %s\n", "-b, --proxy-authorization=AUTH_PAIR"); @@ -1722,10 +1744,39 @@ void print_help(void) { #endif printf("\n %s\n", "CHECK WEBSERVER CONTENT VIA PROXY:"); - printf(" %s\n", _("It is recommended to use an environment proxy like:")); - printf(" %s\n", - _("http_proxy=http://192.168.100.35:3128 ./check_curl -H www.monitoring-plugins.org")); - printf(" %s\n", _("legacy proxy requests in check_http style still work:")); + printf(" %s\n", _("Proxies are specified or disabled for certain hosts/addresses using environment variables" + " or -x/--proxy and --noproxy arguments:")); + printf(" %s\n", _("Checked environment variables: all_proxy, http_proxy, https_proxy, no_proxy")); + printf(" %s\n", _("Environment variables can also be given in uppercase, but the lowercase ones will " + "take predence if both are defined.")); + printf(" %s\n", _("The environment variables are overwritten by -x/--proxy and --noproxy arguments:")); + printf(" %s\n", _("all_proxy/ALL_PROXY environment variables are read first, but protocol " + "specific environment variables override them.")); + printf(" %s\n", _("If SSL is enabled and used, https_proxy/HTTPS_PROXY will be checked and overwrite " + "http_proxy/HTTPS_PROXY.")); + printf(" %s\n", _("Curl accepts proxies using http, https, socks4, socks4a, socks5 and socks5h schemes.")); + printf(" %s\n", _("http_proxy=http://192.168.100.35:3128 ./check_curl -H www.monitoring-plugins.org")); + printf(" %s\n", _("http_proxy=http://used.proxy.com HTTP_PROXY=http://ignored.proxy.com ./check_curl -H www.monitoring-plugins.org")); + printf(" %s\n", _(" Lowercase http_proxy takes predence over uppercase HTTP_PROXY")); + printf(" %s\n", _("./check_curl -H www.monitoring-plugins.org -x http://192.168.100.35:3128")); + printf(" %s\n", _("http_proxy=http://unused.proxy1.com HTTP_PROXY=http://unused.proxy2.com ./check_curl " + "-H www.monitoring-plugins.org --proxy http://used.proxy")); + printf(" %s\n", _(" Proxy specified by --proxy overrides any proxy specified by environment variable.")); + printf(" %s\n", _(" Curl uses port 1080 by default as port is not specified")); + printf(" %s\n", _("HTTPS_PROXY=http://192.168.100.35:3128 ./check_curl -H www.monitoring-plugins.org --ssl")); + printf(" %s\n", _(" HTTPS_PROXY is read as --ssl is toggled")); + printf(" %s\n", _("./check_curl -H www.monitoring-plugins.org --proxy socks5h://192.168.122.21")); + printf(" %s\n", _("./check_curl -H www.monitoring-plugins.org -x http://unused.proxy.com --noproxy '*'")); + printf(" %s\n", _(" Disabled proxy for all hosts by using '*' in no_proxy .")); + printf(" %s\n", _("NO_PROXY=www.monitoring-plugins.org ./check_curl -H www.monitoring-plugins.org -x http://unused.proxy.com")); + printf(" %s\n", _(" Exact matches with the hostname/address work.")); + printf(" %s\n", _("no_proxy=192.168.178.0/24 ./check_curl -I 192.168.178.10 -x http://proxy.acme.org")); + printf(" %s\n", _("no_proxy=acme.org ./check_curl -H nonpublic.internalwebapp.acme.org -x http://proxy.acme.org")); + printf(" %s\n", _(" Do not use proxy when accessing internal domains/addresses, but use a default proxy when accessing public web.")); + printf(" %s\n", _(" IMPORTANT: Check_curl can not always determine whether itself or the proxy will " + "resolve a hostname before sending a request and getting an answer." + "This can lead to DNS resolvation issues if hostname is only resolvable over proxy.")); + printf(" %s\n", _("Legacy proxy requests in check_http style still work:")); printf(" %s\n", _("check_curl -I 192.168.100.35 -p 3128 -u http://www.monitoring-plugins.org/ " "-H www.monitoring-plugins.org")); @@ -1756,13 +1807,15 @@ void print_usage(void) { printf(" %s -H | -I [-u ] [-p ]\n", progname); printf(" [-J ] [-K ] [--ca-cert ] [-D]\n"); - printf(" [-w ] [-c ] [-t ] [-L] [-E] [-a auth]\n"); - printf(" [-b proxy_auth] [-f ]\n"); + printf(" [-w ] [-c ] [-t ] [-L] [-E] [-x ]\n"); + printf(" [-a auth] [-b proxy_auth] [-f " + "]\n"); printf(" [-e ] [-d string] [-s string] [-l] [-r | -R ]\n"); printf(" [-P string] [-m :] [-4|-6] [-N] [-M ]\n"); printf(" [-A string] [-k string] [-S ] [--sni] [--haproxy-protocol]\n"); printf(" [-T ] [-j method]\n"); + printf(" [--noproxy=\n"); printf(" [--http-version=] [--enable-automatic-decompression]\n"); printf(" [--cookie-jar=\n"); printf(" %s -H | -I -C [,]\n", progname); diff --git a/plugins/check_curl.d/check_curl_helpers.c b/plugins/check_curl.d/check_curl_helpers.c index ad31b847..4372dc0b 100644 --- a/plugins/check_curl.d/check_curl_helpers.c +++ b/plugins/check_curl.d/check_curl_helpers.c @@ -3,8 +3,11 @@ #include #include #include +#include +#include #include #include +#include #include "../utils.h" #include "check_curl.d/config.h" #include "output.h" @@ -116,6 +119,107 @@ check_curl_configure_curl(const check_curl_static_curl_config config, curl_easy_setopt(result.curl_state.curl, CURLOPT_TIMEOUT, config.socket_timeout), "CURLOPT_TIMEOUT"); + /* set proxy */ + /* http(s) proxy can either be given from the command line, or taken from environment variables */ + /* socks4(a) / socks5(h) proxy should be given using the command line */ + + /* first source to check is the environment variables */ + /* lower case proxy environment variables are almost always accepted, while some programs also checking + uppercase ones. discover both, but take the lowercase one if both are present */ + + /* extra information: libcurl does not discover the uppercase version HTTP_PROXY due to security reasons */ + /* https://github.com/curl/curl/blob/d445f2d930ae701039518d695481ee53b8490521/lib/url.c#L1987 */ + + /* first environment variable to read is all_proxy. it can be overridden by protocol specific environment variables */ + char *all_proxy_env, *all_proxy_uppercase_env; + all_proxy_env = getenv("all_proxy"); + all_proxy_uppercase_env = getenv("ALL_PROXY"); + if (all_proxy_env != NULL && strlen(all_proxy_env)){ + working_state.curlopt_proxy = strdup(all_proxy_env); + if (all_proxy_uppercase_env != NULL && verbose >= 1) { + printf("* cURL ignoring environment variable 'ALL_PROXY' as 'all_proxy' is set\n"); + } + } else if (all_proxy_uppercase_env != NULL && strlen(all_proxy_uppercase_env) > 0) { + working_state.curlopt_proxy = strdup(all_proxy_uppercase_env); + } + + /* second environment variable to read is http_proxy. only set curlopt_proxy if ssl is not toggled */ + char *http_proxy_env, *http_proxy_uppercase_env; + http_proxy_env = getenv("http_proxy"); + http_proxy_uppercase_env = getenv("HTTP_PROXY"); + if (!working_state.use_ssl){ + if (http_proxy_env != NULL && strlen(http_proxy_env) > 0) { + working_state.curlopt_proxy = strdup(http_proxy_env); + if (http_proxy_uppercase_env != NULL && verbose >= 1) { + printf("* cURL ignoring environment variable 'HTTP_PROXY' as 'http_proxy' is set\n"); + } + } else if (http_proxy_uppercase_env != NULL && strlen(http_proxy_uppercase_env) > 0) { + working_state.curlopt_proxy = strdup(http_proxy_uppercase_env); + } + } +#ifdef LIBCURL_FEATURE_SSL + /* optionally read https_proxy environment variable and set curlopt_proxy if ssl is toggled */ + char *https_proxy_env, *https_proxy_uppercase_env; + https_proxy_env = getenv("https_proxy"); + https_proxy_uppercase_env = getenv("HTTPS_PROXY"); + if (working_state.use_ssl) { + if (https_proxy_env != NULL && strlen(https_proxy_env) > 0) { + working_state.curlopt_proxy = strdup(https_proxy_env); + if (https_proxy_uppercase_env != NULL && verbose >= 1) { + printf("* cURL ignoring environment variable 'HTTPS_PROXY' as 'https_proxy' is set\n"); + } + } + else if (https_proxy_uppercase_env != NULL && strlen(https_proxy_uppercase_env) >= 0) { + working_state.curlopt_proxy = strdup(https_proxy_uppercase_env); + } + } +#endif /* LIBCURL_FEATURE_SSL */ + + /* second source to check for proxies is command line argument, overwriting the environment variables */ + if (strlen(config.proxy) > 0) { + working_state.curlopt_proxy = strdup(config.proxy); + } + + if (working_state.curlopt_proxy != NULL && strlen(working_state.curlopt_proxy)){ + handle_curl_option_return_code( + curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXY, working_state.curlopt_proxy), "CURLOPT_PROXY"); + if (verbose >= 1) { + printf("* curl CURLOPT_PROXY: %s\n", working_state.curlopt_proxy); + } + } + + /* set no_proxy */ + /* first source to check is environment variables */ + char *no_proxy_env, *no_proxy_uppercase_env; + no_proxy_env = getenv("no_proxy"); + no_proxy_uppercase_env = getenv("NO_PROXY"); + if (no_proxy_env != NULL && strlen(no_proxy_env)){ + working_state.curlopt_noproxy = strdup(no_proxy_env); + if (no_proxy_uppercase_env != NULL && verbose >= 1){ + printf("* cURL ignoring environment variable 'NO_PROXY' as 'no_proxy' is set\n"); + } + }else if (no_proxy_uppercase_env != NULL && strlen(no_proxy_uppercase_env) > 0){ + working_state.curlopt_noproxy = strdup(no_proxy_uppercase_env); + } + + /* second source to check for no_proxy is command line argument, overwriting the environment variables */ + if (strlen(config.no_proxy) > 0) { + working_state.curlopt_noproxy = strdup(config.no_proxy); + } + + if ( working_state.curlopt_noproxy != NULL && strlen(working_state.curlopt_noproxy)){ + handle_curl_option_return_code( + curl_easy_setopt(result.curl_state.curl, CURLOPT_NOPROXY, working_state.curlopt_noproxy), "CURLOPT_NOPROXY"); + if (verbose >= 1) { + printf("* curl CURLOPT_NOPROXY: %s\n", working_state.curlopt_noproxy); + } + } + + int proxy_resolves_hostname = determine_hostname_resolver(working_state, config); + if (verbose >= 1) { + printf("* proxy_resolves_hostname: %d\n", proxy_resolves_hostname); + } + /* enable haproxy protocol */ if (config.haproxy_protocol) { handle_curl_option_return_code( @@ -123,11 +227,11 @@ check_curl_configure_curl(const check_curl_static_curl_config config, "CURLOPT_HAPROXYPROTOCOL"); } - // fill dns resolve cache to make curl connect to the given server_address instead of the - // host_name, only required for ssl, because we use the host_name later on to make SNI happy + /* fill dns resolve cache to make curl connect to the given server_address instead of the */ + /* host_name, only required for ssl, because we use the host_name later on to make SNI happy */ char dnscache[DEFAULT_BUFFER_SIZE]; char addrstr[DEFAULT_BUFFER_SIZE / 2]; - if (working_state.use_ssl && working_state.host_name != NULL) { + if (working_state.use_ssl && working_state.host_name != NULL && !proxy_resolves_hostname ) { char *tmp_mod_address; /* lookup_host() requires an IPv6 address without the brackets. */ @@ -562,7 +666,7 @@ check_curl_configure_curl(const check_curl_static_curl_config config, void handle_curl_option_return_code(CURLcode res, const char *option) { if (res != CURLE_OK) { - die(STATE_CRITICAL, _("Error while setting cURL option '%s': cURL returned %d - %s"), + die(STATE_CRITICAL, _("Error while setting cURL option '%s': cURL returned %d - %s\n"), option, res, curl_easy_strerror(res)); } } @@ -589,6 +693,8 @@ check_curl_working_state check_curl_working_state_init() { .serverPort = HTTP_PORT, .use_ssl = false, .no_body = false, + .curlopt_proxy = NULL, + .curlopt_noproxy = NULL, }; return result; } @@ -612,6 +718,8 @@ check_curl_config check_curl_config_init() { .ca_cert = NULL, .verify_peer_and_host = false, .user_agent = {'\0'}, + .proxy = "", + .no_proxy = "", .proxy_auth = "", .user_auth = "", .http_content_type = NULL, @@ -1295,3 +1403,342 @@ char *fmt_url(check_curl_working_state workingState) { return url; } + +int determine_hostname_resolver(const check_curl_working_state working_state, const check_curl_static_curl_config config){ + char *host_name_display = "NULL"; + unsigned long host_name_len = 0; + if( working_state.host_name){ + host_name_len = strlen(working_state.host_name); + host_name_display = working_state.host_name; + } + + /* IPv4 or IPv6 version of the address */ + char *server_address_clean = strdup(working_state.server_address); + /* server address might be a full length ipv6 address encapsulated in square brackets */ + if ((strnlen(working_state.server_address, MAX_IPV4_HOSTLENGTH) > 2) && (working_state.server_address[0] == '[') && (working_state.server_address[strlen(working_state.server_address)-1] == ']') ) { + server_address_clean = strndup( working_state.server_address + 1, strlen(working_state.server_address) - 2); + } + + /* check curlopt_noproxy option first */ + /* https://curl.se/libcurl/c/CURLOPT_NOPROXY.html */ + + /* curlopt_noproxy is specified as a comma separated list of + direct IPv4 or IPv6 addresses e.g 130.133.8.40, 2001:4860:4802:32::a , + IPv4 or IPv6 CIDR regions e.g 10.241.0.0/16 , abcd:ef01:2345::/48 , + direct hostnames e.g example.com, google.de */ + + if (working_state.curlopt_noproxy != NULL){ + char* curlopt_noproxy_copy = strdup( working_state.curlopt_noproxy); + char* noproxy_item = strtok(curlopt_noproxy_copy, ","); + while(noproxy_item != NULL){ + unsigned long noproxy_item_len = strlen(noproxy_item); + + /* According to the CURLOPT_NOPROXY documentation: */ + /* https://curl.se/libcurl/c/CURLOPT_NOPROXY.html */ + /* The only wildcard available is a single * character, which matches all hosts, and effectively disables the proxy. */ + if ( strlen(noproxy_item) == 1 && noproxy_item[0] == '*'){ + if (verbose >= 1){ + printf("* noproxy includes '*' which disables proxy for all host name incl. : %s / server address incl. : %s\n", host_name_display , server_address_clean); + } + free(curlopt_noproxy_copy); + free(server_address_clean); + return 0; + } + + /* direct comparison with the server_address */ + if( server_address_clean != NULL && strlen(server_address_clean) == strlen(noproxy_item) && strcmp(server_address_clean, noproxy_item) == 0){ + if (verbose >= 1){ + printf("* server_address is in the no_proxy list: %s\n", noproxy_item); + } + free(curlopt_noproxy_copy); + free(server_address_clean); + return 0; + } + + /* direct comparison with the host_name */ + if( working_state.host_name != NULL && host_name_len == noproxy_item_len && strcmp(working_state.host_name, noproxy_item) == 0){ + if (verbose >= 1){ + printf("* host_name is in the no_proxy list: %s\n", noproxy_item); + } + free(curlopt_noproxy_copy); + free(server_address_clean); + return 0; + } + + /* check if hostname is a subdomain of the item, e.g www.example.com when token is example.com */ + /* subdomain1.acme.com will not will use a proxy if you only specify 'acme' in the noproxy */ + /* check if noproxy_item is a suffix */ + /* check if the character just before the suffix is '.' */ + if( working_state.host_name != NULL && host_name_len > noproxy_item_len){ + unsigned long suffix_start_idx = host_name_len - noproxy_item_len; + if (strcmp(working_state.host_name + suffix_start_idx, noproxy_item ) == 0 && working_state.host_name[suffix_start_idx-1] == '.' ){ + if (verbose >= 1){ + printf("* host_name: %s is a subdomain of the no_proxy list item: %s\n", working_state.host_name , noproxy_item); + } + free(curlopt_noproxy_copy); + free(server_address_clean); + return 0; + } + } + + // noproxy_item could be a CIDR IP range + if( server_address_clean != NULL && strlen(server_address_clean)){ + + int ip_addr_inside_cidr_ret = ip_addr_inside_cidr(noproxy_item, server_address_clean); + + switch(ip_addr_inside_cidr_ret){ + case 1: + return 0; + break; + case 0: + if(verbose >= 1){ + printf("server address: %s is not inside IP cidr: %s\n", server_address_clean, noproxy_item); + } + break; + case -1: + if(verbose >= 1){ + printf("could not fully determine if server address: %s is inside the IP cidr: %s\n", server_address_clean, noproxy_item); + } + break; + } + } + + noproxy_item = strtok(NULL, ","); + } + + free(curlopt_noproxy_copy); + } + + if (working_state.curlopt_proxy != NULL){ + // Libcurl documentation + // Setting the proxy string to "" (an empty string) explicitly disables the use of a proxy, even if there is an environment variable set for it. + if ( strlen(working_state.curlopt_proxy) == 0){ + return 0; + } + + if ( strncmp( working_state.curlopt_proxy, "http://", 7) == 0){ + if (verbose >= 1){ + printf("* proxy scheme is http, proxy: %s resolves host: %s or server_address: %s\n", working_state.curlopt_proxy, host_name_display, server_address_clean); + } + free(server_address_clean); + return 1; + } + + if ( strncmp( working_state.curlopt_proxy, "https://", 8) == 0){ + if (verbose >= 1){ + printf("* proxy scheme is https, proxy: %s resolves host: %s or server_address: %s\n", working_state.curlopt_proxy, host_name_display, server_address_clean); + } + free(server_address_clean); + return 1; + } + + if ( strncmp( working_state.curlopt_proxy, "socks4://", 9) == 0){ + if (verbose >= 1){ + printf("* proxy scheme is socks, proxy: %s does not resolve host: %s or server_address: %s\n", working_state.curlopt_proxy, host_name_display, server_address_clean); + } + free(server_address_clean); + return 0; + } + + if ( strncmp( working_state.curlopt_proxy, "socks4a://", 10) == 0){ + if (verbose >= 1){ + printf("* proxy scheme is socks4a, proxy: %s resolves host: %s or server_address: %s\n", working_state.curlopt_proxy, host_name_display, server_address_clean); + } + free(server_address_clean); + return 1; + } + + if ( strncmp( working_state.curlopt_proxy, "socks5://", 9) == 0){ + if (verbose >= 1){ + printf("* proxy scheme is socks5, proxy: %s does not resolve host: %s or server_address: %s\n", working_state.curlopt_proxy, host_name_display, server_address_clean); + } + free(server_address_clean); + return 0; + } + + if ( strncmp( working_state.curlopt_proxy, "socks5h://", 10) == 0){ + if (verbose >= 1){ + printf("* proxy scheme is socks5h, proxy: %s resolves host: %s or server_address: %s\n", working_state.curlopt_proxy, host_name_display, server_address_clean); + } + free(server_address_clean); + return 1; + } + + // Libcurl documentation: + // Without a scheme prefix, CURLOPT_PROXYTYPE can be used to specify which kind of proxy the string identifies. + // We do not set this value + // Without a scheme, it is treated as an http proxy + + return 1; + } + + if (verbose >= 1){ + printf("* proxy scheme is unknown/unavailable, no proxy is assumed for host: %s or server_address: %s\n", host_name_display, server_address_clean); + } + + free(server_address_clean); + return 0; +} + +int ip_addr_inside_cidr(const char* cidr_region_or_ip_addr, const char* target_ip){ + unsigned int slash_count = 0; + unsigned int last_slash_idx = 0; + for(size_t i = 0; i < strlen(cidr_region_or_ip_addr); i++){ + if(cidr_region_or_ip_addr[i] == '/'){ + slash_count++; + last_slash_idx = (unsigned int)i; + } + } + + char *cidr_ip_part = NULL; + int prefix_length = 0; + + if (slash_count == 0) { + cidr_ip_part = strdup(cidr_region_or_ip_addr); + if (!cidr_ip_part) return -1; + } else if (slash_count == 1) { + cidr_ip_part = strndup(cidr_region_or_ip_addr, last_slash_idx); + if (!cidr_ip_part) return -1; + + errno = 0; + long long tmp = strtoll(cidr_region_or_ip_addr + last_slash_idx + 1, NULL, 10); + if (errno == ERANGE) { + if (verbose >= 1) { + printf("cidr_region_or_ip: %s , could not parse subnet length\n", cidr_region_or_ip_addr); + } + free(cidr_ip_part); + return -1; + } + prefix_length = (int)tmp; + } else { + printf("cidr_region_or_ip: %s , has %d number of '/' characters, is not a valid cidr_region or IP\n", cidr_region_or_ip_addr, slash_count); + return -1; + } + + int cidr_addr_family, target_addr_family; + if (strchr(cidr_ip_part, ':')){ + cidr_addr_family = AF_INET6; + } else { + cidr_addr_family = AF_INET; + } + + if (strchr(target_ip, ':')){ + target_addr_family = AF_INET6; + } else { + target_addr_family = AF_INET; + } + + if (cidr_addr_family != target_addr_family){ + if (verbose >= 1){ + printf("cidr address: %s and target ip address: %s have different address families\n", cidr_ip_part, target_ip); + } + free(cidr_ip_part); + return 0; + } + + // If no prefix is given, treat the cidr as a single address (full-length prefix) + if (slash_count == 0) { + prefix_length = (cidr_addr_family == AF_INET) ? 32 : 128; + } + + int max_bits = (cidr_addr_family == AF_INET) ? 32u : 128u; + if (prefix_length < 0 || prefix_length > max_bits) { + if (verbose >= 1) { + printf("cidr_region_or_ip: %s has invalid prefix length: %u\n", cidr_region_or_ip_addr, prefix_length); + } + free(cidr_ip_part); + return -1; + } + + if (verbose >= 1){ + printf("cidr_region_or_ip: %s , has prefix length: %u\n", cidr_region_or_ip_addr, prefix_length); + } + + int inet_pton_rc; + uint8_t *cidr_bytes = NULL; + uint8_t *target_bytes = NULL; + uint8_t cidr_buf[16]; + uint8_t target_buf[16]; + size_t total_bytes = 0; + + if (cidr_addr_family == AF_INET) { + struct in_addr cidr_ipv4; + struct in_addr target_ipv4; + inet_pton_rc = inet_pton(AF_INET, cidr_ip_part, &cidr_ipv4); + if (inet_pton_rc != 1) { + if (verbose >= 1) { + printf("ip string: %s contains characters not valid for its address family: IPv4\n", cidr_ip_part); + } + free(cidr_ip_part); + return -1; + } + inet_pton_rc = inet_pton(AF_INET, target_ip, &target_ipv4); + if (inet_pton_rc != 1) { + if (verbose >= 1) { + printf("ip string: %s contains characters not valid for its address family: IPv4\n", target_ip); + } + free(cidr_ip_part); + return -1; + } + // copy the addresses in network byte order to a buffer for comparison + memcpy(cidr_buf, &cidr_ipv4.s_addr, 4); + memcpy(target_buf, &target_ipv4.s_addr, 4); + cidr_bytes = cidr_buf; + target_bytes = target_buf; + total_bytes = 4; + } else { + struct in6_addr cidr_ipv6; + struct in6_addr target_ipv6; + inet_pton_rc = inet_pton(AF_INET6, cidr_ip_part, &cidr_ipv6); + if (inet_pton_rc != 1) { + if (verbose >= 1) { + printf("ip string: %s contains characters not valid for its address family: IPv6\n", cidr_ip_part); + } + free(cidr_ip_part); + return -1; + } + inet_pton_rc = inet_pton(AF_INET6, target_ip, &target_ipv6); + if (inet_pton_rc != 1) { + if (verbose >= 1) { + printf("ip string: %s contains characters not valid for its address family: IPv6\n", target_ip); + } + free(cidr_ip_part); + return -1; + } + memcpy(cidr_buf, &cidr_ipv6, 16); + memcpy(target_buf, &target_ipv6, 16); + cidr_bytes = cidr_buf; + target_bytes = target_buf; + total_bytes = 16; + } + + int prefix_bytes = prefix_length / 8; + int prefix_bits = prefix_length % 8; + + if (prefix_bytes > 0) { + if (memcmp(cidr_bytes, target_bytes, (size_t)prefix_bytes) != 0) { + if (verbose >= 1) { + printf("the first %d bytes of the cidr_region_or_ip: %s and target_ip: %s are different\n", prefix_bytes, cidr_ip_part, target_ip); + } + free(cidr_ip_part); + return 0; + } + } + + if (prefix_bits != 0) { + uint8_t cidr_oct = cidr_bytes[prefix_bytes]; + uint8_t target_oct = target_bytes[prefix_bytes]; + // the mask has first prefix_bits bits 1, the rest as 0 + uint8_t mask = (uint8_t)(0xFFu << (8 - prefix_bits)); + if ((cidr_oct & mask) != (target_oct & mask)) { + if (verbose >= 1) { + printf("looking at the last %d bits of the prefix, cidr_region_or_ip(%s) byte is: %u and target_ip byte(%s) is: %u, applying bitmask: %02X returns different results\n", prefix_bits, cidr_ip_part, (unsigned)cidr_oct, target_ip, (unsigned)target_oct, mask); + } + free(cidr_ip_part); + return 0; + } + } + + free(cidr_ip_part); + return 1; +} diff --git a/plugins/check_curl.d/check_curl_helpers.h b/plugins/check_curl.d/check_curl_helpers.h index e77b763b..cc47bf9d 100644 --- a/plugins/check_curl.d/check_curl_helpers.h +++ b/plugins/check_curl.d/check_curl_helpers.h @@ -126,3 +126,12 @@ void test_file(char *path); mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_till_exp, int crit_days_till_exp); char *fmt_url(check_curl_working_state workingState); + + +/* function that will determine if the host or the proxy resolves the target hostname +returns 0 if requester resolves the hostname locally, 1 if proxy resolves the hostname */ +int determine_hostname_resolver(const check_curl_working_state working_state, const check_curl_static_curl_config config); + +/* Checks if an IP is inside given CIDR region. Using /protocol_size or not specifying the prefix length performs an equality check. Supports both IPv4 and IPv6 +returns 1 if the target_ip address is inside the given cidr_region_or_ip_addr, 0 if its out. return codes < 0 mean an error has occurred. */ +int ip_addr_inside_cidr(const char* cidr_region_or_ip_addr, const char* target_ip); diff --git a/plugins/check_curl.d/config.h b/plugins/check_curl.d/config.h index 61067d46..bcdf3010 100644 --- a/plugins/check_curl.d/config.h +++ b/plugins/check_curl.d/config.h @@ -48,6 +48,11 @@ typedef struct { bool use_ssl; bool no_body; + + /* curl CURLOPT_PROXY option will be set to this value if not NULL */ + char *curlopt_proxy; + /* curl CURLOPT_NOPROXY option will be set to this value if not NULL */ + char *curlopt_noproxy; } check_curl_working_state; check_curl_working_state check_curl_working_state_init(); @@ -65,6 +70,8 @@ typedef struct { char *client_privkey; char *ca_cert; bool verify_peer_and_host; + char proxy[DEFAULT_BUFFER_SIZE]; + char no_proxy[DEFAULT_BUFFER_SIZE]; char user_agent[DEFAULT_BUFFER_SIZE]; char proxy_auth[MAX_INPUT_BUFFER]; char user_auth[MAX_INPUT_BUFFER]; diff --git a/plugins/t/check_curl.t b/plugins/t/check_curl.t index 2c2fafde..a8326f12 100644 --- a/plugins/t/check_curl.t +++ b/plugins/t/check_curl.t @@ -13,7 +13,7 @@ use vars qw($tests $has_ipv6); BEGIN { use NPTest; $has_ipv6 = NPTest::has_ipv6(); - $tests = $has_ipv6 ? 55 : 53; + $tests = $has_ipv6 ? 57 : 92; plan tests => $tests; } @@ -25,7 +25,13 @@ my $plugin = 'check_http'; $plugin = 'check_curl' if $0 =~ m/check_curl/mx; my $host_tcp_http = getTestParameter("NP_HOST_TCP_HTTP", "A host providing the HTTP Service (a web server)", "localhost"); +my $host_tcp_http_subdomain = getTestParameter("NP_HOST_TCP_HTTP_SUBDOMAIN", "A host that is served under a subdomain name", "subdomain1.localhost.com"); +my $host_tcp_http_ipv4 = getTestParameter("NP_HOST_TCP_HTTP_IPV4", "An IPv6 address providing a HTTP Service (a web server)", "127.0.0.1"); +my $host_tcp_http_ipv4_cidr_1 = getTestParameter("NP_HOST_TCP_HTTP_IPV4_CIDR_1", "A CIDR that the provided IPv4 address is in."); +my $host_tcp_http_ipv4_cidr_2 = getTestParameter("NP_HOST_TCP_HTTP_IPV4_CIDR_2", "A CIDR that the provided IPv4 address is in."); my $host_tcp_http_ipv6 = getTestParameter("NP_HOST_TCP_HTTP_IPV6", "An IPv6 address providing a HTTP Service (a web server)", "::1"); +my $host_tcp_http_ipv6_cidr_1 = getTestParameter("NP_HOST_TCP_HTTP_IPV6_CIDR_1", "A CIDR that the provided IPv6 address is in."); +my $host_tcp_http_ipv6_cidr_2 = getTestParameter("NP_HOST_TCP_HTTP_IPV6_CIDR_2", "A CIDR that the provided IPv6 address is in."); my $host_tls_http = getTestParameter("NP_HOST_TLS_HTTP", "A host providing the HTTPS Service (a tls web server)", "localhost"); my $host_tls_cert = getTestParameter("NP_HOST_TLS_CERT", "the common name of the certificate.", "localhost"); my $host_nonresponsive = getTestParameter("NP_HOST_NONRESPONSIVE", "The hostname of system not responsive to network requests", "10.0.0.1"); @@ -222,3 +228,110 @@ SKIP: { $res = NPTest->testCmd( "./$plugin -H monitoring-plugins.org --extended-perfdata" ); like ( $res->output, '/\'time_connect\'=[\d\.]+/', 'Extended Performance Data Output OK' ); } +SKIP: { + skip "No internet access", 2 if $internet_access eq "no"; + + # Proxy tests + # These are the proxy tests that require a working proxy server + # The debian container in the github workflow runs a squid proxy server at port 3128 + # Test that dont require one, like argument/environment variable parsing are in plugins/tests/check_curl.t + + # Test if proxy works + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http --proxy http://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used, there are no preventative measures "); + is( $res->return_code, 0, "Using proxy http://$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http works" ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv4 --proxy http://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used, there are no preventative measures "); + is( $res->return_code, 0, "Using proxy http://$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http_ipv4 works" ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv6 --proxy http://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used, there are no preventative measures "); + is( $res->return_code, 0, "Using proxy http://$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http_ipv6 works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http2 --proxy http://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used, there are no preventative measures "); + is( $res->return_code, 0, "Using proxy http://$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http2 works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http_subdomain --proxy http://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used, there are no preventative measures "); + is( $res->return_code, 0, "Using proxy http://$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http_subdomain works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tls_http --proxy http://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used, there are no preventative measures "); + is( $res->return_code, 0, "Using proxy http://$host_tcp_proxy:$port_tcp_proxy to connect to $host_tls_http works" ); + + # Noproxy '*' should prevent using proxy in any setting, even if its specified + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http_subdomain --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy \"\*\" -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since noproxy has \"\*\" "); + is( $res->return_code, 0, "Should reach $host_tcp_http_subdomain with or without proxy." ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv4 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy \"\*\" -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since noproxy has \"\*\" "); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv4 with or without proxy." ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv6 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy \"\*\" -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since noproxy has \"\*\" "); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv6 with or without proxy." ); + + # Noproxy domain should prevent using proxy for subdomains of that domain + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http_subdomain --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy $host_tcp_http -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since subdomain: $host_tcp_http_subdomain is under a noproxy domain: $host_tcp_http"); + is( $res->return_code, 0, "Should reach $host_tcp_http_subdomain with or without proxy." ); + + # Noproxy should prevent using IP matches if an IP is found directly + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv4 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy $host_tcp_http_ipv4 -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since IP address: $host_tcp_http_ipv4 is added into noproxy: $host_tcp_http_ipv4"); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv4 with or without proxy." ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv6 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy $host_tcp_http_ipv6 -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since IP address: $host_tcp_http_ipv6 is added into noproxy: $host_tcp_http_ipv6"); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv6 with or without proxy." ); + + # Noproxy should prevent using IP matches if a CIDR region that contains that Ip is used directly. + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv4 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy $host_tcp_http_ipv4_cidr_1 -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since IP address: $host_tcp_http_ipv4 is inside CIDR range: $host_tcp_http_ipv4_cidr_1"); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv4 with or without proxy." ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv4 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy $host_tcp_http_ipv4_cidr_2 -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since IP address: $host_tcp_http_ipv4 is inside CIDR range: $host_tcp_http_ipv4_cidr_2"); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv4 with or without proxy." ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv6 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy $host_tcp_http_ipv6_cidr_1 -v " ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since IP address: $host_tcp_http_ipv6 is inside CIDR range: $host_tcp_http_ipv6_cidr_1"); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv6 with or without proxy." ); + + $res = NPTest->testCmd( "./$plugin -I $host_tcp_http_ipv6 --proxy http://$host_tcp_proxy:$port_tcp_proxy --noproxy $host_tcp_http_ipv6_cidr_2 -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used since IP address: $host_tcp_http_ipv6 is inside CIDR range: $host_tcp_http_ipv6_cidr_2"); + is( $res->return_code, 0, "Should reach $host_tcp_http_ipv6 with or without proxy." ); + + # Noproxy should discern over different types of proxy schemes + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http --proxy http://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used for resolving hostname, and is using scheme http "); + is( $res->return_code, 0, "Using proxy http:$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http --proxy https://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used for resolving hostname, and is using scheme https"); + # Squid is not configured for https + # is( $res->return_code, 0, "Using proxy https:$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http --proxy socks4://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used for resolving hostname, and is using scheme socks4"); + # Squid is not configured for socks4 + # is( $res->return_code, 0, "Using proxy socks4:$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http --proxy socks4a://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used for resolving hostname, and is using scheme socks4a"); + # Squid is not configured for socks4a + # is( $res->return_code, 0, "Using proxy socks4a:$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http --proxy socks5://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 0/m, "proxy is not used for resolving hostname, and is using scheme socks5"); + # Squid is not configured for socks5 + # is( $res->return_code, 0, "Using proxy socks5:$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http works" ); + + $res = NPTest->testCmd( "./$plugin -H $host_tcp_http --proxy socks5h://$host_tcp_proxy:$port_tcp_proxy -v" ); + like($res->output, qr/^\* proxy_resolves_hostname: 1/m, "proxy is used for resolving hostname, and is using scheme socks5h"); + # Squid is not configured for socks5h + # is( $res->return_code, 0, "Using proxy socks5h:$host_tcp_proxy:$port_tcp_proxy to connect to $host_tcp_http works" ); +} diff --git a/plugins/tests/check_curl.t b/plugins/tests/check_curl.t index 248eb4c5..94058d5b 100755 --- a/plugins/tests/check_curl.t +++ b/plugins/tests/check_curl.t @@ -27,8 +27,8 @@ use HTTP::Daemon::SSL; $ENV{'LC_TIME'} = "C"; -my $common_tests = 95; -my $ssl_only_tests = 8; +my $common_tests = 111; +my $ssl_only_tests = 12; # Check that all dependent modules are available eval "use HTTP::Daemon 6.01;"; plan skip_all => 'HTTP::Daemon >= 6.01 required' if $@; @@ -41,7 +41,7 @@ my $plugin = 'check_http'; $plugin = 'check_curl' if $0 =~ m/check_curl/mx; # look for libcurl version to see if some advanced checks are possible (>= 7.49.0) -my $advanced_checks = 12; +my $advanced_checks = 16; my $use_advanced_checks = 0; my $required_version = '7.49.0'; my $virtual_host = 'www.somefunnyhost.com'; @@ -410,6 +410,41 @@ SKIP: { $result = NPTest->testCmd( $cmd ); is( $result->return_code, 0, $cmd); like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ second.*/', "Output correct: ".$result->output ); + + # curlopt proxy/noproxy parsing tests, ssl disabled + { + # Make a scope and change environment variables here, to not mess them up for other tests using environment variables + + local $ENV{"http_proxy"} = 'http://proxy.example.com:8080'; + $cmd = "$command -u /statuscode/200 -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://proxy.example.com:8080 */', "Correctly took 'http_proxy' environment variable: ".$result->output ); + delete($ENV{"http_proxy"}); + + local $ENV{"http_proxy"} = 'http://taken.proxy.example:8080'; + local $ENV{"HTTP_PROXY"} = 'http://discarded.proxy.example:8080'; + $cmd = "$command -u /statuscode/200 -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://taken.proxy.example:8080 */', "Correctly took 'http_proxy' environment variable over 'HTTP_PROXY': ".$result->output ); + delete(local $ENV{"http_proxy"}); + delete(local $ENV{"HTTP_PROXY"}); + + local $ENV{"http_proxy"} = 'http://discarded1.proxy.example:8080'; + local $ENV{"HTTP_PROXY"} = 'http://discarded2.proxy.example:8080'; + $cmd = "$command -u /statuscode/200 -x 'http://taken.proxy.example:8080' -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://taken.proxy.example:8080 */', "Argument -x overwrote 'http_proxy' and 'HTTP_PROXY' environment variables: ".$result->output ); + delete(local $ENV{"http_proxy"}); + delete(local $ENV{"HTTP_PROXY"}); + + local $ENV{"http_proxy"} = 'http://discarded1.proxy.example:8080'; + local $ENV{"HTTP_PROXY"} = 'http://discarded2.proxy.example:8080'; + $cmd = "$command -u /statuscode/200 --proxy 'http://taken.example.com:8080' -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://taken.example.com:8080 */', "Argument --proxy overwrote 'http_proxy' and 'HTTP_PROXY' environment variables: ".$result->output ); + delete(local $ENV{"http_proxy"}); + delete(local $ENV{"HTTP_PROXY"}); + } } # and the same for SSL @@ -432,6 +467,41 @@ SKIP: { $result = NPTest->testCmd( $cmd ); is( $result->return_code, 0, $cmd); like( $result->output, '/.*HTTP/1.1 200 OK - \d+ bytes in [\d\.]+ second.*/', "Output correct: ".$result->output ); + + # curlopt proxy/noproxy parsing tests, ssl enabled + { + # Make a scope and change environment variables here, to not mess them up for other tests using environment variables + + local $ENV{"https_proxy"} = 'http://proxy.example.com:8080'; + $cmd = "$command -u /statuscode/200 --ssl -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://proxy.example.com:8080 */', "Correctly took 'https_proxy' environment variable: ".$result->output ); + delete($ENV{"https_proxy"}); + + local $ENV{"https_proxy"} = 'http://taken.proxy.example:8080'; + local $ENV{"HTTPS_PROXY"} = 'http://discarded.proxy.example:8080'; + $cmd = "$command -u /statuscode/200 --ssl -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://taken.proxy.example:8080 */', "Correctly took 'https_proxy' environment variable over 'HTTPS_PROXY': ".$result->output ); + delete(local $ENV{"https_proxy"}); + delete(local $ENV{"HTTPS_PROXY"}); + + local $ENV{"https_proxy"} = 'http://discarded1.proxy.example:8080'; + local $ENV{"HTTPS_PROXY"} = 'http://discarded2.proxy.example:8080'; + $cmd = "$command -u /statuscode/200 --ssl -x 'http://taken.example.com:8080' -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://taken.example.com:8080 */', "Argument -x overwrote environment variables 'https_proxy' and 'HTTPS_PROXY': ".$result->output ); + delete(local $ENV{"https_proxy"}); + delete(local $ENV{"HTTPS_PROXY"}); + + local $ENV{"https_proxy"} = 'http://discarded1.proxy.example:8080'; + local $ENV{"HTTPS_PROXY"} = 'http://discarded2.proxy.example:8080'; + $cmd = "$command -u /statuscode/200 --ssl --proxy 'http://taken.example.com:8080' -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.*CURLOPT_PROXY: http://taken.example.com:8080 */', "Argument --proxy overwrote environment variables 'https_proxy' and 'HTTPS_PROXY': ".$result->output ); + delete(local $ENV{"https_proxy"}); + delete(local $ENV{"HTTPS_PROXY"}); + } } @@ -712,4 +782,63 @@ sub run_common_tests { $result = NPTest->testCmd( $cmd, 5 ); }; is( $@, "", $cmd ); + + # curlopt proxy/noproxy parsing tests + { + # Make a scope and change environment variables here, to not mess them up for other tests using environment variables + + local $ENV{"no_proxy"} = 'internal.acme.org'; + $cmd = "$command -u /statuscode/200 -v"; + $result = NPTest->testCmd( $cmd ); + like( $result->output, '/.* curl CURLOPT_NOPROXY: internal.acme.org */', "Correctly took 'no_proxy' environment variable: ".$result->output ); + delete($ENV{"no_proxy"}); + + local $ENV{"no_proxy"} = 'taken.acme.org'; + local $ENV{"NO_PROXY"} = 'discarded.acme.org'; + $cmd = "$command -u /statuscode/200 -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + like( $result->output, '/.*CURLOPT_NOPROXY: taken.acme.org*/', "Correctly took 'no_proxy' environment variable over 'NO_PROXY': ".$result->output ); + delete(local $ENV{"no_proxy"}); + delete(local $ENV{"NO_PROXY"}); + + local $ENV{"no_proxy"} = 'taken.acme.org'; + local $ENV{"NO_PROXY"} = 'discarded.acme.org'; + $cmd = "$command -u /statuscode/200 --noproxy 'taken.acme.org' -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + like( $result->output, '/.*CURLOPT_NOPROXY: taken.acme.org*/', "Argument --noproxy overwrote environment variables 'no_proxy' and 'NO_PROXY': ".$result->output ); + delete(local $ENV{"no_proxy"}); + delete(local $ENV{"NO_PROXY"}); + + $cmd = "$command -u /statuscode/200 --noproxy 'internal1.acme.org,internal2.acme.org,internal3.acme.org' -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + like( $result->output, '/.*CURLOPT_NOPROXY: internal1.acme.org,internal2.acme.org,internal3.acme.org*/', "Argument --noproxy read multiple noproxy domains: ".$result->output ); + + $cmd = "$command -u /statuscode/200 --noproxy '10.11.12.13,256.256.256.256,0.0.0.0,192.156.0.0/22,10.0.0.0/4' -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + like( $result->output, '/.*CURLOPT_NOPROXY: 10.11.12.13,256.256.256.256,0.0.0.0,192.156.0.0/22,10.0.0.0/4*/', "Argument --noproxy took multiple noproxy domains: ".$result->output ); + + $cmd = "$command -u /statuscode/200 --noproxy '0123:4567:89AB:CDEF:0123:4567:89AB:CDEF,0123::CDEF,0123:4567/96,[::1],::1,[1234::5678:ABCD/4]' -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + like( $result->output, '/.*CURLOPT_NOPROXY: 0123:4567:89AB:CDEF:0123:4567:89AB:CDEF,0123::CDEF,0123:4567\/96,\[::1\],::1,\[1234::5678:ABCD\/4\].*/', "Argument --noproxy took multiple noproxy domains: ".$result->output ); + + $cmd = "$command -u /statuscode/200 --noproxy '300.400.500.600,1.2.3,XYZD:0123::,1:2:3:4:5:6:7,1::2::3,1.1.1.1/64,::/256' -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + + $cmd = "$command -u /statuscode/200 --proxy http://proxy.example.com:8080 --noproxy '*' -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + like( $result->output, '/.*proxy_resolves_hostname: 0.*/', "Proxy will not be used due to '*' in noproxy: ".$result->output ); + + $cmd = "$command -u /statuscode/200 --proxy http://proxy.example.com:8080 --noproxy '127.0.0.1' -v"; + $result = NPTest->testCmd( $cmd ); + is( $result->return_code, 0, $cmd); + like( $result->output, '/.*proxy_resolves_hostname: 0.*/', "Proxy will not be used due to '127.0.0.1' in noproxy: ".$result->output ); + } + } diff --git a/tools/squid.conf b/tools/squid.conf index bed7a583..048aa576 100644 --- a/tools/squid.conf +++ b/tools/squid.conf @@ -972,6 +972,8 @@ # Example rule allowing access from your local networks. # Adapt to list your (internal) IP networks from where browsing # should be allowed +acl localhost src 127.0.0.1/32 +acl localhost src ::1/128 acl localnet src 10.0.0.0/8 # RFC1918 possible internal network acl localnet src 172.16.0.0/12 # RFC1918 possible internal network acl localnet src 192.168.0.0/16 # RFC1918 possible internal network @@ -1172,6 +1174,7 @@ http_access deny !Safe_ports http_access deny CONNECT !SSL_ports # Only allow cachemgr access from localhost +http_access allow localhost http_access allow localhost manager http_access deny manager diff --git a/tools/subdomain1/index.php b/tools/subdomain1/index.php new file mode 100644 index 00000000..e97b19f5 --- /dev/null +++ b/tools/subdomain1/index.php @@ -0,0 +1 @@ +Subdomain: subdomain1.localhost.com diff --git a/tools/subdomain1/subdomain1.conf b/tools/subdomain1/subdomain1.conf new file mode 100644 index 00000000..74521792 --- /dev/null +++ b/tools/subdomain1/subdomain1.conf @@ -0,0 +1,22 @@ +# This apache configuration file is used testing +# check_curl tests use this subdomain to see if --noproxy works on subdomains of a domain. + + + ServerName subdomain1.localhost.com + DocumentRoot /var/www/subdomain1 + + ErrorLog ${APACHE_LOG_DIR}/subdomain1_error.log + CustomLog ${APACHE_LOG_DIR}/subdomain1_access.log combined + + + + ServerName subdomain1.localhost.com + DocumentRoot /var/www/subdomain1 + + SSLEngine on + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + ErrorLog ${APACHE_LOG_DIR}/subdomain1_ssl_error.log + CustomLog ${APACHE_LOG_DIR}/subdomain1_ssl_access.log combined +