diff options
Diffstat (limited to 'plugins/check_curl.d')
| -rw-r--r-- | plugins/check_curl.d/check_curl_helpers.c | 553 | ||||
| -rw-r--r-- | plugins/check_curl.d/check_curl_helpers.h | 23 | ||||
| -rw-r--r-- | plugins/check_curl.d/config.h | 7 |
3 files changed, 565 insertions, 18 deletions
diff --git a/plugins/check_curl.d/check_curl_helpers.c b/plugins/check_curl.d/check_curl_helpers.c index 7be781fc..80d6f4f6 100644 --- a/plugins/check_curl.d/check_curl_helpers.c +++ b/plugins/check_curl.d/check_curl_helpers.c | |||
| @@ -3,8 +3,11 @@ | |||
| 3 | #include <arpa/inet.h> | 3 | #include <arpa/inet.h> |
| 4 | #include <netinet/in.h> | 4 | #include <netinet/in.h> |
| 5 | #include <netdb.h> | 5 | #include <netdb.h> |
| 6 | #include <stdint.h> | ||
| 7 | #include <stdio.h> | ||
| 6 | #include <stdlib.h> | 8 | #include <stdlib.h> |
| 7 | #include <string.h> | 9 | #include <string.h> |
| 10 | #include <sys/socket.h> | ||
| 8 | #include "../utils.h" | 11 | #include "../utils.h" |
| 9 | #include "check_curl.d/config.h" | 12 | #include "check_curl.d/config.h" |
| 10 | #include "output.h" | 13 | #include "output.h" |
| @@ -57,8 +60,8 @@ check_curl_configure_curl(const check_curl_static_curl_config config, | |||
| 57 | result.curl_state.curl_easy_initialized = true; | 60 | result.curl_state.curl_easy_initialized = true; |
| 58 | 61 | ||
| 59 | if (verbose >= 1) { | 62 | if (verbose >= 1) { |
| 60 | handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_VERBOSE, 1L), | 63 | handle_curl_option_return_code( |
| 61 | "CURLOPT_VERBOSE"); | 64 | curl_easy_setopt(result.curl_state.curl, CURLOPT_VERBOSE, 1L), "CURLOPT_VERBOSE"); |
| 62 | } | 65 | } |
| 63 | 66 | ||
| 64 | /* print everything on stdout like check_http would do */ | 67 | /* print everything on stdout like check_http would do */ |
| @@ -116,6 +119,112 @@ check_curl_configure_curl(const check_curl_static_curl_config config, | |||
| 116 | curl_easy_setopt(result.curl_state.curl, CURLOPT_TIMEOUT, config.socket_timeout), | 119 | curl_easy_setopt(result.curl_state.curl, CURLOPT_TIMEOUT, config.socket_timeout), |
| 117 | "CURLOPT_TIMEOUT"); | 120 | "CURLOPT_TIMEOUT"); |
| 118 | 121 | ||
| 122 | /* set proxy */ | ||
| 123 | /* http(s) proxy can either be given from the command line, or taken from environment variables | ||
| 124 | */ | ||
| 125 | /* socks4(a) / socks5(h) proxy should be given using the command line */ | ||
| 126 | |||
| 127 | /* first source to check is the environment variables */ | ||
| 128 | /* lower case proxy environment variables are almost always accepted, while some programs also | ||
| 129 | checking uppercase ones. discover both, but take the lowercase one if both are present */ | ||
| 130 | |||
| 131 | /* extra information: libcurl does not discover the uppercase version HTTP_PROXY due to security | ||
| 132 | * reasons */ | ||
| 133 | /* https://github.com/curl/curl/blob/d445f2d930ae701039518d695481ee53b8490521/lib/url.c#L1987 */ | ||
| 134 | |||
| 135 | /* first environment variable to read is all_proxy. it can be overridden by protocol specific | ||
| 136 | * environment variables */ | ||
| 137 | char *all_proxy_env = getenv("all_proxy"); | ||
| 138 | char *all_proxy_uppercase_env = getenv("ALL_PROXY"); | ||
| 139 | if (all_proxy_env != NULL && strlen(all_proxy_env)) { | ||
| 140 | working_state.curlopt_proxy = strdup(all_proxy_env); | ||
| 141 | if (all_proxy_uppercase_env != NULL && verbose >= 1) { | ||
| 142 | printf("* cURL ignoring environment variable 'ALL_PROXY' as 'all_proxy' is set\n"); | ||
| 143 | } | ||
| 144 | } else if (all_proxy_uppercase_env != NULL && strlen(all_proxy_uppercase_env) > 0) { | ||
| 145 | working_state.curlopt_proxy = strdup(all_proxy_uppercase_env); | ||
| 146 | } | ||
| 147 | |||
| 148 | /* second environment variable to read is http_proxy. only set curlopt_proxy if ssl is not | ||
| 149 | * toggled */ | ||
| 150 | char *http_proxy_env = getenv("http_proxy"); | ||
| 151 | char *http_proxy_uppercase_env = getenv("HTTP_PROXY"); | ||
| 152 | if (!working_state.use_ssl) { | ||
| 153 | if (http_proxy_env != NULL && strlen(http_proxy_env) > 0) { | ||
| 154 | working_state.curlopt_proxy = strdup(http_proxy_env); | ||
| 155 | if (http_proxy_uppercase_env != NULL && verbose >= 1) { | ||
| 156 | printf( | ||
| 157 | "* cURL ignoring environment variable 'HTTP_PROXY' as 'http_proxy' is set\n"); | ||
| 158 | } | ||
| 159 | } else if (http_proxy_uppercase_env != NULL && strlen(http_proxy_uppercase_env) > 0) { | ||
| 160 | working_state.curlopt_proxy = strdup(http_proxy_uppercase_env); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | #ifdef LIBCURL_FEATURE_SSL | ||
| 164 | /* optionally read https_proxy environment variable and set curlopt_proxy if ssl is toggled */ | ||
| 165 | char *https_proxy_env = getenv("https_proxy"); | ||
| 166 | char *https_proxy_uppercase_env = getenv("HTTPS_PROXY"); | ||
| 167 | if (working_state.use_ssl) { | ||
| 168 | if (https_proxy_env != NULL && strlen(https_proxy_env) > 0) { | ||
| 169 | working_state.curlopt_proxy = strdup(https_proxy_env); | ||
| 170 | if (https_proxy_uppercase_env != NULL && verbose >= 1) { | ||
| 171 | printf( | ||
| 172 | "* cURL ignoring environment variable 'HTTPS_PROXY' as 'https_proxy' is set\n"); | ||
| 173 | } | ||
| 174 | } else if (https_proxy_uppercase_env != NULL) { | ||
| 175 | working_state.curlopt_proxy = strdup(https_proxy_uppercase_env); | ||
| 176 | } | ||
| 177 | } | ||
| 178 | #endif /* LIBCURL_FEATURE_SSL */ | ||
| 179 | |||
| 180 | /* second source to check for proxies is command line argument, overwriting the environment | ||
| 181 | * variables */ | ||
| 182 | if (strlen(config.proxy) > 0) { | ||
| 183 | working_state.curlopt_proxy = strdup(config.proxy); | ||
| 184 | } | ||
| 185 | |||
| 186 | if (working_state.curlopt_proxy != NULL && strlen(working_state.curlopt_proxy)) { | ||
| 187 | handle_curl_option_return_code( | ||
| 188 | curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXY, working_state.curlopt_proxy), | ||
| 189 | "CURLOPT_PROXY"); | ||
| 190 | if (verbose >= 1) { | ||
| 191 | printf("* curl CURLOPT_PROXY: %s\n", working_state.curlopt_proxy); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | /* set no_proxy */ | ||
| 196 | /* first source to check is environment variables */ | ||
| 197 | char *no_proxy_env = getenv("no_proxy"); | ||
| 198 | char *no_proxy_uppercase_env = getenv("NO_PROXY"); | ||
| 199 | if (no_proxy_env != NULL && strlen(no_proxy_env)) { | ||
| 200 | working_state.curlopt_noproxy = strdup(no_proxy_env); | ||
| 201 | if (no_proxy_uppercase_env != NULL && verbose >= 1) { | ||
| 202 | printf("* cURL ignoring environment variable 'NO_PROXY' as 'no_proxy' is set\n"); | ||
| 203 | } | ||
| 204 | } else if (no_proxy_uppercase_env != NULL && strlen(no_proxy_uppercase_env) > 0) { | ||
| 205 | working_state.curlopt_noproxy = strdup(no_proxy_uppercase_env); | ||
| 206 | } | ||
| 207 | |||
| 208 | /* second source to check for no_proxy is command line argument, overwriting the environment | ||
| 209 | * variables */ | ||
| 210 | if (strlen(config.no_proxy) > 0) { | ||
| 211 | working_state.curlopt_noproxy = strdup(config.no_proxy); | ||
| 212 | } | ||
| 213 | |||
| 214 | if (working_state.curlopt_noproxy != NULL && strlen(working_state.curlopt_noproxy)) { | ||
| 215 | handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_NOPROXY, | ||
| 216 | working_state.curlopt_noproxy), | ||
| 217 | "CURLOPT_NOPROXY"); | ||
| 218 | if (verbose >= 1) { | ||
| 219 | printf("* curl CURLOPT_NOPROXY: %s\n", working_state.curlopt_noproxy); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | bool have_local_resolution = hostname_gets_resolved_locally(working_state); | ||
| 224 | if (verbose >= 1) { | ||
| 225 | printf("* have local name resolution: %s\n", (have_local_resolution ? "true": "false")); | ||
| 226 | } | ||
| 227 | |||
| 119 | /* enable haproxy protocol */ | 228 | /* enable haproxy protocol */ |
| 120 | if (config.haproxy_protocol) { | 229 | if (config.haproxy_protocol) { |
| 121 | handle_curl_option_return_code( | 230 | handle_curl_option_return_code( |
| @@ -123,11 +232,11 @@ check_curl_configure_curl(const check_curl_static_curl_config config, | |||
| 123 | "CURLOPT_HAPROXYPROTOCOL"); | 232 | "CURLOPT_HAPROXYPROTOCOL"); |
| 124 | } | 233 | } |
| 125 | 234 | ||
| 126 | // fill dns resolve cache to make curl connect to the given server_address instead of the | 235 | /* fill dns resolve cache to make curl connect to the given server_address instead of the */ |
| 127 | // host_name, only required for ssl, because we use the host_name later on to make SNI happy | 236 | /* host_name, only required for ssl, because we use the host_name later on to make SNI happy */ |
| 128 | char dnscache[DEFAULT_BUFFER_SIZE]; | 237 | char dnscache[DEFAULT_BUFFER_SIZE]; |
| 129 | char addrstr[DEFAULT_BUFFER_SIZE / 2]; | 238 | char addrstr[DEFAULT_BUFFER_SIZE / 2]; |
| 130 | if (working_state.use_ssl && working_state.host_name != NULL) { | 239 | if (working_state.use_ssl && working_state.host_name != NULL && !have_local_resolution) { |
| 131 | char *tmp_mod_address; | 240 | char *tmp_mod_address; |
| 132 | 241 | ||
| 133 | /* lookup_host() requires an IPv6 address without the brackets. */ | 242 | /* lookup_host() requires an IPv6 address without the brackets. */ |
| @@ -330,11 +439,11 @@ check_curl_configure_curl(const check_curl_static_curl_config config, | |||
| 330 | case CURLHELP_SSL_LIBRARY_LIBRESSL: | 439 | case CURLHELP_SSL_LIBRARY_LIBRESSL: |
| 331 | /* set callback to extract certificate with OpenSSL context function (works with | 440 | /* set callback to extract certificate with OpenSSL context function (works with |
| 332 | * OpenSSL-style libraries only!) */ | 441 | * OpenSSL-style libraries only!) */ |
| 333 | # ifdef USE_OPENSSL | 442 | # ifdef MOPL_USE_OPENSSL |
| 334 | /* libcurl and monitoring plugins built with OpenSSL, good */ | 443 | /* libcurl and monitoring plugins built with OpenSSL, good */ |
| 335 | add_sslctx_verify_fun = true; | 444 | add_sslctx_verify_fun = true; |
| 336 | is_openssl_callback = true; | 445 | is_openssl_callback = true; |
| 337 | # endif /* USE_OPENSSL */ | 446 | # endif /* MOPL_USE_OPENSSL */ |
| 338 | /* libcurl is built with OpenSSL, monitoring plugins, so falling | 447 | /* libcurl is built with OpenSSL, monitoring plugins, so falling |
| 339 | * back to manually extracting certificate information */ | 448 | * back to manually extracting certificate information */ |
| 340 | handle_curl_option_return_code( | 449 | handle_curl_option_return_code( |
| @@ -488,7 +597,7 @@ check_curl_configure_curl(const check_curl_static_curl_config config, | |||
| 488 | curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4), | 597 | curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4), |
| 489 | "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V4)"); | 598 | "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V4)"); |
| 490 | } | 599 | } |
| 491 | #if defined(USE_IPV6) && defined(LIBCURL_FEATURE_IPV6) | 600 | #if defined(LIBCURL_FEATURE_IPV6) |
| 492 | else if (config.sin_family == AF_INET6) { | 601 | else if (config.sin_family == AF_INET6) { |
| 493 | handle_curl_option_return_code( | 602 | handle_curl_option_return_code( |
| 494 | curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6), | 603 | curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6), |
| @@ -562,7 +671,7 @@ check_curl_configure_curl(const check_curl_static_curl_config config, | |||
| 562 | 671 | ||
| 563 | void handle_curl_option_return_code(CURLcode res, const char *option) { | 672 | void handle_curl_option_return_code(CURLcode res, const char *option) { |
| 564 | if (res != CURLE_OK) { | 673 | if (res != CURLE_OK) { |
| 565 | die(STATE_CRITICAL, _("Error while setting cURL option '%s': cURL returned %d - %s"), | 674 | die(STATE_CRITICAL, _("Error while setting cURL option '%s': cURL returned %d - %s\n"), |
| 566 | option, res, curl_easy_strerror(res)); | 675 | option, res, curl_easy_strerror(res)); |
| 567 | } | 676 | } |
| 568 | } | 677 | } |
| @@ -578,7 +687,7 @@ char *get_header_value(const struct phr_header *headers, const size_t nof_header | |||
| 578 | return NULL; | 687 | return NULL; |
| 579 | } | 688 | } |
| 580 | 689 | ||
| 581 | check_curl_working_state check_curl_working_state_init() { | 690 | check_curl_working_state check_curl_working_state_init(void) { |
| 582 | check_curl_working_state result = { | 691 | check_curl_working_state result = { |
| 583 | .server_address = NULL, | 692 | .server_address = NULL, |
| 584 | .server_url = DEFAULT_SERVER_URL, | 693 | .server_url = DEFAULT_SERVER_URL, |
| @@ -589,11 +698,13 @@ check_curl_working_state check_curl_working_state_init() { | |||
| 589 | .serverPort = HTTP_PORT, | 698 | .serverPort = HTTP_PORT, |
| 590 | .use_ssl = false, | 699 | .use_ssl = false, |
| 591 | .no_body = false, | 700 | .no_body = false, |
| 701 | .curlopt_proxy = NULL, | ||
| 702 | .curlopt_noproxy = NULL, | ||
| 592 | }; | 703 | }; |
| 593 | return result; | 704 | return result; |
| 594 | } | 705 | } |
| 595 | 706 | ||
| 596 | check_curl_config check_curl_config_init() { | 707 | check_curl_config check_curl_config_init(void) { |
| 597 | check_curl_config tmp = { | 708 | check_curl_config tmp = { |
| 598 | .initial_config = check_curl_working_state_init(), | 709 | .initial_config = check_curl_working_state_init(), |
| 599 | 710 | ||
| @@ -612,6 +723,8 @@ check_curl_config check_curl_config_init() { | |||
| 612 | .ca_cert = NULL, | 723 | .ca_cert = NULL, |
| 613 | .verify_peer_and_host = false, | 724 | .verify_peer_and_host = false, |
| 614 | .user_agent = {'\0'}, | 725 | .user_agent = {'\0'}, |
| 726 | .proxy = "", | ||
| 727 | .no_proxy = "", | ||
| 615 | .proxy_auth = "", | 728 | .proxy_auth = "", |
| 616 | .user_auth = "", | 729 | .user_auth = "", |
| 617 | .http_content_type = NULL, | 730 | .http_content_type = NULL, |
| @@ -1199,24 +1312,24 @@ mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_ | |||
| 1199 | 1312 | ||
| 1200 | #ifdef LIBCURL_FEATURE_SSL | 1313 | #ifdef LIBCURL_FEATURE_SSL |
| 1201 | if (is_openssl_callback) { | 1314 | if (is_openssl_callback) { |
| 1202 | # ifdef USE_OPENSSL | 1315 | # ifdef MOPL_USE_OPENSSL |
| 1203 | /* check certificate with OpenSSL functions, curl has been built against OpenSSL | 1316 | /* check certificate with OpenSSL functions, curl has been built against OpenSSL |
| 1204 | * and we actually have OpenSSL in the monitoring tools | 1317 | * and we actually have OpenSSL in the monitoring tools |
| 1205 | */ | 1318 | */ |
| 1206 | return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp); | 1319 | return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp); |
| 1207 | # else /* USE_OPENSSL */ | 1320 | # else /* MOPL_USE_OPENSSL */ |
| 1208 | xasprintf(&result.output, "HTTP CRITICAL - Cannot retrieve certificates - OpenSSL " | 1321 | xasprintf(&result.output, "HTTP CRITICAL - Cannot retrieve certificates - OpenSSL " |
| 1209 | "callback used and not linked against OpenSSL\n"); | 1322 | "callback used and not linked against OpenSSL\n"); |
| 1210 | mp_set_subcheck_state(result, STATE_CRITICAL); | 1323 | mp_set_subcheck_state(result, STATE_CRITICAL); |
| 1211 | # endif /* USE_OPENSSL */ | 1324 | # endif /* MOPL_USE_OPENSSL */ |
| 1212 | } else { | 1325 | } else { |
| 1213 | struct curl_slist *slist; | 1326 | struct curl_slist *slist; |
| 1214 | 1327 | ||
| 1215 | cert_ptr_union cert_ptr = {0}; | 1328 | cert_ptr_union cert_ptr = {0}; |
| 1216 | cert_ptr.to_info = NULL; | 1329 | cert_ptr.to_info = NULL; |
| 1217 | CURLcode res = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &cert_ptr.to_info); | 1330 | CURLcode res = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &cert_ptr.to_certinfo); |
| 1218 | if (!res && cert_ptr.to_info) { | 1331 | if (!res && cert_ptr.to_info) { |
| 1219 | # ifdef USE_OPENSSL | 1332 | # ifdef MOPL_USE_OPENSSL |
| 1220 | /* We have no OpenSSL in libcurl, but we can use OpenSSL for X509 cert | 1333 | /* We have no OpenSSL in libcurl, but we can use OpenSSL for X509 cert |
| 1221 | * parsing We only check the first certificate and assume it's the one of | 1334 | * parsing We only check the first certificate and assume it's the one of |
| 1222 | * the server | 1335 | * the server |
| @@ -1262,13 +1375,13 @@ mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_ | |||
| 1262 | 1375 | ||
| 1263 | BIO_free(cert_BIO); | 1376 | BIO_free(cert_BIO); |
| 1264 | return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp); | 1377 | return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp); |
| 1265 | # else /* USE_OPENSSL */ | 1378 | # else /* MOPL_USE_OPENSSL */ |
| 1266 | /* We assume we don't have OpenSSL and np_net_ssl_check_certificate at our | 1379 | /* We assume we don't have OpenSSL and np_net_ssl_check_certificate at our |
| 1267 | * disposal, so we use the libcurl CURLINFO data | 1380 | * disposal, so we use the libcurl CURLINFO data |
| 1268 | */ | 1381 | */ |
| 1269 | return net_noopenssl_check_certificate(&cert_ptr, days_till_exp_warn, | 1382 | return net_noopenssl_check_certificate(&cert_ptr, days_till_exp_warn, |
| 1270 | days_till_exp_crit); | 1383 | days_till_exp_crit); |
| 1271 | # endif /* USE_OPENSSL */ | 1384 | # endif /* MOPL_USE_OPENSSL */ |
| 1272 | } else { | 1385 | } else { |
| 1273 | xasprintf(&sc_cert_result.output, | 1386 | xasprintf(&sc_cert_result.output, |
| 1274 | _("Cannot retrieve certificates - cURL returned %d - %s"), res, | 1387 | _("Cannot retrieve certificates - cURL returned %d - %s"), res, |
| @@ -1295,3 +1408,407 @@ char *fmt_url(check_curl_working_state workingState) { | |||
| 1295 | 1408 | ||
| 1296 | return url; | 1409 | return url; |
| 1297 | } | 1410 | } |
| 1411 | |||
| 1412 | bool hostname_gets_resolved_locally(const check_curl_working_state working_state) { | ||
| 1413 | char *host_name_display = "NULL"; | ||
| 1414 | unsigned long host_name_len = 0; | ||
| 1415 | if (working_state.host_name) { | ||
| 1416 | host_name_len = strlen(working_state.host_name); | ||
| 1417 | host_name_display = working_state.host_name; | ||
| 1418 | } | ||
| 1419 | |||
| 1420 | /* IPv4 or IPv6 version of the address */ | ||
| 1421 | char *server_address_clean = strdup(working_state.server_address); | ||
| 1422 | /* server address might be a full length ipv6 address encapsulated in square brackets */ | ||
| 1423 | if ((strnlen(working_state.server_address, MAX_IPV4_HOSTLENGTH) > 2) && | ||
| 1424 | (working_state.server_address[0] == '[') && | ||
| 1425 | (working_state.server_address[strlen(working_state.server_address) - 1] == ']')) { | ||
| 1426 | server_address_clean = | ||
| 1427 | strndup(working_state.server_address + 1, strlen(working_state.server_address) - 2); | ||
| 1428 | } | ||
| 1429 | |||
| 1430 | /* check curlopt_noproxy option first */ | ||
| 1431 | /* https://curl.se/libcurl/c/CURLOPT_NOPROXY.html */ | ||
| 1432 | |||
| 1433 | /* curlopt_noproxy is specified as a comma separated list of | ||
| 1434 | direct IPv4 or IPv6 addresses e.g 130.133.8.40, 2001:4860:4802:32::a , | ||
| 1435 | IPv4 or IPv6 CIDR regions e.g 10.241.0.0/16 , abcd:ef01:2345::/48 , | ||
| 1436 | direct hostnames e.g example.com, google.de */ | ||
| 1437 | |||
| 1438 | if (working_state.curlopt_noproxy != NULL) { | ||
| 1439 | char *curlopt_noproxy_copy = strdup(working_state.curlopt_noproxy); | ||
| 1440 | char *noproxy_item = strtok(curlopt_noproxy_copy, ","); | ||
| 1441 | while (noproxy_item != NULL) { | ||
| 1442 | unsigned long noproxy_item_len = strlen(noproxy_item); | ||
| 1443 | |||
| 1444 | /* According to the CURLOPT_NOPROXY documentation: */ | ||
| 1445 | /* https://curl.se/libcurl/c/CURLOPT_NOPROXY.html */ | ||
| 1446 | /* The only wildcard available is a single * character, which matches all hosts, and | ||
| 1447 | * effectively disables the proxy. */ | ||
| 1448 | if (strlen(noproxy_item) == 1 && noproxy_item[0] == '*') { | ||
| 1449 | if (verbose >= 1) { | ||
| 1450 | printf("* noproxy includes '*' which disables proxy for all host name incl. : " | ||
| 1451 | "%s / server address incl. : %s\n", | ||
| 1452 | host_name_display, server_address_clean); | ||
| 1453 | } | ||
| 1454 | free(curlopt_noproxy_copy); | ||
| 1455 | free(server_address_clean); | ||
| 1456 | return true; | ||
| 1457 | } | ||
| 1458 | |||
| 1459 | /* direct comparison with the server_address */ | ||
| 1460 | if (server_address_clean != NULL && | ||
| 1461 | strlen(server_address_clean) == strlen(noproxy_item) && | ||
| 1462 | strcmp(server_address_clean, noproxy_item) == 0) { | ||
| 1463 | if (verbose >= 1) { | ||
| 1464 | printf("* server_address is in the no_proxy list: %s\n", noproxy_item); | ||
| 1465 | } | ||
| 1466 | free(curlopt_noproxy_copy); | ||
| 1467 | free(server_address_clean); | ||
| 1468 | return true; | ||
| 1469 | } | ||
| 1470 | |||
| 1471 | /* direct comparison with the host_name */ | ||
| 1472 | if (working_state.host_name != NULL && host_name_len == noproxy_item_len && | ||
| 1473 | strcmp(working_state.host_name, noproxy_item) == 0) { | ||
| 1474 | if (verbose >= 1) { | ||
| 1475 | printf("* host_name is in the no_proxy list: %s\n", noproxy_item); | ||
| 1476 | } | ||
| 1477 | free(curlopt_noproxy_copy); | ||
| 1478 | free(server_address_clean); | ||
| 1479 | return true; | ||
| 1480 | } | ||
| 1481 | |||
| 1482 | /* check if hostname is a subdomain of the item, e.g www.example.com when token is | ||
| 1483 | * example.com */ | ||
| 1484 | /* subdomain1.acme.com will not will use a proxy if you only specify 'acme' in the | ||
| 1485 | * noproxy */ | ||
| 1486 | /* check if noproxy_item is a suffix */ | ||
| 1487 | /* check if the character just before the suffix is '.' */ | ||
| 1488 | if (working_state.host_name != NULL && host_name_len > noproxy_item_len) { | ||
| 1489 | unsigned long suffix_start_idx = host_name_len - noproxy_item_len; | ||
| 1490 | if (strcmp(working_state.host_name + suffix_start_idx, noproxy_item) == 0 && | ||
| 1491 | working_state.host_name[suffix_start_idx - 1] == '.') { | ||
| 1492 | if (verbose >= 1) { | ||
| 1493 | printf("* host_name: %s is a subdomain of the no_proxy list item: %s\n", | ||
| 1494 | working_state.host_name, noproxy_item); | ||
| 1495 | } | ||
| 1496 | free(curlopt_noproxy_copy); | ||
| 1497 | free(server_address_clean); | ||
| 1498 | return true; | ||
| 1499 | } | ||
| 1500 | } | ||
| 1501 | |||
| 1502 | // noproxy_item could be a CIDR IP range | ||
| 1503 | if (server_address_clean != NULL && strlen(server_address_clean)) { | ||
| 1504 | ip_addr_inside ip_addr_inside_cidr_ret = | ||
| 1505 | ip_addr_inside_cidr(noproxy_item, server_address_clean); | ||
| 1506 | |||
| 1507 | if (ip_addr_inside_cidr_ret.error == NO_ERROR) { | ||
| 1508 | if (ip_addr_inside_cidr_ret.inside) { | ||
| 1509 | return true; | ||
| 1510 | } else { | ||
| 1511 | if (verbose >= 1) { | ||
| 1512 | printf("server address: %s is not inside IP cidr: %s\n", | ||
| 1513 | server_address_clean, noproxy_item); | ||
| 1514 | } | ||
| 1515 | } | ||
| 1516 | } else { | ||
| 1517 | if (verbose >= 1) { | ||
| 1518 | printf("could not fully determine if server address: %s is inside the IP " | ||
| 1519 | "cidr: %s\n", | ||
| 1520 | server_address_clean, noproxy_item); | ||
| 1521 | } | ||
| 1522 | } | ||
| 1523 | } | ||
| 1524 | |||
| 1525 | noproxy_item = strtok(NULL, ","); | ||
| 1526 | } | ||
| 1527 | |||
| 1528 | free(curlopt_noproxy_copy); | ||
| 1529 | } | ||
| 1530 | |||
| 1531 | if (working_state.curlopt_proxy != NULL) { | ||
| 1532 | // Libcurl documentation | ||
| 1533 | // Setting the proxy string to "" (an empty string) explicitly disables the use of a proxy, | ||
| 1534 | // even if there is an environment variable set for it. | ||
| 1535 | if (strlen(working_state.curlopt_proxy) == 0) { | ||
| 1536 | return true; | ||
| 1537 | } | ||
| 1538 | |||
| 1539 | if (strncmp(working_state.curlopt_proxy, "http://", 7) == 0) { | ||
| 1540 | if (verbose >= 1) { | ||
| 1541 | printf( | ||
| 1542 | "* proxy scheme is http, proxy: %s resolves host: %s or server_address: %s\n", | ||
| 1543 | working_state.curlopt_proxy, host_name_display, server_address_clean); | ||
| 1544 | } | ||
| 1545 | free(server_address_clean); | ||
| 1546 | return false; | ||
| 1547 | } | ||
| 1548 | |||
| 1549 | if (strncmp(working_state.curlopt_proxy, "https://", 8) == 0) { | ||
| 1550 | if (verbose >= 1) { | ||
| 1551 | printf( | ||
| 1552 | "* proxy scheme is https, proxy: %s resolves host: %s or server_address: %s\n", | ||
| 1553 | working_state.curlopt_proxy, host_name_display, server_address_clean); | ||
| 1554 | } | ||
| 1555 | free(server_address_clean); | ||
| 1556 | return false; | ||
| 1557 | } | ||
| 1558 | |||
| 1559 | if (strncmp(working_state.curlopt_proxy, "socks4://", 9) == 0) { | ||
| 1560 | if (verbose >= 1) { | ||
| 1561 | printf("* proxy scheme is socks, proxy: %s does not resolve host: %s or " | ||
| 1562 | "server_address: %s\n", | ||
| 1563 | working_state.curlopt_proxy, host_name_display, server_address_clean); | ||
| 1564 | } | ||
| 1565 | free(server_address_clean); | ||
| 1566 | return true; | ||
| 1567 | } | ||
| 1568 | |||
| 1569 | if (strncmp(working_state.curlopt_proxy, "socks4a://", 10) == 0) { | ||
| 1570 | if (verbose >= 1) { | ||
| 1571 | printf("* proxy scheme is socks4a, proxy: %s resolves host: %s or server_address: " | ||
| 1572 | "%s\n", | ||
| 1573 | working_state.curlopt_proxy, host_name_display, server_address_clean); | ||
| 1574 | } | ||
| 1575 | free(server_address_clean); | ||
| 1576 | return false; | ||
| 1577 | } | ||
| 1578 | |||
| 1579 | if (strncmp(working_state.curlopt_proxy, "socks5://", 9) == 0) { | ||
| 1580 | if (verbose >= 1) { | ||
| 1581 | printf("* proxy scheme is socks5, proxy: %s does not resolve host: %s or " | ||
| 1582 | "server_address: %s\n", | ||
| 1583 | working_state.curlopt_proxy, host_name_display, server_address_clean); | ||
| 1584 | } | ||
| 1585 | free(server_address_clean); | ||
| 1586 | return true; | ||
| 1587 | } | ||
| 1588 | |||
| 1589 | if (strncmp(working_state.curlopt_proxy, "socks5h://", 10) == 0) { | ||
| 1590 | if (verbose >= 1) { | ||
| 1591 | printf("* proxy scheme is socks5h, proxy: %s resolves host: %s or server_address: " | ||
| 1592 | "%s\n", | ||
| 1593 | working_state.curlopt_proxy, host_name_display, server_address_clean); | ||
| 1594 | } | ||
| 1595 | free(server_address_clean); | ||
| 1596 | return false; | ||
| 1597 | } | ||
| 1598 | |||
| 1599 | // Libcurl documentation: | ||
| 1600 | // Without a scheme prefix, CURLOPT_PROXYTYPE can be used to specify which kind of proxy the | ||
| 1601 | // string identifies. We do not set this value Without a scheme, it is treated as an http | ||
| 1602 | // proxy | ||
| 1603 | |||
| 1604 | return false; | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | if (verbose >= 1) { | ||
| 1608 | printf("* proxy scheme is unknown/unavailable, no proxy is assumed for host: %s or " | ||
| 1609 | "server_address: %s\n", | ||
| 1610 | host_name_display, server_address_clean); | ||
| 1611 | } | ||
| 1612 | |||
| 1613 | free(server_address_clean); | ||
| 1614 | return 0; | ||
| 1615 | } | ||
| 1616 | |||
| 1617 | ip_addr_inside ip_addr_inside_cidr(const char *cidr_region_or_ip_addr, const char *target_ip) { | ||
| 1618 | unsigned int slash_count = 0; | ||
| 1619 | unsigned int last_slash_idx = 0; | ||
| 1620 | for (size_t i = 0; i < strlen(cidr_region_or_ip_addr); i++) { | ||
| 1621 | if (cidr_region_or_ip_addr[i] == '/') { | ||
| 1622 | slash_count++; | ||
| 1623 | last_slash_idx = (unsigned int)i; | ||
| 1624 | } | ||
| 1625 | } | ||
| 1626 | |||
| 1627 | char *cidr_ip_part = NULL; | ||
| 1628 | int prefix_length = 0; | ||
| 1629 | ip_addr_inside result = { | ||
| 1630 | .inside = false, | ||
| 1631 | .error = NO_ERROR, | ||
| 1632 | }; | ||
| 1633 | |||
| 1634 | if (slash_count == 0) { | ||
| 1635 | cidr_ip_part = strdup(cidr_region_or_ip_addr); | ||
| 1636 | if (!cidr_ip_part) { | ||
| 1637 | result.error = FAILED_STRDUP; | ||
| 1638 | return result; | ||
| 1639 | } | ||
| 1640 | } else if (slash_count == 1) { | ||
| 1641 | cidr_ip_part = strndup(cidr_region_or_ip_addr, last_slash_idx); | ||
| 1642 | if (!cidr_ip_part) { | ||
| 1643 | result.error = FAILED_STRDUP; | ||
| 1644 | return result; | ||
| 1645 | } | ||
| 1646 | |||
| 1647 | errno = 0; | ||
| 1648 | long long tmp = strtoll(cidr_region_or_ip_addr + last_slash_idx + 1, NULL, 10); | ||
| 1649 | if (errno == ERANGE) { | ||
| 1650 | if (verbose >= 1) { | ||
| 1651 | printf("cidr_region_or_ip: %s , could not parse subnet length\n", | ||
| 1652 | cidr_region_or_ip_addr); | ||
| 1653 | } | ||
| 1654 | free(cidr_ip_part); | ||
| 1655 | result.error = COULD_NOT_PARSE_SUBNET_LENGTH; | ||
| 1656 | return result; | ||
| 1657 | } | ||
| 1658 | prefix_length = (int)tmp; | ||
| 1659 | } else { | ||
| 1660 | if (verbose >= 1) { | ||
| 1661 | printf("cidr_region_or_ip: %s , has %d number of '/' characters, is not a valid " | ||
| 1662 | "cidr_region or IP\n", | ||
| 1663 | cidr_region_or_ip_addr, slash_count); | ||
| 1664 | } | ||
| 1665 | result.error = CIDR_REGION_INVALID; | ||
| 1666 | return result; | ||
| 1667 | } | ||
| 1668 | |||
| 1669 | int cidr_addr_family, target_addr_family; | ||
| 1670 | if (strchr(cidr_ip_part, ':')) { | ||
| 1671 | cidr_addr_family = AF_INET6; | ||
| 1672 | } else { | ||
| 1673 | cidr_addr_family = AF_INET; | ||
| 1674 | } | ||
| 1675 | |||
| 1676 | if (strchr(target_ip, ':')) { | ||
| 1677 | target_addr_family = AF_INET6; | ||
| 1678 | } else { | ||
| 1679 | target_addr_family = AF_INET; | ||
| 1680 | } | ||
| 1681 | |||
| 1682 | if (cidr_addr_family != target_addr_family) { | ||
| 1683 | if (verbose >= 1) { | ||
| 1684 | printf("cidr address: %s and target ip address: %s have different address families\n", | ||
| 1685 | cidr_ip_part, target_ip); | ||
| 1686 | } | ||
| 1687 | free(cidr_ip_part); | ||
| 1688 | result.inside = false; | ||
| 1689 | return result; | ||
| 1690 | } | ||
| 1691 | |||
| 1692 | // If no prefix is given, treat the cidr as a single address (full-length prefix) | ||
| 1693 | if (slash_count == 0) { | ||
| 1694 | prefix_length = (cidr_addr_family == AF_INET) ? 32 : 128; | ||
| 1695 | } | ||
| 1696 | |||
| 1697 | int max_bits = (cidr_addr_family == AF_INET) ? 32u : 128u; | ||
| 1698 | if (prefix_length < 0 || prefix_length > max_bits) { | ||
| 1699 | if (verbose >= 1) { | ||
| 1700 | printf("cidr_region_or_ip: %s has invalid prefix length: %u\n", cidr_region_or_ip_addr, | ||
| 1701 | prefix_length); | ||
| 1702 | } | ||
| 1703 | free(cidr_ip_part); | ||
| 1704 | result.error = CIDR_REGION_INVALID_PREFIX; | ||
| 1705 | return result; | ||
| 1706 | } | ||
| 1707 | |||
| 1708 | if (verbose >= 1) { | ||
| 1709 | printf("cidr_region_or_ip: %s , has prefix length: %u\n", cidr_region_or_ip_addr, | ||
| 1710 | prefix_length); | ||
| 1711 | } | ||
| 1712 | |||
| 1713 | int inet_pton_rc; | ||
| 1714 | uint8_t *cidr_bytes = NULL; | ||
| 1715 | uint8_t *target_bytes = NULL; | ||
| 1716 | uint8_t cidr_buf[16]; | ||
| 1717 | uint8_t target_buf[16]; | ||
| 1718 | |||
| 1719 | if (cidr_addr_family == AF_INET) { | ||
| 1720 | struct in_addr cidr_ipv4; | ||
| 1721 | struct in_addr target_ipv4; | ||
| 1722 | inet_pton_rc = inet_pton(AF_INET, cidr_ip_part, &cidr_ipv4); | ||
| 1723 | if (inet_pton_rc != 1) { | ||
| 1724 | if (verbose >= 1) { | ||
| 1725 | printf("ip string: %s contains characters not valid for its address family: IPv4\n", | ||
| 1726 | cidr_ip_part); | ||
| 1727 | } | ||
| 1728 | free(cidr_ip_part); | ||
| 1729 | result.error = IP_CONTAINS_INVALID_CHARACTERS; | ||
| 1730 | return result; | ||
| 1731 | } | ||
| 1732 | inet_pton_rc = inet_pton(AF_INET, target_ip, &target_ipv4); | ||
| 1733 | if (inet_pton_rc != 1) { | ||
| 1734 | if (verbose >= 1) { | ||
| 1735 | printf("ip string: %s contains characters not valid for its address family: IPv4\n", | ||
| 1736 | target_ip); | ||
| 1737 | } | ||
| 1738 | free(cidr_ip_part); | ||
| 1739 | result.error = IP_CONTAINS_INVALID_CHARACTERS; | ||
| 1740 | return result; | ||
| 1741 | } | ||
| 1742 | // copy the addresses in network byte order to a buffer for comparison | ||
| 1743 | memcpy(cidr_buf, &cidr_ipv4.s_addr, 4); | ||
| 1744 | memcpy(target_buf, &target_ipv4.s_addr, 4); | ||
| 1745 | cidr_bytes = cidr_buf; | ||
| 1746 | target_bytes = target_buf; | ||
| 1747 | } else { | ||
| 1748 | struct in6_addr cidr_ipv6; | ||
| 1749 | struct in6_addr target_ipv6; | ||
| 1750 | inet_pton_rc = inet_pton(AF_INET6, cidr_ip_part, &cidr_ipv6); | ||
| 1751 | if (inet_pton_rc != 1) { | ||
| 1752 | if (verbose >= 1) { | ||
| 1753 | printf("ip string: %s contains characters not valid for its address family: IPv6\n", | ||
| 1754 | cidr_ip_part); | ||
| 1755 | } | ||
| 1756 | free(cidr_ip_part); | ||
| 1757 | result.error = IP_CONTAINS_INVALID_CHARACTERS; | ||
| 1758 | return result; | ||
| 1759 | } | ||
| 1760 | inet_pton_rc = inet_pton(AF_INET6, target_ip, &target_ipv6); | ||
| 1761 | if (inet_pton_rc != 1) { | ||
| 1762 | if (verbose >= 1) { | ||
| 1763 | printf("ip string: %s contains characters not valid for its address family: IPv6\n", | ||
| 1764 | target_ip); | ||
| 1765 | } | ||
| 1766 | free(cidr_ip_part); | ||
| 1767 | result.error = IP_CONTAINS_INVALID_CHARACTERS; | ||
| 1768 | return result; | ||
| 1769 | } | ||
| 1770 | memcpy(cidr_buf, &cidr_ipv6, 16); | ||
| 1771 | memcpy(target_buf, &target_ipv6, 16); | ||
| 1772 | cidr_bytes = cidr_buf; | ||
| 1773 | target_bytes = target_buf; | ||
| 1774 | } | ||
| 1775 | |||
| 1776 | int prefix_bytes = prefix_length / 8; | ||
| 1777 | int prefix_bits = prefix_length % 8; | ||
| 1778 | |||
| 1779 | if (prefix_bytes > 0) { | ||
| 1780 | if (memcmp(cidr_bytes, target_bytes, (size_t)prefix_bytes) != 0) { | ||
| 1781 | if (verbose >= 1) { | ||
| 1782 | printf("the first %d bytes of the cidr_region_or_ip: %s and target_ip: %s are " | ||
| 1783 | "different\n", | ||
| 1784 | prefix_bytes, cidr_ip_part, target_ip); | ||
| 1785 | } | ||
| 1786 | free(cidr_ip_part); | ||
| 1787 | result.inside = false; | ||
| 1788 | return result; | ||
| 1789 | } | ||
| 1790 | } | ||
| 1791 | |||
| 1792 | if (prefix_bits != 0) { | ||
| 1793 | uint8_t cidr_oct = cidr_bytes[prefix_bytes]; | ||
| 1794 | uint8_t target_oct = target_bytes[prefix_bytes]; | ||
| 1795 | // the mask has first prefix_bits bits 1, the rest as 0 | ||
| 1796 | uint8_t mask = (uint8_t)(0xFFu << (8 - prefix_bits)); | ||
| 1797 | if ((cidr_oct & mask) != (target_oct & mask)) { | ||
| 1798 | if (verbose >= 1) { | ||
| 1799 | printf("looking at the last %d bits of the prefix, cidr_region_or_ip(%s) byte is: " | ||
| 1800 | "%u and target_ip byte(%s) is: %u, applying bitmask: %02X returns different " | ||
| 1801 | "results\n", | ||
| 1802 | prefix_bits, cidr_ip_part, (unsigned)cidr_oct, target_ip, | ||
| 1803 | (unsigned)target_oct, mask); | ||
| 1804 | } | ||
| 1805 | free(cidr_ip_part); | ||
| 1806 | result.inside = false; | ||
| 1807 | return result; | ||
| 1808 | } | ||
| 1809 | } | ||
| 1810 | |||
| 1811 | free(cidr_ip_part); | ||
| 1812 | result.inside = true; | ||
| 1813 | return result; | ||
| 1814 | } | ||
diff --git a/plugins/check_curl.d/check_curl_helpers.h b/plugins/check_curl.d/check_curl_helpers.h index e77b763b..55df9bc1 100644 --- a/plugins/check_curl.d/check_curl_helpers.h +++ b/plugins/check_curl.d/check_curl_helpers.h | |||
| @@ -126,3 +126,26 @@ void test_file(char *path); | |||
| 126 | mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_till_exp, | 126 | mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_till_exp, |
| 127 | int crit_days_till_exp); | 127 | int crit_days_till_exp); |
| 128 | char *fmt_url(check_curl_working_state workingState); | 128 | char *fmt_url(check_curl_working_state workingState); |
| 129 | |||
| 130 | /* determine_hostname_resolver determines if the host or the proxy resolves the target hostname | ||
| 131 | returns RESOLVE_LOCALLY if requester resolves the hostname locally, RESOLVE_REMOTELY if proxy | ||
| 132 | resolves the hostname */ | ||
| 133 | bool hostname_gets_resolved_locally(const check_curl_working_state working_state); | ||
| 134 | |||
| 135 | /* Checks if an IP is inside given CIDR region. Using /protocol_size or not specifying the prefix | ||
| 136 | length performs an equality check. Supports both IPv4 and IPv6 returns 1 if the target_ip address is | ||
| 137 | inside the given cidr_region_or_ip_addr, 0 if its out. return codes < 0 mean an error has occurred. | ||
| 138 | */ | ||
| 139 | typedef enum { | ||
| 140 | NO_ERROR, | ||
| 141 | FAILED_STRDUP, | ||
| 142 | COULD_NOT_PARSE_SUBNET_LENGTH, | ||
| 143 | CIDR_REGION_INVALID, | ||
| 144 | CIDR_REGION_INVALID_PREFIX, | ||
| 145 | IP_CONTAINS_INVALID_CHARACTERS, | ||
| 146 | } ip_addr_inside_error_code; | ||
| 147 | typedef struct { | ||
| 148 | bool inside; | ||
| 149 | ip_addr_inside_error_code error; | ||
| 150 | } ip_addr_inside; | ||
| 151 | ip_addr_inside 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 { | |||
| 48 | 48 | ||
| 49 | bool use_ssl; | 49 | bool use_ssl; |
| 50 | bool no_body; | 50 | bool no_body; |
| 51 | |||
| 52 | /* curl CURLOPT_PROXY option will be set to this value if not NULL */ | ||
| 53 | char *curlopt_proxy; | ||
| 54 | /* curl CURLOPT_NOPROXY option will be set to this value if not NULL */ | ||
| 55 | char *curlopt_noproxy; | ||
| 51 | } check_curl_working_state; | 56 | } check_curl_working_state; |
| 52 | 57 | ||
| 53 | check_curl_working_state check_curl_working_state_init(); | 58 | check_curl_working_state check_curl_working_state_init(); |
| @@ -65,6 +70,8 @@ typedef struct { | |||
| 65 | char *client_privkey; | 70 | char *client_privkey; |
| 66 | char *ca_cert; | 71 | char *ca_cert; |
| 67 | bool verify_peer_and_host; | 72 | bool verify_peer_and_host; |
| 73 | char proxy[DEFAULT_BUFFER_SIZE]; | ||
| 74 | char no_proxy[DEFAULT_BUFFER_SIZE]; | ||
| 68 | char user_agent[DEFAULT_BUFFER_SIZE]; | 75 | char user_agent[DEFAULT_BUFFER_SIZE]; |
| 69 | char proxy_auth[MAX_INPUT_BUFFER]; | 76 | char proxy_auth[MAX_INPUT_BUFFER]; |
| 70 | char user_auth[MAX_INPUT_BUFFER]; | 77 | char user_auth[MAX_INPUT_BUFFER]; |
