From cef40299a93233f043f5b0821a9ad2c69dd612f7 Mon Sep 17 00:00:00 2001 From: Alvar Date: Fri, 6 Feb 2026 11:58:38 +0000 Subject: OpenBSD: pledge(2) some network-facing checks (#2225) OpenBSD's pledge(2) system call allows the current process to self-restrict itself, being reduced to promised pledges. For example, unless a process says it wants to write to files, it is not allowed to do so any longer. This change starts by calling pledge(2) in some network-facing checks, removing the more dangerous privileges, such as executing other files. My initial motivation came from check_icmp, being installed as a setuid binary and (temporarily) running with root privileges. There, the pledge(2) calls result in check_icmp to only being allowed to interact with the network and to setuid(2) to the calling user later on. Afterwards, I went through my most commonly used monitoring plugins directly interacting with the network. Thus, I continued with pledge(2)-ing check_curl - having a huge codebase and all -, check_ntp_time, check_smtp, check_ssh, and check_tcp. For most of those, the changes were quite similar: start with network-friendly promises, parse the configuration, give up file access, and proceed with the actual check. --- plugins/check_curl.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'plugins/check_curl.c') diff --git a/plugins/check_curl.c b/plugins/check_curl.c index 1dec8a2a..19d36237 100644 --- a/plugins/check_curl.c +++ b/plugins/check_curl.c @@ -120,6 +120,14 @@ mp_state_enum np_net_ssl_check_certificate(X509 *certificate, int days_till_exp_ #endif /* defined(HAVE_SSL) && defined(USE_OPENSSL) */ int main(int argc, char **argv) { +#ifdef __OpenBSD__ + /* - rpath is required to read --extra-opts, CA and/or client certs + * - wpath is required to write --cookie-jar (possibly given up later) + * - inet is required for sockets + * - dns is required for name lookups */ + pledge("stdio rpath wpath inet dns", NULL); +#endif // __OpenBSD__ + setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); @@ -135,6 +143,15 @@ int main(int argc, char **argv) { const check_curl_config config = tmp_config.config; +#ifdef __OpenBSD__ + if (!config.curl_config.cookie_jar_file) { + if (verbose >= 2) { + printf(_("* No \"--cookie-jar\" is used, giving up \"wpath\" pledge(2)\n")); + } + pledge("stdio rpath inet dns", NULL); + } +#endif // __OpenBSD__ + if (config.output_format_is_set) { mp_set_format(config.output_format); } -- cgit v1.2.3-74-g34f1 From 0f0865c910096c95594ac09929708e84934e46df Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:59:58 +0100 Subject: Make IPv6 unconditional (#2219) This commits removes the detection of IPv6 availability. The IPv6 code in the plugins is used unconditionally now. --- configure.ac | 24 ------------------------ plugins/check_curl.c | 2 +- plugins/check_curl.d/check_curl_helpers.c | 2 +- plugins/check_http.c | 4 ---- plugins/check_ldap.c | 4 ---- plugins/check_ntp_peer.c | 4 ---- plugins/check_ntp_time.c | 4 ---- plugins/check_ping.c | 4 ---- plugins/check_smtp.c | 4 ---- plugins/check_ssh.c | 4 ---- plugins/check_tcp.c | 4 ---- plugins/netutils.c | 10 ---------- plugins/netutils.h | 4 ---- 13 files changed, 2 insertions(+), 72 deletions(-) (limited to 'plugins/check_curl.c') diff --git a/configure.ac b/configure.ac index 7361434a..ae7eb30b 100644 --- a/configure.ac +++ b/configure.ac @@ -475,30 +475,6 @@ AC_ARG_WITH([ipv6], [AS_HELP_STRING([--with-ipv6], [support IPv6 @<:@default=check@:>@])], [], [with_ipv6=check]) -dnl Check for AF_INET6 support - unistd.h required for Darwin -if test "$with_ipv6" != "no"; then - AC_CACHE_CHECK([for IPv6 support], np_cv_sys_ipv6, [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#ifdef HAVE_UNISTD_H - #include - #endif - #include - #include ]], [[struct sockaddr_in6 sin6; - void *p; - - sin6.sin6_family = AF_INET6; - sin6.sin6_port = 587; - p = &sin6.sin6_addr;]])],[np_cv_sys_ipv6=yes],[np_cv_sys_ipv6=no]) - ]) - if test "$np_cv_sys_ipv6" = "no" -a "$with_ipv6" != "check"; then - AC_MSG_FAILURE([--with-ipv6 was given, but test for IPv6 support failed]) - fi - if test "$np_cv_sys_ipv6" = "yes"; then - AC_DEFINE(USE_IPV6,1,[Enable IPv6 support]) - fi - with_ipv6="$np_cv_sys_ipv6" -fi - - dnl Checks for Kerberos. Must come before openssl checks for Redhat EL 3 AC_CHECK_HEADERS(krb5.h,FOUNDINCLUDE=yes,FOUNDINCLUDE=no) if test "$FOUNDINCLUDE" = "no"; then diff --git a/plugins/check_curl.c b/plugins/check_curl.c index 19d36237..95e45282 100644 --- a/plugins/check_curl.c +++ b/plugins/check_curl.c @@ -1265,7 +1265,7 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { result.config.curl_config.sin_family = AF_INET; break; case '6': -#if defined(USE_IPV6) && defined(LIBCURL_FEATURE_IPV6) +#if defined(LIBCURL_FEATURE_IPV6) result.config.curl_config.sin_family = AF_INET6; #else usage4(_("IPv6 support not available")); diff --git a/plugins/check_curl.d/check_curl_helpers.c b/plugins/check_curl.d/check_curl_helpers.c index 5af00973..ad31b847 100644 --- a/plugins/check_curl.d/check_curl_helpers.c +++ b/plugins/check_curl.d/check_curl_helpers.c @@ -488,7 +488,7 @@ check_curl_configure_curl(const check_curl_static_curl_config config, curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4), "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V4)"); } -#if defined(USE_IPV6) && defined(LIBCURL_FEATURE_IPV6) +#if defined(LIBCURL_FEATURE_IPV6) else if (config.sin_family == AF_INET6) { handle_curl_option_return_code( curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6), diff --git a/plugins/check_http.c b/plugins/check_http.c index d2f080c7..71f94b91 100644 --- a/plugins/check_http.c +++ b/plugins/check_http.c @@ -544,11 +544,7 @@ bool process_arguments(int argc, char **argv) { address_family = AF_INET; break; case '6': -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage4(_("IPv6 support not available")); -#endif break; case 'v': /* verbose */ verbose = true; diff --git a/plugins/check_ldap.c b/plugins/check_ldap.c index 1b2e2826..333dae41 100644 --- a/plugins/check_ldap.c +++ b/plugins/check_ldap.c @@ -462,11 +462,7 @@ check_ldap_config_wrapper process_arguments(int argc, char **argv) { } break; case '6': -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage(_("IPv6 support not available\n")); -#endif break; case output_format_index: { parsed_output_format parser = mp_parse_output_format(optarg); diff --git a/plugins/check_ntp_peer.c b/plugins/check_ntp_peer.c index 26f74286..06737a27 100644 --- a/plugins/check_ntp_peer.c +++ b/plugins/check_ntp_peer.c @@ -640,11 +640,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { address_family = AF_INET; break; case '6': -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage4(_("IPv6 support not available")); -#endif break; case '?': /* print short usage statement if args not parsable */ diff --git a/plugins/check_ntp_time.c b/plugins/check_ntp_time.c index afa6d16c..5955d22e 100644 --- a/plugins/check_ntp_time.c +++ b/plugins/check_ntp_time.c @@ -640,11 +640,7 @@ static check_ntp_time_config_wrapper process_arguments(int argc, char **argv) { address_family = AF_INET; break; case '6': -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage4(_("IPv6 support not available")); -#endif break; case '?': /* print short usage statement if args not parsable */ diff --git a/plugins/check_ping.c b/plugins/check_ping.c index 61feb958..e1ee0f5c 100644 --- a/plugins/check_ping.c +++ b/plugins/check_ping.c @@ -246,11 +246,7 @@ check_ping_config_wrapper process_arguments(int argc, char **argv) { address_family = AF_INET; break; case '6': /* IPv6 only */ -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage(_("IPv6 support not available\n")); -#endif break; case 'H': /* hostname */ { char *ptr = optarg; diff --git a/plugins/check_smtp.c b/plugins/check_smtp.c index 03335665..701af7b0 100644 --- a/plugins/check_smtp.c +++ b/plugins/check_smtp.c @@ -819,11 +819,7 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) { address_family = AF_INET; break; case '6': -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage4(_("IPv6 support not available")); -#endif break; case 'V': /* version */ print_revision(progname, NP_VERSION); diff --git a/plugins/check_ssh.c b/plugins/check_ssh.c index 84b70a53..911f6787 100644 --- a/plugins/check_ssh.c +++ b/plugins/check_ssh.c @@ -173,11 +173,7 @@ process_arguments_wrapper process_arguments(int argc, char **argv) { address_family = AF_INET; break; case '6': -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage4(_("IPv6 support not available")); -#endif break; case 'r': /* remote version */ result.config.remote_version = optarg; diff --git a/plugins/check_tcp.c b/plugins/check_tcp.c index 430f1218..49a8c4c1 100644 --- a/plugins/check_tcp.c +++ b/plugins/check_tcp.c @@ -583,11 +583,7 @@ static check_tcp_config_wrapper process_arguments(int argc, char **argv, check_t address_family = AF_INET; break; case '6': // Apparently unused TODO -#ifdef USE_IPV6 address_family = AF_INET6; -#else - usage4(_("IPv6 support not available")); -#endif break; case 'H': /* hostname */ config.host_specified = true; diff --git a/plugins/netutils.c b/plugins/netutils.c index b4c6ff0a..f9933ebd 100644 --- a/plugins/netutils.c +++ b/plugins/netutils.c @@ -38,11 +38,7 @@ mp_state_enum socket_timeout_state = STATE_CRITICAL; mp_state_enum econn_refuse_state = STATE_CRITICAL; bool was_refused = false; -#if USE_IPV6 int address_family = AF_UNSPEC; -#else -int address_family = AF_INET; -#endif /* handles socket timeouts */ void socket_timeout_alarm_handler(int sig) { @@ -348,7 +344,6 @@ void host_or_die(const char *str) { } bool is_addr(const char *address) { -#ifdef USE_IPV6 if (address_family == AF_INET && is_inet_addr(address)) { return true; } @@ -356,11 +351,6 @@ bool is_addr(const char *address) { if (address_family == AF_INET6 && is_inet6_addr(address)) { return true; } -#else - if (is_inet_addr(address)) { - return true; - } -#endif return false; } diff --git a/plugins/netutils.h b/plugins/netutils.h index dbd22398..f3d046c3 100644 --- a/plugins/netutils.h +++ b/plugins/netutils.h @@ -78,12 +78,8 @@ bool dns_lookup(const char *, struct sockaddr_storage *, int); void host_or_die(const char *str); #define resolve_host_or_addr(addr, family) dns_lookup(addr, NULL, family) #define is_inet_addr(addr) resolve_host_or_addr(addr, AF_INET) -#ifdef USE_IPV6 # define is_inet6_addr(addr) resolve_host_or_addr(addr, AF_INET6) # define is_hostname(addr) resolve_host_or_addr(addr, address_family) -#else -# define is_hostname(addr) resolve_host_or_addr(addr, AF_INET) -#endif extern unsigned int socket_timeout; extern mp_state_enum socket_timeout_state; -- cgit v1.2.3-74-g34f1 From 07a249a5d74a980ca78cead569de5351963cc561 Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Mon, 16 Feb 2026 11:22:39 +0100 Subject: Fix typo in enum MP_PARSING_SUCCES(S) (#2233) --- lib/perfdata.c | 14 +++++++------- lib/perfdata.h | 3 ++- plugins/check_curl.c | 6 +++--- plugins/check_dbi.c | 4 ++-- plugins/check_disk.c | 4 ++-- plugins/check_ldap.c | 8 ++++---- plugins/check_mrtg.c | 8 ++++---- plugins/check_mysql.c | 4 ++-- plugins/check_mysql_query.c | 4 ++-- plugins/check_ntp_peer.c | 16 ++++++++-------- plugins/check_ntp_time.c | 4 ++-- plugins/check_pgsql.c | 8 ++++---- plugins/check_real.c | 4 ++-- plugins/check_smtp.c | 4 ++-- plugins/check_snmp.d/check_snmp_helpers.c | 4 ++-- plugins/check_users.c | 4 ++-- 16 files changed, 50 insertions(+), 49 deletions(-) (limited to 'plugins/check_curl.c') diff --git a/lib/perfdata.c b/lib/perfdata.c index b447b8ea..138df53b 100644 --- a/lib/perfdata.c +++ b/lib/perfdata.c @@ -393,7 +393,7 @@ mp_range_parsed mp_parse_range_string(const char *input) { mp_range_parsed result = { .range = mp_range_init(), - .error = MP_PARSING_SUCCES, + .error = MP_PARSING_SUCCESS, }; if (input[0] == '@') { @@ -436,7 +436,7 @@ mp_range_parsed mp_parse_range_string(const char *input) { result.range.start_infinity = false; perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input); - if (parsed_pd.error != MP_PARSING_SUCCES) { + if (parsed_pd.error != MP_PARSING_SUCCESS) { result.error = parsed_pd.error; free(working_copy); return result; @@ -457,7 +457,7 @@ mp_range_parsed mp_parse_range_string(const char *input) { } else { perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input); - if (parsed_pd.error != MP_PARSING_SUCCES) { + if (parsed_pd.error != MP_PARSING_SUCCESS) { result.error = parsed_pd.error; return result; } @@ -470,7 +470,7 @@ mp_range_parsed mp_parse_range_string(const char *input) { double_parser_wrapper parse_double(const char *input) { double_parser_wrapper result = { - .error = MP_PARSING_SUCCES, + .error = MP_PARSING_SUCCESS, }; if (input == NULL) { @@ -501,7 +501,7 @@ double_parser_wrapper parse_double(const char *input) { integer_parser_wrapper parse_integer(const char *input) { integer_parser_wrapper result = { - .error = MP_PARSING_SUCCES, + .error = MP_PARSING_SUCCESS, }; if (input == NULL) { @@ -548,7 +548,7 @@ perfdata_value_parser_wrapper parse_pd_value(const char *input) { // try integer first integer_parser_wrapper tmp_int = parse_integer(input); - if (tmp_int.error == MP_PARSING_SUCCES) { + if (tmp_int.error == MP_PARSING_SUCCESS) { perfdata_value_parser_wrapper result = { .error = tmp_int.error, .value = tmp_int.value, @@ -558,7 +558,7 @@ perfdata_value_parser_wrapper parse_pd_value(const char *input) { double_parser_wrapper tmp_double = parse_double(input); perfdata_value_parser_wrapper result = {}; - if (tmp_double.error == MP_PARSING_SUCCES) { + if (tmp_double.error == MP_PARSING_SUCCESS) { result.error = tmp_double.error; result.value = tmp_double.value; } else { diff --git a/lib/perfdata.h b/lib/perfdata.h index e51ef5fd..d7e5670a 100644 --- a/lib/perfdata.h +++ b/lib/perfdata.h @@ -111,7 +111,8 @@ mp_range mp_range_set_end(mp_range, mp_perfdata_value); */ typedef enum { - MP_PARSING_SUCCES = 0, + MP_PARSING_SUCCESS = 0, + MP_PARSING_SUCCES = MP_PARSING_SUCCESS, MP_PARSING_FAILURE, MP_RANGE_PARSING_FAILURE, MP_RANGE_PARSING_UNDERFLOW, diff --git a/plugins/check_curl.c b/plugins/check_curl.c index 95e45282..d7d68de5 100644 --- a/plugins/check_curl.c +++ b/plugins/check_curl.c @@ -990,7 +990,7 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { case 'c': /* critical time threshold */ { mp_range_parsed critical_range = mp_parse_range_string(optarg); - if (critical_range.error != MP_PARSING_SUCCES) { + if (critical_range.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical threshold: %s", optarg); } result.config.thlds = mp_thresholds_set_crit(result.config.thlds, critical_range.range); @@ -999,7 +999,7 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { { mp_range_parsed warning_range = mp_parse_range_string(optarg); - if (warning_range.error != MP_PARSING_SUCCES) { + if (warning_range.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold: %s", optarg); } result.config.thlds = mp_thresholds_set_warn(result.config.thlds, warning_range.range); @@ -1275,7 +1275,7 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) { { mp_range_parsed foo = mp_parse_range_string(optarg); - if (foo.error != MP_PARSING_SUCCES) { + if (foo.error != MP_PARSING_SUCCESS) { die(STATE_CRITICAL, "failed to parse page size limits: %s", optarg); } diff --git a/plugins/check_dbi.c b/plugins/check_dbi.c index 81d92952..dd466d00 100644 --- a/plugins/check_dbi.c +++ b/plugins/check_dbi.c @@ -470,7 +470,7 @@ check_dbi_config_wrapper process_arguments(int argc, char **argv) { case 'c': /* critical range */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical threshold"); } result.config.thresholds = mp_thresholds_set_crit(result.config.thresholds, tmp.range); @@ -478,7 +478,7 @@ check_dbi_config_wrapper process_arguments(int argc, char **argv) { } break; case 'w': /* warning range */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold"); } result.config.thresholds = mp_thresholds_set_warn(result.config.thresholds, tmp.range); diff --git a/plugins/check_disk.c b/plugins/check_disk.c index e1a2baff..0d941f25 100644 --- a/plugins/check_disk.c +++ b/plugins/check_disk.c @@ -838,7 +838,7 @@ check_disk_config_wrapper process_arguments(int argc, char **argv) { } char *range = argv[index++]; mp_range_parsed tmp = mp_parse_range_string(range); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold"); } @@ -859,7 +859,7 @@ check_disk_config_wrapper process_arguments(int argc, char **argv) { } char *range = argv[index++]; mp_range_parsed tmp = mp_parse_range_string(range); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold"); } diff --git a/plugins/check_ldap.c b/plugins/check_ldap.c index 333dae41..7f8282b4 100644 --- a/plugins/check_ldap.c +++ b/plugins/check_ldap.c @@ -400,7 +400,7 @@ check_ldap_config_wrapper process_arguments(int argc, char **argv) { break; case 'w': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning connection time threshold"); } result.config.connection_time_threshold = @@ -408,7 +408,7 @@ check_ldap_config_wrapper process_arguments(int argc, char **argv) { } break; case 'c': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical connection time threshold"); } result.config.connection_time_threshold = @@ -416,7 +416,7 @@ check_ldap_config_wrapper process_arguments(int argc, char **argv) { } break; case 'W': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse number of entries warning threshold"); } result.config.entries_thresholds = @@ -424,7 +424,7 @@ check_ldap_config_wrapper process_arguments(int argc, char **argv) { } break; case 'C': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse number of entries critical threshold"); } result.config.entries_thresholds = diff --git a/plugins/check_mrtg.c b/plugins/check_mrtg.c index cdc2a035..bb38fcc5 100644 --- a/plugins/check_mrtg.c +++ b/plugins/check_mrtg.c @@ -255,7 +255,7 @@ check_mrtg_config_wrapper process_arguments(int argc, char **argv) { break; case 'w': /* critical time threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold"); } result.config.values_threshold = @@ -263,7 +263,7 @@ check_mrtg_config_wrapper process_arguments(int argc, char **argv) { } break; case 'c': /* warning time threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical threshold"); } result.config.values_threshold = @@ -330,7 +330,7 @@ check_mrtg_config_wrapper process_arguments(int argc, char **argv) { if (argc > option_char && !result.config.values_threshold.warning_is_set) { mp_range_parsed tmp = mp_parse_range_string(argv[option_char++]); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold"); } result.config.values_threshold = @@ -339,7 +339,7 @@ check_mrtg_config_wrapper process_arguments(int argc, char **argv) { if (argc > option_char && !result.config.values_threshold.critical_is_set) { mp_range_parsed tmp = mp_parse_range_string(argv[option_char++]); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical threshold"); } result.config.values_threshold = diff --git a/plugins/check_mysql.c b/plugins/check_mysql.c index 26730d4c..15005bf5 100644 --- a/plugins/check_mysql.c +++ b/plugins/check_mysql.c @@ -572,7 +572,7 @@ check_mysql_config_wrapper process_arguments(int argc, char **argv) { break; case 'w': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning time threshold"); } result.config.replica_thresholds = @@ -580,7 +580,7 @@ check_mysql_config_wrapper process_arguments(int argc, char **argv) { } break; case 'c': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical time threshold"); } result.config.replica_thresholds = diff --git a/plugins/check_mysql_query.c b/plugins/check_mysql_query.c index ae6cc15d..fc0966d3 100644 --- a/plugins/check_mysql_query.c +++ b/plugins/check_mysql_query.c @@ -277,14 +277,14 @@ check_mysql_query_config_wrapper process_arguments(int argc, char **argv) { break; case 'w': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold"); } result.config.thresholds = mp_thresholds_set_warn(result.config.thresholds, tmp.range); } break; case 'c': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical threshold"); } result.config.thresholds = mp_thresholds_set_crit(result.config.thresholds, tmp.range); diff --git a/plugins/check_ntp_peer.c b/plugins/check_ntp_peer.c index 06737a27..b5cfb460 100644 --- a/plugins/check_ntp_peer.c +++ b/plugins/check_ntp_peer.c @@ -548,7 +548,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { break; case 'w': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning offset threshold"); } @@ -557,7 +557,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { } break; case 'c': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical offset threshold"); } @@ -567,7 +567,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { case 'W': { result.config.do_stratum = true; mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning stratum threshold"); } @@ -577,7 +577,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { case 'C': { result.config.do_stratum = true; mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical stratum threshold"); } @@ -587,7 +587,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { case 'j': { result.config.do_jitter = true; mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning jitter threshold"); } @@ -597,7 +597,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { case 'k': { result.config.do_jitter = true; mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical jitter threshold"); } @@ -607,7 +607,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { case 'm': { result.config.do_truechimers = true; mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning truechimer threshold"); } @@ -617,7 +617,7 @@ check_ntp_peer_config_wrapper process_arguments(int argc, char **argv) { case 'n': { result.config.do_truechimers = true; mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical truechimer threshold"); } diff --git a/plugins/check_ntp_time.c b/plugins/check_ntp_time.c index 5955d22e..4e3a55db 100644 --- a/plugins/check_ntp_time.c +++ b/plugins/check_ntp_time.c @@ -605,7 +605,7 @@ static check_ntp_time_config_wrapper process_arguments(int argc, char **argv) { break; case 'w': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold"); } @@ -614,7 +614,7 @@ static check_ntp_time_config_wrapper process_arguments(int argc, char **argv) { } break; case 'c': { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse crit threshold"); } diff --git a/plugins/check_pgsql.c b/plugins/check_pgsql.c index 0ce75e0a..8cbaaeeb 100644 --- a/plugins/check_pgsql.c +++ b/plugins/check_pgsql.c @@ -401,7 +401,7 @@ static check_pgsql_config_wrapper process_arguments(int argc, char **argv) { break; case 'c': /* critical time threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical time threshold"); } result.config.time_thresholds = @@ -409,7 +409,7 @@ static check_pgsql_config_wrapper process_arguments(int argc, char **argv) { } break; case 'w': /* warning time threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning time threshold"); } result.config.time_thresholds = @@ -417,7 +417,7 @@ static check_pgsql_config_wrapper process_arguments(int argc, char **argv) { } break; case 'C': /* critical query threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical query threshold"); } @@ -427,7 +427,7 @@ static check_pgsql_config_wrapper process_arguments(int argc, char **argv) { } break; case 'W': /* warning query threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning query threshold"); } result.config.qthresholds = diff --git a/plugins/check_real.c b/plugins/check_real.c index 15c8a20c..b415578f 100644 --- a/plugins/check_real.c +++ b/plugins/check_real.c @@ -409,7 +409,7 @@ check_real_config_wrapper process_arguments(int argc, char **argv) { case 'w': /* warning time threshold */ { mp_range_parsed critical_range = mp_parse_range_string(optarg); - if (critical_range.error != MP_PARSING_SUCCES) { + if (critical_range.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning threshold: %s", optarg); } result.config.time_thresholds = @@ -418,7 +418,7 @@ check_real_config_wrapper process_arguments(int argc, char **argv) { case 'c': /* critical time threshold */ { mp_range_parsed critical_range = mp_parse_range_string(optarg); - if (critical_range.error != MP_PARSING_SUCCES) { + if (critical_range.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical threshold: %s", optarg); } result.config.time_thresholds = diff --git a/plugins/check_smtp.c b/plugins/check_smtp.c index 701af7b0..24883fd8 100644 --- a/plugins/check_smtp.c +++ b/plugins/check_smtp.c @@ -735,7 +735,7 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) { break; case 'c': /* critical time threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse critical time threshold"); } result.config.connection_time = @@ -743,7 +743,7 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) { } break; case 'w': /* warning time threshold */ { mp_range_parsed tmp = mp_parse_range_string(optarg); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "failed to parse warning time threshold"); } result.config.connection_time = diff --git a/plugins/check_snmp.d/check_snmp_helpers.c b/plugins/check_snmp.d/check_snmp_helpers.c index 2dfc88b5..83e94a34 100644 --- a/plugins/check_snmp.d/check_snmp_helpers.c +++ b/plugins/check_snmp.d/check_snmp_helpers.c @@ -52,7 +52,7 @@ int check_snmp_set_thresholds(const char *threshold_string, check_snmp_test_unit } mp_range_parsed tmp = mp_parse_range_string(ptr); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "Unable to parse critical threshold range: %s", ptr); } @@ -70,7 +70,7 @@ int check_snmp_set_thresholds(const char *threshold_string, check_snmp_test_unit // Single value // only valid for the first test unit mp_range_parsed tmp = mp_parse_range_string(threshold_string); - if (tmp.error != MP_PARSING_SUCCES) { + if (tmp.error != MP_PARSING_SUCCESS) { die(STATE_UNKNOWN, "Unable to parse critical threshold range: %s", threshold_string); } diff --git a/plugins/check_users.c b/plugins/check_users.c index 3b2e265e..4027d21a 100644 --- a/plugins/check_users.c +++ b/plugins/check_users.c @@ -222,7 +222,7 @@ check_users_config_wrapper process_arguments(int argc, char **argv) { exit(STATE_UNKNOWN); } - if (tmp.error == MP_PARSING_SUCCES) { + if (tmp.error == MP_PARSING_SUCCESS) { result.config.thresholds.warning = tmp.range; result.config.thresholds.warning_is_set = true; } else { @@ -238,7 +238,7 @@ check_users_config_wrapper process_arguments(int argc, char **argv) { exit(STATE_UNKNOWN); } - if (tmp.error == MP_PARSING_SUCCES) { + if (tmp.error == MP_PARSING_SUCCESS) { result.config.thresholds.critical = tmp.range; result.config.thresholds.critical_is_set = true; } else { -- cgit v1.2.3-74-g34f1 From b9cd60ec3a2eaa8155286c6b2ee131030f7feec7 Mon Sep 17 00:00:00 2001 From: inqrphl <32687873+inqrphl@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:54:23 +0100 Subject: add proxy argument and improve dns cache usage (#2209) * add proxy argument and improve dns cache usage add proxy argument that useing the -x and --proxy argument. add it to the static curl config struct, command usage and help outputs of the cli. parse these argument together with the environment variables like http_proxy before setting the CURLOPT_PROXY in the curl configuration option. this is required, as there is no easy way to ascertain/get what the CURLOPT_PROXY that libcurl will use. by the point it is set by libcurl, we have no control over it anymore, and need it for the other steps in the configuration. if the CURLOPT_PROXY is set, skip the DNS cache population which would set the CURLOPT_RESOLVE. this is currently not perfect however. if a proxy is set with socks4 or socks5 scheme, the host should be resolving the hostname. * codespell, clang-format and hints fixes * add curl version and ssl enabelement macro checks might fix rocky linux 8 compilation issues. * add proxy_resolves_hostname, determined by proxy scheme leave the functions that print out an curl_easyoption, but dont use it. organize the code slightly, print out the final CURLOPT_PROXY and proxy_resolves_hostname flag on verbose mode, add comments * remove unused handle_curl_easyoption and format_curl_easyoption functions * fix typo in the proxy argument * fix typo with proxy scheme socks5a->socks5h * improve proxy environment parsing add another argument: --no-proxy , which is used when setting CURL_NOPROXY additionally parse all_proxy, ALL_PROXY, no_proxy and NO_PROXY environment variables in the correct order. set the curlopt_proxy and curlopt_noproxy of libcurl, and additionally save them in check_curl_working_state. add function determine_hostname_resolver, uses the working state and static config. it can tokenize the no_proxy variable and check for exact matches, but cannot determine subnet matches for ip addresses yet. * document proxy cli arguments clarify and add more examples of proxy environment variables and their behavior when multiple are specified, overriden etc. add single wildcard '*' checking for no_proxy to determine_hostname_resolver, special case per curlopt_noproxy documentation * check curlopt_noproxy before accessing it * switch argument from --no-proxy to --noproxy like curl cli * check if host name is a subdomain of an noproxy item * use strdup where destination working_state.curlopt_proxy may be NULL * add disclaimer about uppercase HTTP_PROXY * add subdomain checks for each item in the no_proxy, if the target host is a subdomain proxy wont resolve it add function ip_addr_inside_cidr, use it for checking possible cidr ranges given in the no_proxy * wip tests that work on local perl http/https server * wip tests that work on the live debian image * fix subnet definition * make apache2 listen on [::1] for ipv6 tests * remove squid certificate * rewrite ip_addr_inside_cidr, split ipv4 and ipv6 parsing path and copy them to a shared buffer later on for prefix check * Adapt tests for the squid sever, disable checking return code for socks 4/5 proxies. Squid does not support it, and we do not install a capable proxy for these schemes. * specify localhost acl and allow it through the proxy. used in check_curl tests * typo in comment * move function comments to header * fix failing tests * handle case where proxy is set as empty string * removed duplicate tests, corrected wrong comments * corrected some annotations * move docker apache subdomain setup files to /tools/subdomain1 * add a newline before dying in handle_curl_option_return_code * fix the -ssl better, now does not segfault on empty --ssl argument as well. --------- Co-authored-by: Ahmet Oeztuerk --- .github/NPTest.cache | 7 + .github/prepare_debian.sh | 21 +- plugins/check_curl.c | 91 ++++-- plugins/check_curl.d/check_curl_helpers.c | 455 +++++++++++++++++++++++++++++- plugins/check_curl.d/check_curl_helpers.h | 9 + plugins/check_curl.d/config.h | 7 + plugins/t/check_curl.t | 115 +++++++- plugins/tests/check_curl.t | 135 ++++++++- tools/squid.conf | 3 + tools/subdomain1/index.php | 1 + tools/subdomain1/subdomain1.conf | 22 ++ 11 files changed, 835 insertions(+), 31 deletions(-) create mode 100644 tools/subdomain1/index.php create mode 100644 tools/subdomain1/subdomain1.conf (limited to 'plugins/check_curl.c') 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 + -- cgit v1.2.3-74-g34f1 From a9e23d05a6c0180b76cc5dc796c8892aaa5ddd3e Mon Sep 17 00:00:00 2001 From: inqrphl <32687873+inqrphl@users.noreply.github.com> Date: Fri, 13 Mar 2026 17:06:59 +0100 Subject: check_curl: check certificates and exit before checking for curl_easy_perform result (#2239) * check certificates first, before the return code of curl_easy_perform * fix typo * simply the comment for the change details go into PR request. --- plugins/check_curl.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) (limited to 'plugins/check_curl.c') diff --git a/plugins/check_curl.c b/plugins/check_curl.c index 4a1fb647..f63cdea2 100644 --- a/plugins/check_curl.c +++ b/plugins/check_curl.c @@ -239,10 +239,35 @@ mp_subcheck check_http(const check_curl_config config, check_curl_working_state // ============== CURLcode res = curl_easy_perform(curl_state.curl); + if (verbose > 1) { + printf("* curl_easy_perform returned: %s\n", curl_easy_strerror(res)); + } + if (verbose >= 2 && workingState.http_post_data) { printf("**** REQUEST CONTENT ****\n%s\n", workingState.http_post_data); } + // curl_state is updated after curl_easy_perform, and with updated curl_state certificate checks can be done + // Check_http tries to check certs as early as possible, and exits with certificate check result by default. Behave similarly. +#ifdef LIBCURL_FEATURE_SSL + if (workingState.use_ssl && config.check_cert) { + if (verbose > 1) { + printf("* adding a subcheck for the certificate\n"); + } + mp_subcheck sc_certificate = check_curl_certificate_checks( + curl_state.curl, cert, config.days_till_exp_warn, config.days_till_exp_crit); + + mp_add_subcheck_to_subcheck(&sc_result, sc_certificate); + if (!config.continue_after_check_cert) { + if (verbose > 1) { + printf("* returning after adding the subcheck for certificate, continuing after " + "checking the certificate is turned off\n"); + } + return sc_result; + } + } +#endif + mp_subcheck sc_curl = mp_subcheck_init(); /* Curl errors, result in critical Nagios state */ @@ -283,18 +308,6 @@ mp_subcheck check_http(const check_curl_config config, check_curl_working_state // Evaluation // ========== -#ifdef LIBCURL_FEATURE_SSL - if (workingState.use_ssl && config.check_cert) { - mp_subcheck sc_certificate = check_curl_certificate_checks( - curl_state.curl, cert, config.days_till_exp_warn, config.days_till_exp_crit); - - mp_add_subcheck_to_subcheck(&sc_result, sc_certificate); - if (!config.continue_after_check_cert) { - return sc_result; - } - } -#endif - /* we got the data and we executed the request in a given time, so we can append * performance data to the answer always */ -- cgit v1.2.3-74-g34f1