summaryrefslogtreecommitdiffstats
path: root/plugins/check_curl.d
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/check_curl.d')
-rw-r--r--plugins/check_curl.d/check_curl_helpers.c1744
-rw-r--r--plugins/check_curl.d/check_curl_helpers.h137
-rw-r--r--plugins/check_curl.d/config.h123
3 files changed, 2004 insertions, 0 deletions
diff --git a/plugins/check_curl.d/check_curl_helpers.c b/plugins/check_curl.d/check_curl_helpers.c
new file mode 100644
index 00000000..4372dc0b
--- /dev/null
+++ b/plugins/check_curl.d/check_curl_helpers.c
@@ -0,0 +1,1744 @@
1#include "./check_curl_helpers.h"
2#include <stdbool.h>
3#include <arpa/inet.h>
4#include <netinet/in.h>
5#include <netdb.h>
6#include <stdint.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <sys/socket.h>
11#include "../utils.h"
12#include "check_curl.d/config.h"
13#include "output.h"
14#include "perfdata.h"
15#include "states.h"
16
17extern int verbose;
18char errbuf[MAX_INPUT_BUFFER];
19bool is_openssl_callback = false;
20bool add_sslctx_verify_fun = false;
21
22check_curl_configure_curl_wrapper
23check_curl_configure_curl(const check_curl_static_curl_config config,
24 check_curl_working_state working_state, bool check_cert,
25 bool on_redirect_dependent, int follow_method, long max_depth) {
26 check_curl_configure_curl_wrapper result = {
27 .errorcode = OK,
28 .curl_state =
29 {
30 .curl_global_initialized = false,
31 .curl_easy_initialized = false,
32 .curl = NULL,
33
34 .body_buf_initialized = false,
35 .body_buf = NULL,
36 .header_buf_initialized = false,
37 .header_buf = NULL,
38 .status_line_initialized = false,
39 .status_line = NULL,
40 .put_buf_initialized = false,
41 .put_buf = NULL,
42
43 .header_list = NULL,
44 .host = NULL,
45 },
46 };
47
48 if ((result.curl_state.status_line = calloc(1, sizeof(curlhelp_statusline))) == NULL) {
49 die(STATE_UNKNOWN, "HTTP UNKNOWN - allocation of statusline failed\n");
50 }
51
52 if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
53 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_global_init failed\n");
54 }
55 result.curl_state.curl_global_initialized = true;
56
57 if ((result.curl_state.curl = curl_easy_init()) == NULL) {
58 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_easy_init failed\n");
59 }
60 result.curl_state.curl_easy_initialized = true;
61
62 if (verbose >= 1) {
63 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_VERBOSE, 1L),
64 "CURLOPT_VERBOSE");
65 }
66
67 /* print everything on stdout like check_http would do */
68 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_STDERR, stdout),
69 "CURLOPT_STDERR");
70
71 if (config.automatic_decompression) {
72#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6)
73 handle_curl_option_return_code(
74 curl_easy_setopt(result.curl_state.curl, CURLOPT_ACCEPT_ENCODING, ""),
75 "CURLOPT_ACCEPT_ENCODING");
76#else
77 handle_curl_option_return_code(
78 curl_easy_setopt(result.curl_state.curl, CURLOPT_ENCODING, ""), "CURLOPT_ENCODING");
79#endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6) */
80 }
81
82 /* initialize buffer for body of the answer */
83 if (curlhelp_initwritebuffer(&result.curl_state.body_buf) < 0) {
84 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for body\n");
85 }
86 result.curl_state.body_buf_initialized = true;
87
88 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEFUNCTION,
89 curlhelp_buffer_write_callback),
90 "CURLOPT_WRITEFUNCTION");
91 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEDATA,
92 (void *)result.curl_state.body_buf),
93 "CURLOPT_WRITEDATA");
94
95 /* initialize buffer for header of the answer */
96 if (curlhelp_initwritebuffer(&result.curl_state.header_buf) < 0) {
97 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for header\n");
98 }
99 result.curl_state.header_buf_initialized = true;
100
101 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_HEADERFUNCTION,
102 curlhelp_buffer_write_callback),
103 "CURLOPT_HEADERFUNCTION");
104 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEHEADER,
105 (void *)result.curl_state.header_buf),
106 "CURLOPT_WRITEHEADER");
107
108 /* set the error buffer */
109 handle_curl_option_return_code(
110 curl_easy_setopt(result.curl_state.curl, CURLOPT_ERRORBUFFER, errbuf),
111 "CURLOPT_ERRORBUFFER");
112
113 /* set timeouts */
114 handle_curl_option_return_code(
115 curl_easy_setopt(result.curl_state.curl, CURLOPT_CONNECTTIMEOUT, config.socket_timeout),
116 "CURLOPT_CONNECTTIMEOUT");
117
118 handle_curl_option_return_code(
119 curl_easy_setopt(result.curl_state.curl, CURLOPT_TIMEOUT, config.socket_timeout),
120 "CURLOPT_TIMEOUT");
121
122 /* set proxy */
123 /* http(s) proxy can either be given from the command line, or taken from environment variables */
124 /* socks4(a) / socks5(h) proxy should be given using the command line */
125
126 /* first source to check is the environment variables */
127 /* lower case proxy environment variables are almost always accepted, while some programs also checking
128 uppercase ones. discover both, but take the lowercase one if both are present */
129
130 /* extra information: libcurl does not discover the uppercase version HTTP_PROXY due to security reasons */
131 /* https://github.com/curl/curl/blob/d445f2d930ae701039518d695481ee53b8490521/lib/url.c#L1987 */
132
133 /* first environment variable to read is all_proxy. it can be overridden by protocol specific environment variables */
134 char *all_proxy_env, *all_proxy_uppercase_env;
135 all_proxy_env = getenv("all_proxy");
136 all_proxy_uppercase_env = getenv("ALL_PROXY");
137 if (all_proxy_env != NULL && strlen(all_proxy_env)){
138 working_state.curlopt_proxy = strdup(all_proxy_env);
139 if (all_proxy_uppercase_env != NULL && verbose >= 1) {
140 printf("* cURL ignoring environment variable 'ALL_PROXY' as 'all_proxy' is set\n");
141 }
142 } else if (all_proxy_uppercase_env != NULL && strlen(all_proxy_uppercase_env) > 0) {
143 working_state.curlopt_proxy = strdup(all_proxy_uppercase_env);
144 }
145
146 /* second environment variable to read is http_proxy. only set curlopt_proxy if ssl is not toggled */
147 char *http_proxy_env, *http_proxy_uppercase_env;
148 http_proxy_env = getenv("http_proxy");
149 http_proxy_uppercase_env = getenv("HTTP_PROXY");
150 if (!working_state.use_ssl){
151 if (http_proxy_env != NULL && strlen(http_proxy_env) > 0) {
152 working_state.curlopt_proxy = strdup(http_proxy_env);
153 if (http_proxy_uppercase_env != NULL && verbose >= 1) {
154 printf("* cURL ignoring environment variable 'HTTP_PROXY' as 'http_proxy' is set\n");
155 }
156 } else if (http_proxy_uppercase_env != NULL && strlen(http_proxy_uppercase_env) > 0) {
157 working_state.curlopt_proxy = strdup(http_proxy_uppercase_env);
158 }
159 }
160#ifdef LIBCURL_FEATURE_SSL
161 /* optionally read https_proxy environment variable and set curlopt_proxy if ssl is toggled */
162 char *https_proxy_env, *https_proxy_uppercase_env;
163 https_proxy_env = getenv("https_proxy");
164 https_proxy_uppercase_env = getenv("HTTPS_PROXY");
165 if (working_state.use_ssl) {
166 if (https_proxy_env != NULL && strlen(https_proxy_env) > 0) {
167 working_state.curlopt_proxy = strdup(https_proxy_env);
168 if (https_proxy_uppercase_env != NULL && verbose >= 1) {
169 printf("* cURL ignoring environment variable 'HTTPS_PROXY' as 'https_proxy' is set\n");
170 }
171 }
172 else if (https_proxy_uppercase_env != NULL && strlen(https_proxy_uppercase_env) >= 0) {
173 working_state.curlopt_proxy = strdup(https_proxy_uppercase_env);
174 }
175 }
176#endif /* LIBCURL_FEATURE_SSL */
177
178 /* second source to check for proxies is command line argument, overwriting the environment variables */
179 if (strlen(config.proxy) > 0) {
180 working_state.curlopt_proxy = strdup(config.proxy);
181 }
182
183 if (working_state.curlopt_proxy != NULL && strlen(working_state.curlopt_proxy)){
184 handle_curl_option_return_code(
185 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXY, working_state.curlopt_proxy), "CURLOPT_PROXY");
186 if (verbose >= 1) {
187 printf("* curl CURLOPT_PROXY: %s\n", working_state.curlopt_proxy);
188 }
189 }
190
191 /* set no_proxy */
192 /* first source to check is environment variables */
193 char *no_proxy_env, *no_proxy_uppercase_env;
194 no_proxy_env = getenv("no_proxy");
195 no_proxy_uppercase_env = getenv("NO_PROXY");
196 if (no_proxy_env != NULL && strlen(no_proxy_env)){
197 working_state.curlopt_noproxy = strdup(no_proxy_env);
198 if (no_proxy_uppercase_env != NULL && verbose >= 1){
199 printf("* cURL ignoring environment variable 'NO_PROXY' as 'no_proxy' is set\n");
200 }
201 }else if (no_proxy_uppercase_env != NULL && strlen(no_proxy_uppercase_env) > 0){
202 working_state.curlopt_noproxy = strdup(no_proxy_uppercase_env);
203 }
204
205 /* second source to check for no_proxy is command line argument, overwriting the environment variables */
206 if (strlen(config.no_proxy) > 0) {
207 working_state.curlopt_noproxy = strdup(config.no_proxy);
208 }
209
210 if ( working_state.curlopt_noproxy != NULL && strlen(working_state.curlopt_noproxy)){
211 handle_curl_option_return_code(
212 curl_easy_setopt(result.curl_state.curl, CURLOPT_NOPROXY, working_state.curlopt_noproxy), "CURLOPT_NOPROXY");
213 if (verbose >= 1) {
214 printf("* curl CURLOPT_NOPROXY: %s\n", working_state.curlopt_noproxy);
215 }
216 }
217
218 int proxy_resolves_hostname = determine_hostname_resolver(working_state, config);
219 if (verbose >= 1) {
220 printf("* proxy_resolves_hostname: %d\n", proxy_resolves_hostname);
221 }
222
223 /* enable haproxy protocol */
224 if (config.haproxy_protocol) {
225 handle_curl_option_return_code(
226 curl_easy_setopt(result.curl_state.curl, CURLOPT_HAPROXYPROTOCOL, 1L),
227 "CURLOPT_HAPROXYPROTOCOL");
228 }
229
230 /* fill dns resolve cache to make curl connect to the given server_address instead of the */
231 /* host_name, only required for ssl, because we use the host_name later on to make SNI happy */
232 char dnscache[DEFAULT_BUFFER_SIZE];
233 char addrstr[DEFAULT_BUFFER_SIZE / 2];
234 if (working_state.use_ssl && working_state.host_name != NULL && !proxy_resolves_hostname ) {
235 char *tmp_mod_address;
236
237 /* lookup_host() requires an IPv6 address without the brackets. */
238 if ((strnlen(working_state.server_address, MAX_IPV4_HOSTLENGTH) > 2) &&
239 (working_state.server_address[0] == '[')) {
240 // Duplicate and strip the leading '['
241 tmp_mod_address =
242 strndup(working_state.server_address + 1, strlen(working_state.server_address) - 2);
243 } else {
244 tmp_mod_address = working_state.server_address;
245 }
246
247 int res;
248 if ((res = lookup_host(tmp_mod_address, addrstr, DEFAULT_BUFFER_SIZE / 2,
249 config.sin_family)) != 0) {
250 die(STATE_CRITICAL,
251 _("Unable to lookup IP address for '%s': getaddrinfo returned %d - %s"),
252 working_state.server_address, res, gai_strerror(res));
253 }
254
255 snprintf(dnscache, DEFAULT_BUFFER_SIZE, "%s:%d:%s", working_state.host_name,
256 working_state.serverPort, addrstr);
257 result.curl_state.host = curl_slist_append(NULL, dnscache);
258 curl_easy_setopt(result.curl_state.curl, CURLOPT_RESOLVE, result.curl_state.host);
259
260 if (verbose >= 1) {
261 printf("* curl CURLOPT_RESOLVE: %s\n", dnscache);
262 }
263 }
264
265 // If server_address is an IPv6 address it must be surround by square brackets
266 struct in6_addr tmp_in_addr;
267 if (inet_pton(AF_INET6, working_state.server_address, &tmp_in_addr) == 1) {
268 char *new_server_address = calloc(strlen(working_state.server_address) + 3, sizeof(char));
269 if (new_server_address == NULL) {
270 die(STATE_UNKNOWN, "HTTP UNKNOWN - Unable to allocate memory\n");
271 }
272 snprintf(new_server_address, strlen(working_state.server_address) + 3, "[%s]",
273 working_state.server_address);
274 working_state.server_address = new_server_address;
275 }
276
277 /* compose URL: use the address we want to connect to, set Host: header later */
278 char *url = fmt_url(working_state);
279
280 if (verbose >= 1) {
281 printf("* curl CURLOPT_URL: %s\n", url);
282 }
283 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, url),
284 "CURLOPT_URL");
285
286 free(url);
287
288 /* extract proxy information for legacy proxy https requests */
289 if (!strcmp(working_state.http_method, "CONNECT") ||
290 strstr(working_state.server_url, "http") == working_state.server_url) {
291 handle_curl_option_return_code(
292 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXY, working_state.server_address),
293 "CURLOPT_PROXY");
294 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYPORT,
295 (long)working_state.serverPort),
296 "CURLOPT_PROXYPORT");
297 if (verbose >= 2) {
298 printf("* curl CURLOPT_PROXY: %s:%d\n", working_state.server_address,
299 working_state.serverPort);
300 }
301 working_state.http_method = "GET";
302 handle_curl_option_return_code(
303 curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, working_state.server_url),
304 "CURLOPT_URL");
305 }
306
307 /* disable body for HEAD request */
308 if (working_state.http_method && !strcmp(working_state.http_method, "HEAD")) {
309 working_state.no_body = true;
310 }
311
312 /* set HTTP protocol version */
313 handle_curl_option_return_code(
314 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTP_VERSION, config.curl_http_version),
315 "CURLOPT_HTTP_VERSION");
316
317 /* set HTTP method */
318 if (working_state.http_method) {
319 if (!strcmp(working_state.http_method, "POST")) {
320 handle_curl_option_return_code(
321 curl_easy_setopt(result.curl_state.curl, CURLOPT_POST, 1L), "CURLOPT_POST");
322 } else if (!strcmp(working_state.http_method, "PUT")) {
323 handle_curl_option_return_code(
324 curl_easy_setopt(result.curl_state.curl, CURLOPT_UPLOAD, 1L), "CURLOPT_UPLOAD");
325 } else {
326 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
327 CURLOPT_CUSTOMREQUEST,
328 working_state.http_method),
329 "CURLOPT_CUSTOMREQUEST");
330 }
331 }
332
333 char *force_host_header = NULL;
334 /* check if Host header is explicitly set in options */
335 if (config.http_opt_headers_count) {
336 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
337 if (strncmp(config.http_opt_headers[i], "Host:", 5) == 0) {
338 force_host_header = config.http_opt_headers[i];
339 }
340 }
341 }
342
343 /* set hostname (virtual hosts), not needed if CURLOPT_CONNECT_TO is used, but left in
344 * anyway */
345 char http_header[DEFAULT_BUFFER_SIZE];
346 if (working_state.host_name != NULL && force_host_header == NULL) {
347 if ((working_state.virtualPort != HTTP_PORT && !working_state.use_ssl) ||
348 (working_state.virtualPort != HTTPS_PORT && working_state.use_ssl)) {
349 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s:%d", working_state.host_name,
350 working_state.virtualPort);
351 } else {
352 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s", working_state.host_name);
353 }
354 result.curl_state.header_list =
355 curl_slist_append(result.curl_state.header_list, http_header);
356 }
357
358 /* always close connection, be nice to servers */
359 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Connection: close");
360 result.curl_state.header_list = curl_slist_append(result.curl_state.header_list, http_header);
361
362 /* attach additional headers supplied by the user */
363 /* optionally send any other header tag */
364 if (config.http_opt_headers_count) {
365 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
366 result.curl_state.header_list =
367 curl_slist_append(result.curl_state.header_list, config.http_opt_headers[i]);
368 }
369 }
370
371 /* set HTTP headers */
372 handle_curl_option_return_code(
373 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTPHEADER, result.curl_state.header_list),
374 "CURLOPT_HTTPHEADER");
375
376#ifdef LIBCURL_FEATURE_SSL
377 /* set SSL version, warn about insecure or unsupported versions */
378 if (working_state.use_ssl) {
379 handle_curl_option_return_code(
380 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLVERSION, config.ssl_version),
381 "CURLOPT_SSLVERSION");
382 }
383
384 /* client certificate and key to present to server (SSL) */
385 if (config.client_cert) {
386 handle_curl_option_return_code(
387 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLCERT, config.client_cert),
388 "CURLOPT_SSLCERT");
389 }
390
391 if (config.client_privkey) {
392 handle_curl_option_return_code(
393 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLKEY, config.client_privkey),
394 "CURLOPT_SSLKEY");
395 }
396
397 if (config.ca_cert) {
398 handle_curl_option_return_code(
399 curl_easy_setopt(result.curl_state.curl, CURLOPT_CAINFO, config.ca_cert),
400 "CURLOPT_CAINFO");
401 }
402
403 if (config.ca_cert || config.verify_peer_and_host) {
404 /* per default if we have a CA verify both the peer and the
405 * hostname in the certificate, can be switched off later */
406 handle_curl_option_return_code(
407 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 1L),
408 "CURLOPT_SSL_VERIFYPEER");
409 handle_curl_option_return_code(
410 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 2L),
411 "CURLOPT_SSL_VERIFYHOST");
412 } else {
413 /* backward-compatible behaviour, be tolerant in checks
414 * TODO: depending on more options have aspects we want
415 * to be less tolerant about ssl verfications
416 */
417 handle_curl_option_return_code(
418 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 0L),
419 "CURLOPT_SSL_VERIFYPEER");
420 handle_curl_option_return_code(
421 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 0L),
422 "CURLOPT_SSL_VERIFYHOST");
423 }
424
425 /* detect SSL library used by libcurl */
426 curlhelp_ssl_library ssl_library = curlhelp_get_ssl_library();
427
428 /* try hard to get a stack of certificates to verify against */
429 if (check_cert) {
430# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1)
431 /* inform curl to report back certificates */
432 switch (ssl_library) {
433 case CURLHELP_SSL_LIBRARY_OPENSSL:
434 case CURLHELP_SSL_LIBRARY_LIBRESSL:
435 /* set callback to extract certificate with OpenSSL context function (works with
436 * OpenSSL-style libraries only!) */
437# ifdef USE_OPENSSL
438 /* libcurl and monitoring plugins built with OpenSSL, good */
439 add_sslctx_verify_fun = true;
440 is_openssl_callback = true;
441# endif /* USE_OPENSSL */
442 /* libcurl is built with OpenSSL, monitoring plugins, so falling
443 * back to manually extracting certificate information */
444 handle_curl_option_return_code(
445 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
446 break;
447
448 case CURLHELP_SSL_LIBRARY_NSS:
449# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0)
450 /* NSS: support for CERTINFO is implemented since 7.34.0 */
451 handle_curl_option_return_code(
452 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
453# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
454 die(STATE_CRITICAL,
455 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
456 "'%s' is too old)\n",
457 curlhelp_get_ssl_library_string(ssl_library));
458# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
459 break;
460
461 case CURLHELP_SSL_LIBRARY_GNUTLS:
462# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0)
463 /* GnuTLS: support for CERTINFO is implemented since 7.42.0 */
464 handle_curl_option_return_code(
465 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
466# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
467 die(STATE_CRITICAL,
468 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
469 "'%s' is too old)\n",
470 curlhelp_get_ssl_library_string(ssl_library));
471# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
472 break;
473
474 case CURLHELP_SSL_LIBRARY_UNKNOWN:
475 default:
476 die(STATE_CRITICAL,
477 "HTTP CRITICAL - Cannot retrieve certificates (unknown SSL library '%s', must "
478 "implement first)\n",
479 curlhelp_get_ssl_library_string(ssl_library));
480 break;
481 }
482# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
483 /* old libcurl, our only hope is OpenSSL, otherwise we are out of luck */
484 if (ssl_library == CURLHELP_SSL_LIBRARY_OPENSSL ||
485 ssl_library == CURLHELP_SSL_LIBRARY_LIBRESSL) {
486 add_sslctx_verify_fun = true;
487 } else {
488 die(STATE_CRITICAL, "HTTP CRITICAL - Cannot retrieve certificates (no "
489 "CURLOPT_SSL_CTX_FUNCTION, no OpenSSL library or libcurl "
490 "too old and has no CURLOPT_CERTINFO)\n");
491 }
492# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
493 }
494
495# if LIBCURL_VERSION_NUM >= \
496 MAKE_LIBCURL_VERSION(7, 10, 6) /* required for CURLOPT_SSL_CTX_FUNCTION */
497 // ssl ctx function is not available with all ssl backends
498 if (curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, NULL) !=
499 CURLE_UNKNOWN_OPTION) {
500 handle_curl_option_return_code(
501 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun),
502 "CURLOPT_SSL_CTX_FUNCTION");
503 }
504# endif
505#endif /* LIBCURL_FEATURE_SSL */
506
507 /* set default or user-given user agent identification */
508 handle_curl_option_return_code(
509 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERAGENT, config.user_agent),
510 "CURLOPT_USERAGENT");
511
512 /* proxy-authentication */
513 if (strcmp(config.proxy_auth, "")) {
514 handle_curl_option_return_code(
515 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYUSERPWD, config.proxy_auth),
516 "CURLOPT_PROXYUSERPWD");
517 }
518
519 /* authentication */
520 if (strcmp(config.user_auth, "")) {
521 handle_curl_option_return_code(
522 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERPWD, config.user_auth),
523 "CURLOPT_USERPWD");
524 }
525 /* TODO: parameter auth method, bitfield of following methods:
526 * CURLAUTH_BASIC (default)
527 * CURLAUTH_DIGEST
528 * CURLAUTH_DIGEST_IE
529 * CURLAUTH_NEGOTIATE
530 * CURLAUTH_NTLM
531 * CURLAUTH_NTLM_WB
532 *
533 * convenience tokens for typical sets of methods:
534 * CURLAUTH_ANYSAFE: most secure, without BASIC
535 * or CURLAUTH_ANY: most secure, even BASIC if necessary
536 *
537 * handle_curl_option_return_code (curl_easy_setopt( curl, CURLOPT_HTTPAUTH,
538 * (long)CURLAUTH_DIGEST ), "CURLOPT_HTTPAUTH");
539 */
540
541 /* handle redirections */
542 if (on_redirect_dependent) {
543 if (follow_method == FOLLOW_LIBCURL) {
544 handle_curl_option_return_code(
545 curl_easy_setopt(result.curl_state.curl, CURLOPT_FOLLOWLOCATION, 1L),
546 "CURLOPT_FOLLOWLOCATION");
547
548 /* default -1 is infinite, not good, could lead to zombie plugins!
549 Setting it to one bigger than maximal limit to handle errors nicely below
550 */
551 handle_curl_option_return_code(
552 curl_easy_setopt(result.curl_state.curl, CURLOPT_MAXREDIRS, max_depth + 1),
553 "CURLOPT_MAXREDIRS");
554
555 /* for now allow only http and https (we are a http(s) check plugin in the end) */
556#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 85, 0)
557 handle_curl_option_return_code(
558 curl_easy_setopt(result.curl_state.curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https"),
559 "CURLOPT_REDIR_PROTOCOLS_STR");
560#elif LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 4)
561 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
562 CURLOPT_REDIR_PROTOCOLS,
563 CURLPROTO_HTTP | CURLPROTO_HTTPS),
564 "CURLOPT_REDIRECT_PROTOCOLS");
565#endif
566
567 /* TODO: handle the following aspects of redirection, make them
568 * command line options too later:
569 CURLOPT_POSTREDIR: method switch
570 CURLINFO_REDIRECT_URL: custom redirect option
571 CURLOPT_REDIRECT_PROTOCOLS: allow people to step outside safe protocols
572 CURLINFO_REDIRECT_COUNT: get the number of redirects, print it, maybe a range
573 option here is nice like for expected page size?
574 */
575 } else {
576 /* old style redirection*/
577 }
578 }
579 /* no-body */
580 if (working_state.no_body) {
581 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_NOBODY, 1L),
582 "CURLOPT_NOBODY");
583 }
584
585 /* IPv4 or IPv6 forced DNS resolution */
586 if (config.sin_family == AF_UNSPEC) {
587 handle_curl_option_return_code(
588 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER),
589 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_WHATEVER)");
590 } else if (config.sin_family == AF_INET) {
591 handle_curl_option_return_code(
592 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4),
593 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V4)");
594 }
595#if defined(LIBCURL_FEATURE_IPV6)
596 else if (config.sin_family == AF_INET6) {
597 handle_curl_option_return_code(
598 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6),
599 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V6)");
600 }
601#endif
602
603 /* either send http POST data (any data, not only POST)*/
604 if (!strcmp(working_state.http_method, "POST") || !strcmp(working_state.http_method, "PUT")) {
605 /* set content of payload for POST and PUT */
606 if (config.http_content_type) {
607 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Content-Type: %s",
608 config.http_content_type);
609 result.curl_state.header_list =
610 curl_slist_append(result.curl_state.header_list, http_header);
611 }
612 /* NULL indicates "HTTP Continue" in libcurl, provide an empty string
613 * in case of no POST/PUT data */
614 if (!working_state.http_post_data) {
615 working_state.http_post_data = "";
616 }
617
618 if (!strcmp(working_state.http_method, "POST")) {
619 /* POST method, set payload with CURLOPT_POSTFIELDS */
620 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
621 CURLOPT_POSTFIELDS,
622 working_state.http_post_data),
623 "CURLOPT_POSTFIELDS");
624 } else if (!strcmp(working_state.http_method, "PUT")) {
625 handle_curl_option_return_code(
626 curl_easy_setopt(result.curl_state.curl, CURLOPT_READFUNCTION,
627 (curl_read_callback)curlhelp_buffer_read_callback),
628 "CURLOPT_READFUNCTION");
629 if (curlhelp_initreadbuffer(&result.curl_state.put_buf, working_state.http_post_data,
630 strlen(working_state.http_post_data)) < 0) {
631 die(STATE_UNKNOWN,
632 "HTTP CRITICAL - out of memory allocating read buffer for PUT\n");
633 }
634 result.curl_state.put_buf_initialized = true;
635 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
636 CURLOPT_READDATA,
637 (void *)result.curl_state.put_buf),
638 "CURLOPT_READDATA");
639 handle_curl_option_return_code(
640 curl_easy_setopt(result.curl_state.curl, CURLOPT_INFILESIZE,
641 (curl_off_t)strlen(working_state.http_post_data)),
642 "CURLOPT_INFILESIZE");
643 }
644 }
645
646 /* cookie handling */
647 if (config.cookie_jar_file != NULL) {
648 /* enable reading cookies from a file, and if the filename is an empty string, only
649 * enable the curl cookie engine */
650 handle_curl_option_return_code(
651 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEFILE, config.cookie_jar_file),
652 "CURLOPT_COOKIEFILE");
653 /* now enable saving cookies to a file, but only if the filename is not an empty string,
654 * since writing it would fail */
655 if (*config.cookie_jar_file) {
656 handle_curl_option_return_code(
657 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEJAR, config.cookie_jar_file),
658 "CURLOPT_COOKIEJAR");
659 }
660 }
661
662 result.working_state = working_state;
663
664 return result;
665}
666
667void handle_curl_option_return_code(CURLcode res, const char *option) {
668 if (res != CURLE_OK) {
669 die(STATE_CRITICAL, _("Error while setting cURL option '%s': cURL returned %d - %s\n"),
670 option, res, curl_easy_strerror(res));
671 }
672}
673
674char *get_header_value(const struct phr_header *headers, const size_t nof_headers,
675 const char *header) {
676 for (size_t i = 0; i < nof_headers; i++) {
677 if (headers[i].name != NULL &&
678 strncasecmp(header, headers[i].name, max(headers[i].name_len, 4)) == 0) {
679 return strndup(headers[i].value, headers[i].value_len);
680 }
681 }
682 return NULL;
683}
684
685check_curl_working_state check_curl_working_state_init() {
686 check_curl_working_state result = {
687 .server_address = NULL,
688 .server_url = DEFAULT_SERVER_URL,
689 .host_name = NULL,
690 .http_method = NULL,
691 .http_post_data = NULL,
692 .virtualPort = 0,
693 .serverPort = HTTP_PORT,
694 .use_ssl = false,
695 .no_body = false,
696 .curlopt_proxy = NULL,
697 .curlopt_noproxy = NULL,
698 };
699 return result;
700}
701
702check_curl_config check_curl_config_init() {
703 check_curl_config tmp = {
704 .initial_config = check_curl_working_state_init(),
705
706 .curl_config =
707 {
708 .automatic_decompression = false,
709 .socket_timeout = DEFAULT_SOCKET_TIMEOUT,
710 .haproxy_protocol = false,
711 .sin_family = AF_UNSPEC,
712 .curl_http_version = CURL_HTTP_VERSION_NONE,
713 .http_opt_headers = NULL,
714 .http_opt_headers_count = 0,
715 .ssl_version = CURL_SSLVERSION_DEFAULT,
716 .client_cert = NULL,
717 .client_privkey = NULL,
718 .ca_cert = NULL,
719 .verify_peer_and_host = false,
720 .user_agent = {'\0'},
721 .proxy = "",
722 .no_proxy = "",
723 .proxy_auth = "",
724 .user_auth = "",
725 .http_content_type = NULL,
726 .cookie_jar_file = NULL,
727 },
728 .max_depth = DEFAULT_MAX_REDIRS,
729 .followmethod = FOLLOW_HTTP_CURL,
730 .followsticky = STICKY_NONE,
731
732 .maximum_age = -1,
733 .regexp = {},
734 .compiled_regex = {},
735 .state_regex = STATE_CRITICAL,
736 .invert_regex = false,
737 .check_cert = false,
738 .continue_after_check_cert = false,
739 .days_till_exp_warn = 0,
740 .days_till_exp_crit = 0,
741 .thlds = mp_thresholds_init(),
742 .page_length_limits = mp_range_init(),
743 .page_length_limits_is_set = false,
744 .server_expect =
745 {
746 .string = HTTP_EXPECT,
747 .is_present = false,
748 },
749 .string_expect = "",
750 .header_expect = "",
751 .on_redirect_result_state = STATE_OK,
752 .on_redirect_dependent = false,
753
754 .show_extended_perfdata = false,
755 .show_body = false,
756
757 .output_format_is_set = false,
758 };
759
760 snprintf(tmp.curl_config.user_agent, DEFAULT_BUFFER_SIZE, "%s/v%s (monitoring-plugins %s, %s)",
761 "check_curl", NP_VERSION, VERSION, curl_version());
762
763 return tmp;
764}
765
766/* TODO: is there a better way in libcurl to check for the SSL library? */
767curlhelp_ssl_library curlhelp_get_ssl_library(void) {
768 curlhelp_ssl_library ssl_library = CURLHELP_SSL_LIBRARY_UNKNOWN;
769
770 curl_version_info_data *version_data = curl_version_info(CURLVERSION_NOW);
771 if (version_data == NULL) {
772 return CURLHELP_SSL_LIBRARY_UNKNOWN;
773 }
774
775 char *ssl_version = strdup(version_data->ssl_version);
776 if (ssl_version == NULL) {
777 return CURLHELP_SSL_LIBRARY_UNKNOWN;
778 }
779
780 char *library = strtok(ssl_version, "/");
781 if (library == NULL) {
782 return CURLHELP_SSL_LIBRARY_UNKNOWN;
783 }
784
785 if (strcmp(library, "OpenSSL") == 0) {
786 ssl_library = CURLHELP_SSL_LIBRARY_OPENSSL;
787 } else if (strcmp(library, "LibreSSL") == 0) {
788 ssl_library = CURLHELP_SSL_LIBRARY_LIBRESSL;
789 } else if (strcmp(library, "GnuTLS") == 0) {
790 ssl_library = CURLHELP_SSL_LIBRARY_GNUTLS;
791 } else if (strcmp(library, "NSS") == 0) {
792 ssl_library = CURLHELP_SSL_LIBRARY_NSS;
793 }
794
795 if (verbose >= 2) {
796 printf("* SSL library string is : %s %s (%d)\n", version_data->ssl_version, library,
797 ssl_library);
798 }
799
800 free(ssl_version);
801
802 return ssl_library;
803}
804
805const char *curlhelp_get_ssl_library_string(const curlhelp_ssl_library ssl_library) {
806 switch (ssl_library) {
807 case CURLHELP_SSL_LIBRARY_OPENSSL:
808 return "OpenSSL";
809 case CURLHELP_SSL_LIBRARY_LIBRESSL:
810 return "LibreSSL";
811 case CURLHELP_SSL_LIBRARY_GNUTLS:
812 return "GnuTLS";
813 case CURLHELP_SSL_LIBRARY_NSS:
814 return "NSS";
815 case CURLHELP_SSL_LIBRARY_UNKNOWN:
816 default:
817 return "unknown";
818 }
819}
820
821size_t get_content_length(const curlhelp_write_curlbuf *header_buf,
822 const curlhelp_write_curlbuf *body_buf) {
823 struct phr_header headers[255];
824 size_t nof_headers = 255;
825 size_t msglen;
826 curlhelp_statusline status_line;
827 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
828 &status_line.http_minor, &status_line.http_code, &status_line.msg,
829 &msglen, headers, &nof_headers, 0);
830
831 if (res == -1) {
832 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
833 }
834
835 char *content_length_s = get_header_value(headers, nof_headers, "content-length");
836 if (!content_length_s) {
837 return header_buf->buflen + body_buf->buflen;
838 }
839
840 content_length_s += strspn(content_length_s, " \t");
841 size_t content_length = atoi(content_length_s);
842 if (content_length != body_buf->buflen) {
843 /* TODO: should we warn if the actual and the reported body length don't match? */
844 }
845
846 if (content_length_s) {
847 free(content_length_s);
848 }
849
850 return header_buf->buflen + body_buf->buflen;
851}
852
853mp_subcheck check_document_dates(const curlhelp_write_curlbuf *header_buf, const int maximum_age) {
854 struct phr_header headers[255];
855 size_t nof_headers = 255;
856 curlhelp_statusline status_line;
857 size_t msglen;
858 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
859 &status_line.http_minor, &status_line.http_code, &status_line.msg,
860 &msglen, headers, &nof_headers, 0);
861
862 if (res == -1) {
863 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
864 }
865
866 char *server_date = get_header_value(headers, nof_headers, "date");
867 char *document_date = get_header_value(headers, nof_headers, "last-modified");
868
869 mp_subcheck sc_document_dates = mp_subcheck_init();
870 if (!server_date || !*server_date) {
871 xasprintf(&sc_document_dates.output, _("Server date unknown"));
872 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_UNKNOWN);
873 } else if (!document_date || !*document_date) {
874 xasprintf(&sc_document_dates.output, _("Document modification date unknown, "));
875 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
876 } else {
877 time_t srv_data = curl_getdate(server_date, NULL);
878 time_t doc_data = curl_getdate(document_date, NULL);
879
880 if (verbose >= 2) {
881 printf("* server date: '%s' (%d), doc_date: '%s' (%d)\n", server_date, (int)srv_data,
882 document_date, (int)doc_data);
883 }
884
885 if (srv_data <= 0) {
886 xasprintf(&sc_document_dates.output, _("Server date \"%100s\" unparsable"),
887 server_date);
888 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
889 } else if (doc_data <= 0) {
890
891 xasprintf(&sc_document_dates.output, _("Document date \"%100s\" unparsable"),
892 document_date);
893 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
894 } else if (doc_data > srv_data + 30) {
895
896 xasprintf(&sc_document_dates.output, _("Document is %d seconds in the future"),
897 (int)doc_data - (int)srv_data);
898
899 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
900 } else if (doc_data < srv_data - maximum_age) {
901 time_t last_modified = (srv_data - doc_data);
902 if (last_modified > (60 * 60 * 24 * 2)) { // two days hardcoded?
903 xasprintf(&sc_document_dates.output, _("Last modified %.1f days ago"),
904 ((float)last_modified) / (60 * 60 * 24));
905 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
906 } else {
907 xasprintf(&sc_document_dates.output, _("Last modified %lld:%02d:%02d ago"),
908 (long long)last_modified / (60 * 60), (int)(last_modified / 60) % 60,
909 (int)last_modified % 60);
910 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
911 }
912 } else {
913 // TODO is this the OK case?
914 time_t last_modified = (srv_data - doc_data);
915 xasprintf(&sc_document_dates.output, _("Last modified %lld:%02d:%02d ago"),
916 (long long)last_modified / (60 * 60), (int)(last_modified / 60) % 60,
917 (int)last_modified % 60);
918 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_OK);
919 }
920 }
921
922 if (server_date) {
923 free(server_date);
924 }
925 if (document_date) {
926 free(document_date);
927 }
928
929 return sc_document_dates;
930}
931
932void curlhelp_free_statusline(curlhelp_statusline *status_line) { free(status_line->first_line); }
933
934int curlhelp_parse_statusline(const char *buf, curlhelp_statusline *status_line) {
935 /* find last start of a new header */
936 const char *start = strrstr2(buf, "\r\nHTTP/");
937 if (start != NULL) {
938 start += 2;
939 buf = start;
940 }
941
942 // Accept either LF or CRLF as end of line for the status line
943 // CRLF is the standard (RFC9112), but it is recommended to accept both
944 size_t length_of_first_line = strcspn(buf, "\r\n");
945 const char *first_line_end = &buf[length_of_first_line];
946 if (first_line_end == NULL) {
947 return -1;
948 }
949
950 size_t first_line_len = (size_t)(first_line_end - buf);
951 status_line->first_line = (char *)calloc(first_line_len + 1, sizeof(char));
952 if (status_line->first_line == NULL) {
953 return -1;
954 }
955
956 memcpy(status_line->first_line, buf, first_line_len);
957 status_line->first_line[first_line_len] = '\0';
958 char *first_line_buf = strdup(status_line->first_line);
959
960 /* protocol and version: "HTTP/x.x" SP or "HTTP/2" SP */
961 char *temp_string = strtok(first_line_buf, "/");
962 if (temp_string == NULL) {
963 if (verbose > 1) {
964 printf("%s: no / found\n", __func__);
965 }
966 free(first_line_buf);
967 return -1;
968 }
969
970 if (strcmp(temp_string, "HTTP") != 0) {
971 if (verbose > 1) {
972 printf("%s: string 'HTTP' not found\n", __func__);
973 }
974 free(first_line_buf);
975 return -1;
976 }
977
978 // try to find a space in the remaining string?
979 // the space after HTTP/1.1 probably
980 temp_string = strtok(NULL, " ");
981 if (temp_string == NULL) {
982 if (verbose > 1) {
983 printf("%s: no space after protocol definition\n", __func__);
984 }
985 free(first_line_buf);
986 return -1;
987 }
988
989 char *temp_string_2;
990 if (strchr(temp_string, '.') != NULL) {
991 /* HTTP 1.x case */
992 strtok(temp_string, ".");
993 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
994 if (*temp_string_2 != '\0') {
995 free(first_line_buf);
996 return -1;
997 }
998 strtok(NULL, " ");
999 status_line->http_minor = (int)strtol(temp_string, &temp_string_2, 10);
1000 if (*temp_string_2 != '\0') {
1001 free(first_line_buf);
1002 return -1;
1003 }
1004 temp_string += 4; /* 1.x SP */
1005 } else {
1006 /* HTTP 2 case */
1007 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
1008 status_line->http_minor = 0;
1009 temp_string += 2; /* 2 SP */
1010 }
1011
1012 /* status code: "404" or "404.1", then SP */
1013 temp_string = strtok(temp_string, " ");
1014 if (temp_string == NULL) {
1015 free(first_line_buf);
1016 return -1;
1017 }
1018 if (strchr(temp_string, '.') != NULL) {
1019 char *ppp;
1020 ppp = strtok(temp_string, ".");
1021 status_line->http_code = (int)strtol(ppp, &temp_string_2, 10);
1022 if (*temp_string_2 != '\0') {
1023 free(first_line_buf);
1024 return -1;
1025 }
1026 ppp = strtok(NULL, "");
1027 status_line->http_subcode = (int)strtol(ppp, &temp_string_2, 10);
1028 if (*temp_string_2 != '\0') {
1029 free(first_line_buf);
1030 return -1;
1031 }
1032 temp_string += 6; /* 400.1 SP */
1033 } else {
1034 status_line->http_code = (int)strtol(temp_string, &temp_string_2, 10);
1035 status_line->http_subcode = -1;
1036 if (*temp_string_2 != '\0') {
1037 free(first_line_buf);
1038 return -1;
1039 }
1040 temp_string += 4; /* 400 SP */
1041 }
1042
1043 /* Human readable message: "Not Found" CRLF */
1044
1045 temp_string = strtok(temp_string, "");
1046 if (temp_string == NULL) {
1047 status_line->msg = "";
1048 return 0;
1049 }
1050 status_line->msg = status_line->first_line + (temp_string - first_line_buf);
1051 free(first_line_buf);
1052
1053 return 0;
1054}
1055
1056/* TODO: where to put this, it's actually part of sstrings2 (logically)?
1057 */
1058const char *strrstr2(const char *haystack, const char *needle) {
1059 if (haystack == NULL || needle == NULL) {
1060 return NULL;
1061 }
1062
1063 if (haystack[0] == '\0' || needle[0] == '\0') {
1064 return NULL;
1065 }
1066
1067 int counter = 0;
1068 const char *prev_pos = NULL;
1069 const char *pos = haystack;
1070 size_t len = strlen(needle);
1071 for (;;) {
1072 pos = strstr(pos, needle);
1073 if (pos == NULL) {
1074 if (counter == 0) {
1075 return NULL;
1076 }
1077 return prev_pos;
1078 }
1079 counter++;
1080 prev_pos = pos;
1081 pos += len;
1082 if (*pos == '\0') {
1083 return prev_pos;
1084 }
1085 }
1086}
1087
1088void curlhelp_freereadbuffer(curlhelp_read_curlbuf *buf) {
1089 free(buf->buf);
1090 buf->buf = NULL;
1091}
1092
1093void curlhelp_freewritebuffer(curlhelp_write_curlbuf *buf) {
1094 free(buf->buf);
1095 buf->buf = NULL;
1096}
1097
1098int curlhelp_initreadbuffer(curlhelp_read_curlbuf **buf, const char *data, size_t datalen) {
1099 if ((*buf = calloc(1, sizeof(curlhelp_read_curlbuf))) == NULL) {
1100 return 1;
1101 }
1102
1103 (*buf)->buflen = datalen;
1104 (*buf)->buf = (char *)calloc((*buf)->buflen, sizeof(char));
1105 if ((*buf)->buf == NULL) {
1106 return -1;
1107 }
1108 memcpy((*buf)->buf, data, datalen);
1109 (*buf)->pos = 0;
1110 return 0;
1111}
1112
1113size_t curlhelp_buffer_read_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
1114 curlhelp_read_curlbuf *buf = (curlhelp_read_curlbuf *)stream;
1115
1116 size_t minimalSize = min(nmemb * size, buf->buflen - buf->pos);
1117
1118 memcpy(buffer, buf->buf + buf->pos, minimalSize);
1119 buf->pos += minimalSize;
1120
1121 return minimalSize;
1122}
1123
1124int curlhelp_initwritebuffer(curlhelp_write_curlbuf **buf) {
1125 if ((*buf = calloc(1, sizeof(curlhelp_write_curlbuf))) == NULL) {
1126 return 1;
1127 }
1128 (*buf)->bufsize = DEFAULT_BUFFER_SIZE * sizeof(char);
1129 (*buf)->buflen = 0;
1130 (*buf)->buf = (char *)calloc((*buf)->bufsize, sizeof(char));
1131 if ((*buf)->buf == NULL) {
1132 return -1;
1133 }
1134 return 0;
1135}
1136
1137size_t curlhelp_buffer_write_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
1138 curlhelp_write_curlbuf *buf = (curlhelp_write_curlbuf *)stream;
1139
1140 while (buf->bufsize < buf->buflen + size * nmemb + 1) {
1141 buf->bufsize = buf->bufsize * 2;
1142 buf->buf = (char *)realloc(buf->buf, buf->bufsize);
1143 if (buf->buf == NULL) {
1144 fprintf(stderr, "malloc failed (%d) %s\n", errno, strerror(errno));
1145 return 0;
1146 }
1147 }
1148
1149 memcpy(buf->buf + buf->buflen, buffer, size * nmemb);
1150 buf->buflen += size * nmemb;
1151 buf->buf[buf->buflen] = '\0';
1152
1153 return size * nmemb;
1154}
1155
1156void cleanup(check_curl_global_state global_state) {
1157 if (global_state.status_line_initialized) {
1158 curlhelp_free_statusline(global_state.status_line);
1159 }
1160 global_state.status_line_initialized = false;
1161
1162 if (global_state.curl_easy_initialized) {
1163 curl_easy_cleanup(global_state.curl);
1164 }
1165 global_state.curl_easy_initialized = false;
1166
1167 if (global_state.curl_global_initialized) {
1168 curl_global_cleanup();
1169 }
1170 global_state.curl_global_initialized = false;
1171
1172 if (global_state.body_buf_initialized) {
1173 curlhelp_freewritebuffer(global_state.body_buf);
1174 }
1175 global_state.body_buf_initialized = false;
1176
1177 if (global_state.header_buf_initialized) {
1178 curlhelp_freewritebuffer(global_state.header_buf);
1179 }
1180 global_state.header_buf_initialized = false;
1181
1182 if (global_state.put_buf_initialized) {
1183 curlhelp_freereadbuffer(global_state.put_buf);
1184 }
1185 global_state.put_buf_initialized = false;
1186
1187 if (global_state.header_list) {
1188 curl_slist_free_all(global_state.header_list);
1189 }
1190
1191 if (global_state.host) {
1192 curl_slist_free_all(global_state.host);
1193 }
1194}
1195
1196int lookup_host(const char *host, char *buf, size_t buflen, sa_family_t addr_family) {
1197 struct addrinfo hints = {
1198 .ai_family = addr_family,
1199 .ai_socktype = SOCK_STREAM,
1200 .ai_flags = AI_CANONNAME,
1201 };
1202
1203 struct addrinfo *result;
1204 int errcode = getaddrinfo(host, NULL, &hints, &result);
1205 if (errcode != 0) {
1206 return errcode;
1207 }
1208
1209 strcpy(buf, "");
1210 struct addrinfo *res = result;
1211
1212 size_t buflen_remaining = buflen - 1;
1213 size_t addrstr_len;
1214 char addrstr[100];
1215 void *ptr = {0};
1216 while (res) {
1217 switch (res->ai_family) {
1218 case AF_INET:
1219 ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
1220 break;
1221 case AF_INET6:
1222 ptr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
1223 break;
1224 }
1225
1226 inet_ntop(res->ai_family, ptr, addrstr, 100);
1227 if (verbose >= 1) {
1228 printf("* getaddrinfo IPv%d address: %s\n", res->ai_family == PF_INET6 ? 6 : 4,
1229 addrstr);
1230 }
1231
1232 // Append all IPs to buf as a comma-separated string
1233 addrstr_len = strlen(addrstr);
1234 if (buflen_remaining > addrstr_len + 1) {
1235 if (buf[0] != '\0') {
1236 strncat(buf, ",", buflen_remaining);
1237 buflen_remaining -= 1;
1238 }
1239 strncat(buf, addrstr, buflen_remaining);
1240 buflen_remaining -= addrstr_len;
1241 }
1242
1243 res = res->ai_next;
1244 }
1245
1246 freeaddrinfo(result);
1247
1248 return 0;
1249}
1250
1251/* Checks if the server 'reply' is one of the expected 'statuscodes' */
1252bool expected_statuscode(const char *reply, const char *statuscodes) {
1253 char *expected;
1254
1255 if ((expected = strdup(statuscodes)) == NULL) {
1256 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
1257 }
1258
1259 bool result = false;
1260 for (char *code = strtok(expected, ","); code != NULL; code = strtok(NULL, ",")) {
1261 if (strstr(reply, code) != NULL) {
1262 result = true;
1263 break;
1264 }
1265 }
1266
1267 free(expected);
1268 return result;
1269}
1270
1271/* returns a string "HTTP/1.x" or "HTTP/2" */
1272char *string_statuscode(int major, int minor) {
1273 static char buf[10];
1274
1275 switch (major) {
1276 case 1:
1277 snprintf(buf, sizeof(buf), "HTTP/%d.%d", major, minor);
1278 break;
1279 case 2:
1280 case 3:
1281 snprintf(buf, sizeof(buf), "HTTP/%d", major);
1282 break;
1283 default:
1284 /* assuming here HTTP/N with N>=4 */
1285 snprintf(buf, sizeof(buf), "HTTP/%d", major);
1286 break;
1287 }
1288
1289 return buf;
1290}
1291
1292/* check whether a file exists */
1293void test_file(char *path) {
1294 if (access(path, R_OK) == 0) {
1295 return;
1296 }
1297 usage2(_("file does not exist or is not readable"), path);
1298}
1299
1300mp_subcheck mp_net_ssl_check_certificate(X509 *certificate, int days_till_exp_warn,
1301 int days_till_exp_crit);
1302
1303mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_till_exp,
1304 int crit_days_till_exp) {
1305 mp_subcheck sc_cert_result = mp_subcheck_init();
1306 sc_cert_result = mp_set_subcheck_default_state(sc_cert_result, STATE_OK);
1307
1308#ifdef LIBCURL_FEATURE_SSL
1309 if (is_openssl_callback) {
1310# ifdef USE_OPENSSL
1311 /* check certificate with OpenSSL functions, curl has been built against OpenSSL
1312 * and we actually have OpenSSL in the monitoring tools
1313 */
1314 return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp);
1315# else /* USE_OPENSSL */
1316 xasprintf(&result.output, "HTTP CRITICAL - Cannot retrieve certificates - OpenSSL "
1317 "callback used and not linked against OpenSSL\n");
1318 mp_set_subcheck_state(result, STATE_CRITICAL);
1319# endif /* USE_OPENSSL */
1320 } else {
1321 struct curl_slist *slist;
1322
1323 cert_ptr_union cert_ptr = {0};
1324 cert_ptr.to_info = NULL;
1325 CURLcode res = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &cert_ptr.to_certinfo);
1326 if (!res && cert_ptr.to_info) {
1327# ifdef USE_OPENSSL
1328 /* We have no OpenSSL in libcurl, but we can use OpenSSL for X509 cert
1329 * parsing We only check the first certificate and assume it's the one of
1330 * the server
1331 */
1332 char *raw_cert = NULL;
1333 bool got_first_cert = false;
1334 for (int i = 0; i < cert_ptr.to_certinfo->num_of_certs; i++) {
1335 if (got_first_cert) {
1336 break;
1337 }
1338
1339 for (slist = cert_ptr.to_certinfo->certinfo[i]; slist; slist = slist->next) {
1340 if (verbose >= 2) {
1341 printf("%d ** %s\n", i, slist->data);
1342 }
1343 if (strncmp(slist->data, "Cert:", 5) == 0) {
1344 raw_cert = &slist->data[5];
1345 got_first_cert = true;
1346 break;
1347 }
1348 }
1349 }
1350
1351 if (!raw_cert) {
1352
1353 xasprintf(&sc_cert_result.output,
1354 _("Cannot retrieve certificates from CERTINFO information - "
1355 "certificate data was empty"));
1356 sc_cert_result = mp_set_subcheck_state(sc_cert_result, STATE_CRITICAL);
1357 return sc_cert_result;
1358 }
1359
1360 BIO *cert_BIO = BIO_new(BIO_s_mem());
1361 BIO_write(cert_BIO, raw_cert, (int)strlen(raw_cert));
1362
1363 cert = PEM_read_bio_X509(cert_BIO, NULL, NULL, NULL);
1364 if (!cert) {
1365 xasprintf(&sc_cert_result.output,
1366 _("Cannot read certificate from CERTINFO information - BIO error"));
1367 sc_cert_result = mp_set_subcheck_state(sc_cert_result, STATE_CRITICAL);
1368 return sc_cert_result;
1369 }
1370
1371 BIO_free(cert_BIO);
1372 return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp);
1373# else /* USE_OPENSSL */
1374 /* We assume we don't have OpenSSL and np_net_ssl_check_certificate at our
1375 * disposal, so we use the libcurl CURLINFO data
1376 */
1377 return net_noopenssl_check_certificate(&cert_ptr, days_till_exp_warn,
1378 days_till_exp_crit);
1379# endif /* USE_OPENSSL */
1380 } else {
1381 xasprintf(&sc_cert_result.output,
1382 _("Cannot retrieve certificates - cURL returned %d - %s"), res,
1383 curl_easy_strerror(res));
1384 mp_set_subcheck_state(sc_cert_result, STATE_CRITICAL);
1385 }
1386 }
1387#endif /* LIBCURL_FEATURE_SSL */
1388
1389 return sc_cert_result;
1390}
1391
1392char *fmt_url(check_curl_working_state workingState) {
1393 char *url = calloc(DEFAULT_BUFFER_SIZE, sizeof(char));
1394 if (url == NULL) {
1395 die(STATE_UNKNOWN, "memory allocation failed");
1396 }
1397
1398 snprintf(url, DEFAULT_BUFFER_SIZE, "%s://%s:%d%s", workingState.use_ssl ? "https" : "http",
1399 (workingState.use_ssl & (workingState.host_name != NULL))
1400 ? workingState.host_name
1401 : workingState.server_address,
1402 workingState.serverPort, workingState.server_url);
1403
1404 return url;
1405}
1406
1407int determine_hostname_resolver(const check_curl_working_state working_state, const check_curl_static_curl_config config){
1408 char *host_name_display = "NULL";
1409 unsigned long host_name_len = 0;
1410 if( working_state.host_name){
1411 host_name_len = strlen(working_state.host_name);
1412 host_name_display = working_state.host_name;
1413 }
1414
1415 /* IPv4 or IPv6 version of the address */
1416 char *server_address_clean = strdup(working_state.server_address);
1417 /* server address might be a full length ipv6 address encapsulated in square brackets */
1418 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] == ']') ) {
1419 server_address_clean = strndup( working_state.server_address + 1, strlen(working_state.server_address) - 2);
1420 }
1421
1422 /* check curlopt_noproxy option first */
1423 /* https://curl.se/libcurl/c/CURLOPT_NOPROXY.html */
1424
1425 /* curlopt_noproxy is specified as a comma separated list of
1426 direct IPv4 or IPv6 addresses e.g 130.133.8.40, 2001:4860:4802:32::a ,
1427 IPv4 or IPv6 CIDR regions e.g 10.241.0.0/16 , abcd:ef01:2345::/48 ,
1428 direct hostnames e.g example.com, google.de */
1429
1430 if (working_state.curlopt_noproxy != NULL){
1431 char* curlopt_noproxy_copy = strdup( working_state.curlopt_noproxy);
1432 char* noproxy_item = strtok(curlopt_noproxy_copy, ",");
1433 while(noproxy_item != NULL){
1434 unsigned long noproxy_item_len = strlen(noproxy_item);
1435
1436 /* According to the CURLOPT_NOPROXY documentation: */
1437 /* https://curl.se/libcurl/c/CURLOPT_NOPROXY.html */
1438 /* The only wildcard available is a single * character, which matches all hosts, and effectively disables the proxy. */
1439 if ( strlen(noproxy_item) == 1 && noproxy_item[0] == '*'){
1440 if (verbose >= 1){
1441 printf("* noproxy includes '*' which disables proxy for all host name incl. : %s / server address incl. : %s\n", host_name_display , server_address_clean);
1442 }
1443 free(curlopt_noproxy_copy);
1444 free(server_address_clean);
1445 return 0;
1446 }
1447
1448 /* direct comparison with the server_address */
1449 if( server_address_clean != NULL && strlen(server_address_clean) == strlen(noproxy_item) && strcmp(server_address_clean, noproxy_item) == 0){
1450 if (verbose >= 1){
1451 printf("* server_address is in the no_proxy list: %s\n", noproxy_item);
1452 }
1453 free(curlopt_noproxy_copy);
1454 free(server_address_clean);
1455 return 0;
1456 }
1457
1458 /* direct comparison with the host_name */
1459 if( working_state.host_name != NULL && host_name_len == noproxy_item_len && strcmp(working_state.host_name, noproxy_item) == 0){
1460 if (verbose >= 1){
1461 printf("* host_name is in the no_proxy list: %s\n", noproxy_item);
1462 }
1463 free(curlopt_noproxy_copy);
1464 free(server_address_clean);
1465 return 0;
1466 }
1467
1468 /* check if hostname is a subdomain of the item, e.g www.example.com when token is example.com */
1469 /* subdomain1.acme.com will not will use a proxy if you only specify 'acme' in the noproxy */
1470 /* check if noproxy_item is a suffix */
1471 /* check if the character just before the suffix is '.' */
1472 if( working_state.host_name != NULL && host_name_len > noproxy_item_len){
1473 unsigned long suffix_start_idx = host_name_len - noproxy_item_len;
1474 if (strcmp(working_state.host_name + suffix_start_idx, noproxy_item ) == 0 && working_state.host_name[suffix_start_idx-1] == '.' ){
1475 if (verbose >= 1){
1476 printf("* host_name: %s is a subdomain of the no_proxy list item: %s\n", working_state.host_name , noproxy_item);
1477 }
1478 free(curlopt_noproxy_copy);
1479 free(server_address_clean);
1480 return 0;
1481 }
1482 }
1483
1484 // noproxy_item could be a CIDR IP range
1485 if( server_address_clean != NULL && strlen(server_address_clean)){
1486
1487 int ip_addr_inside_cidr_ret = ip_addr_inside_cidr(noproxy_item, server_address_clean);
1488
1489 switch(ip_addr_inside_cidr_ret){
1490 case 1:
1491 return 0;
1492 break;
1493 case 0:
1494 if(verbose >= 1){
1495 printf("server address: %s is not inside IP cidr: %s\n", server_address_clean, noproxy_item);
1496 }
1497 break;
1498 case -1:
1499 if(verbose >= 1){
1500 printf("could not fully determine if server address: %s is inside the IP cidr: %s\n", server_address_clean, noproxy_item);
1501 }
1502 break;
1503 }
1504 }
1505
1506 noproxy_item = strtok(NULL, ",");
1507 }
1508
1509 free(curlopt_noproxy_copy);
1510 }
1511
1512 if (working_state.curlopt_proxy != NULL){
1513 // Libcurl documentation
1514 // 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.
1515 if ( strlen(working_state.curlopt_proxy) == 0){
1516 return 0;
1517 }
1518
1519 if ( strncmp( working_state.curlopt_proxy, "http://", 7) == 0){
1520 if (verbose >= 1){
1521 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);
1522 }
1523 free(server_address_clean);
1524 return 1;
1525 }
1526
1527 if ( strncmp( working_state.curlopt_proxy, "https://", 8) == 0){
1528 if (verbose >= 1){
1529 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);
1530 }
1531 free(server_address_clean);
1532 return 1;
1533 }
1534
1535 if ( strncmp( working_state.curlopt_proxy, "socks4://", 9) == 0){
1536 if (verbose >= 1){
1537 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);
1538 }
1539 free(server_address_clean);
1540 return 0;
1541 }
1542
1543 if ( strncmp( working_state.curlopt_proxy, "socks4a://", 10) == 0){
1544 if (verbose >= 1){
1545 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);
1546 }
1547 free(server_address_clean);
1548 return 1;
1549 }
1550
1551 if ( strncmp( working_state.curlopt_proxy, "socks5://", 9) == 0){
1552 if (verbose >= 1){
1553 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);
1554 }
1555 free(server_address_clean);
1556 return 0;
1557 }
1558
1559 if ( strncmp( working_state.curlopt_proxy, "socks5h://", 10) == 0){
1560 if (verbose >= 1){
1561 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);
1562 }
1563 free(server_address_clean);
1564 return 1;
1565 }
1566
1567 // Libcurl documentation:
1568 // Without a scheme prefix, CURLOPT_PROXYTYPE can be used to specify which kind of proxy the string identifies.
1569 // We do not set this value
1570 // Without a scheme, it is treated as an http proxy
1571
1572 return 1;
1573 }
1574
1575 if (verbose >= 1){
1576 printf("* proxy scheme is unknown/unavailable, no proxy is assumed for host: %s or server_address: %s\n", host_name_display, server_address_clean);
1577 }
1578
1579 free(server_address_clean);
1580 return 0;
1581}
1582
1583int ip_addr_inside_cidr(const char* cidr_region_or_ip_addr, const char* target_ip){
1584 unsigned int slash_count = 0;
1585 unsigned int last_slash_idx = 0;
1586 for(size_t i = 0; i < strlen(cidr_region_or_ip_addr); i++){
1587 if(cidr_region_or_ip_addr[i] == '/'){
1588 slash_count++;
1589 last_slash_idx = (unsigned int)i;
1590 }
1591 }
1592
1593 char *cidr_ip_part = NULL;
1594 int prefix_length = 0;
1595
1596 if (slash_count == 0) {
1597 cidr_ip_part = strdup(cidr_region_or_ip_addr);
1598 if (!cidr_ip_part) return -1;
1599 } else if (slash_count == 1) {
1600 cidr_ip_part = strndup(cidr_region_or_ip_addr, last_slash_idx);
1601 if (!cidr_ip_part) return -1;
1602
1603 errno = 0;
1604 long long tmp = strtoll(cidr_region_or_ip_addr + last_slash_idx + 1, NULL, 10);
1605 if (errno == ERANGE) {
1606 if (verbose >= 1) {
1607 printf("cidr_region_or_ip: %s , could not parse subnet length\n", cidr_region_or_ip_addr);
1608 }
1609 free(cidr_ip_part);
1610 return -1;
1611 }
1612 prefix_length = (int)tmp;
1613 } else {
1614 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);
1615 return -1;
1616 }
1617
1618 int cidr_addr_family, target_addr_family;
1619 if (strchr(cidr_ip_part, ':')){
1620 cidr_addr_family = AF_INET6;
1621 } else {
1622 cidr_addr_family = AF_INET;
1623 }
1624
1625 if (strchr(target_ip, ':')){
1626 target_addr_family = AF_INET6;
1627 } else {
1628 target_addr_family = AF_INET;
1629 }
1630
1631 if (cidr_addr_family != target_addr_family){
1632 if (verbose >= 1){
1633 printf("cidr address: %s and target ip address: %s have different address families\n", cidr_ip_part, target_ip);
1634 }
1635 free(cidr_ip_part);
1636 return 0;
1637 }
1638
1639 // If no prefix is given, treat the cidr as a single address (full-length prefix)
1640 if (slash_count == 0) {
1641 prefix_length = (cidr_addr_family == AF_INET) ? 32 : 128;
1642 }
1643
1644 int max_bits = (cidr_addr_family == AF_INET) ? 32u : 128u;
1645 if (prefix_length < 0 || prefix_length > max_bits) {
1646 if (verbose >= 1) {
1647 printf("cidr_region_or_ip: %s has invalid prefix length: %u\n", cidr_region_or_ip_addr, prefix_length);
1648 }
1649 free(cidr_ip_part);
1650 return -1;
1651 }
1652
1653 if (verbose >= 1){
1654 printf("cidr_region_or_ip: %s , has prefix length: %u\n", cidr_region_or_ip_addr, prefix_length);
1655 }
1656
1657 int inet_pton_rc;
1658 uint8_t *cidr_bytes = NULL;
1659 uint8_t *target_bytes = NULL;
1660 uint8_t cidr_buf[16];
1661 uint8_t target_buf[16];
1662 size_t total_bytes = 0;
1663
1664 if (cidr_addr_family == AF_INET) {
1665 struct in_addr cidr_ipv4;
1666 struct in_addr target_ipv4;
1667 inet_pton_rc = inet_pton(AF_INET, cidr_ip_part, &cidr_ipv4);
1668 if (inet_pton_rc != 1) {
1669 if (verbose >= 1) {
1670 printf("ip string: %s contains characters not valid for its address family: IPv4\n", cidr_ip_part);
1671 }
1672 free(cidr_ip_part);
1673 return -1;
1674 }
1675 inet_pton_rc = inet_pton(AF_INET, target_ip, &target_ipv4);
1676 if (inet_pton_rc != 1) {
1677 if (verbose >= 1) {
1678 printf("ip string: %s contains characters not valid for its address family: IPv4\n", target_ip);
1679 }
1680 free(cidr_ip_part);
1681 return -1;
1682 }
1683 // copy the addresses in network byte order to a buffer for comparison
1684 memcpy(cidr_buf, &cidr_ipv4.s_addr, 4);
1685 memcpy(target_buf, &target_ipv4.s_addr, 4);
1686 cidr_bytes = cidr_buf;
1687 target_bytes = target_buf;
1688 total_bytes = 4;
1689 } else {
1690 struct in6_addr cidr_ipv6;
1691 struct in6_addr target_ipv6;
1692 inet_pton_rc = inet_pton(AF_INET6, cidr_ip_part, &cidr_ipv6);
1693 if (inet_pton_rc != 1) {
1694 if (verbose >= 1) {
1695 printf("ip string: %s contains characters not valid for its address family: IPv6\n", cidr_ip_part);
1696 }
1697 free(cidr_ip_part);
1698 return -1;
1699 }
1700 inet_pton_rc = inet_pton(AF_INET6, target_ip, &target_ipv6);
1701 if (inet_pton_rc != 1) {
1702 if (verbose >= 1) {
1703 printf("ip string: %s contains characters not valid for its address family: IPv6\n", target_ip);
1704 }
1705 free(cidr_ip_part);
1706 return -1;
1707 }
1708 memcpy(cidr_buf, &cidr_ipv6, 16);
1709 memcpy(target_buf, &target_ipv6, 16);
1710 cidr_bytes = cidr_buf;
1711 target_bytes = target_buf;
1712 total_bytes = 16;
1713 }
1714
1715 int prefix_bytes = prefix_length / 8;
1716 int prefix_bits = prefix_length % 8;
1717
1718 if (prefix_bytes > 0) {
1719 if (memcmp(cidr_bytes, target_bytes, (size_t)prefix_bytes) != 0) {
1720 if (verbose >= 1) {
1721 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);
1722 }
1723 free(cidr_ip_part);
1724 return 0;
1725 }
1726 }
1727
1728 if (prefix_bits != 0) {
1729 uint8_t cidr_oct = cidr_bytes[prefix_bytes];
1730 uint8_t target_oct = target_bytes[prefix_bytes];
1731 // the mask has first prefix_bits bits 1, the rest as 0
1732 uint8_t mask = (uint8_t)(0xFFu << (8 - prefix_bits));
1733 if ((cidr_oct & mask) != (target_oct & mask)) {
1734 if (verbose >= 1) {
1735 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);
1736 }
1737 free(cidr_ip_part);
1738 return 0;
1739 }
1740 }
1741
1742 free(cidr_ip_part);
1743 return 1;
1744}
diff --git a/plugins/check_curl.d/check_curl_helpers.h b/plugins/check_curl.d/check_curl_helpers.h
new file mode 100644
index 00000000..cc47bf9d
--- /dev/null
+++ b/plugins/check_curl.d/check_curl_helpers.h
@@ -0,0 +1,137 @@
1#include "./config.h"
2#include <curl/curl.h>
3#include "../picohttpparser/picohttpparser.h"
4#include "output.h"
5
6#if defined(HAVE_SSL) && defined(USE_OPENSSL)
7# include <openssl/opensslv.h>
8#endif
9
10enum {
11 MAX_IPV4_HOSTLENGTH = 255,
12};
13
14/* for buffers for header and body */
15typedef struct {
16 size_t buflen;
17 size_t bufsize;
18 char *buf;
19} curlhelp_write_curlbuf;
20
21/* for buffering the data sent in PUT */
22typedef struct {
23 size_t buflen;
24 off_t pos;
25 char *buf;
26} curlhelp_read_curlbuf;
27
28/* for parsing the HTTP status line */
29typedef struct {
30 int http_major; /* major version of the protocol, always 1 (HTTP/0.9
31 * never reached the big internet most likely) */
32 int http_minor; /* minor version of the protocol, usually 0 or 1 */
33 int http_code; /* HTTP return code as in RFC 2145 */
34 int http_subcode; /* Microsoft IIS extension, HTTP subcodes, see
35 * http://support.microsoft.com/kb/318380/en-us */
36 const char *msg; /* the human readable message */
37 char *first_line; /* a copy of the first line */
38} curlhelp_statusline;
39
40typedef struct {
41 bool curl_global_initialized;
42 bool curl_easy_initialized;
43
44 bool body_buf_initialized;
45 curlhelp_write_curlbuf *body_buf;
46
47 bool header_buf_initialized;
48 curlhelp_write_curlbuf *header_buf;
49
50 bool status_line_initialized;
51 curlhelp_statusline *status_line;
52
53 bool put_buf_initialized;
54 curlhelp_read_curlbuf *put_buf;
55
56 CURL *curl;
57
58 struct curl_slist *header_list;
59 struct curl_slist *host;
60} check_curl_global_state;
61
62/* to know the underlying SSL library used by libcurl */
63typedef enum curlhelp_ssl_library {
64 CURLHELP_SSL_LIBRARY_UNKNOWN,
65 CURLHELP_SSL_LIBRARY_OPENSSL,
66 CURLHELP_SSL_LIBRARY_LIBRESSL,
67 CURLHELP_SSL_LIBRARY_GNUTLS,
68 CURLHELP_SSL_LIBRARY_NSS
69} curlhelp_ssl_library;
70
71#define MAKE_LIBCURL_VERSION(major, minor, patch) ((major) * 0x10000 + (minor) * 0x100 + (patch))
72
73typedef struct {
74 int errorcode;
75 check_curl_global_state curl_state;
76 check_curl_working_state working_state;
77} check_curl_configure_curl_wrapper;
78
79check_curl_configure_curl_wrapper check_curl_configure_curl(check_curl_static_curl_config config,
80 check_curl_working_state working_state,
81 bool check_cert,
82 bool on_redirect_dependent,
83 int follow_method, long max_depth);
84
85void handle_curl_option_return_code(CURLcode res, const char *option);
86
87int curlhelp_initwritebuffer(curlhelp_write_curlbuf **buf);
88size_t curlhelp_buffer_write_callback(void * /*buffer*/, size_t /*size*/, size_t /*nmemb*/,
89 void * /*stream*/);
90void curlhelp_freewritebuffer(curlhelp_write_curlbuf * /*buf*/);
91
92int curlhelp_initreadbuffer(curlhelp_read_curlbuf **buf, const char * /*data*/, size_t /*datalen*/);
93size_t curlhelp_buffer_read_callback(void * /*buffer*/, size_t /*size*/, size_t /*nmemb*/,
94 void * /*stream*/);
95void curlhelp_freereadbuffer(curlhelp_read_curlbuf * /*buf*/);
96
97curlhelp_ssl_library curlhelp_get_ssl_library(void);
98const char *curlhelp_get_ssl_library_string(curlhelp_ssl_library /*ssl_library*/);
99
100typedef union {
101 struct curl_slist *to_info;
102 struct curl_certinfo *to_certinfo;
103} cert_ptr_union;
104int net_noopenssl_check_certificate(cert_ptr_union *, int, int);
105
106int curlhelp_parse_statusline(const char * /*buf*/, curlhelp_statusline * /*status_line*/);
107void curlhelp_free_statusline(curlhelp_statusline * /*status_line*/);
108
109char *get_header_value(const struct phr_header *headers, size_t nof_headers, const char *header);
110mp_subcheck check_document_dates(const curlhelp_write_curlbuf * /*header_buf*/,
111 int /*maximum_age*/);
112size_t get_content_length(const curlhelp_write_curlbuf *header_buf,
113 const curlhelp_write_curlbuf *body_buf);
114int lookup_host(const char *host, char *buf, size_t buflen, sa_family_t addr_family);
115CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm);
116
117#define INET_ADDR_MAX_SIZE INET6_ADDRSTRLEN
118const char *strrstr2(const char *haystack, const char *needle);
119
120void cleanup(check_curl_global_state global_state);
121
122bool expected_statuscode(const char *reply, const char *statuscodes);
123char *string_statuscode(int major, int minor);
124
125void test_file(char *path);
126mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_till_exp,
127 int crit_days_till_exp);
128char *fmt_url(check_curl_working_state workingState);
129
130
131/* function that will determine if the host or the proxy resolves the target hostname
132returns 0 if requester resolves the hostname locally, 1 if proxy resolves the hostname */
133int determine_hostname_resolver(const check_curl_working_state working_state, const check_curl_static_curl_config config);
134
135/* 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
136returns 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. */
137int 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
new file mode 100644
index 00000000..bcdf3010
--- /dev/null
+++ b/plugins/check_curl.d/config.h
@@ -0,0 +1,123 @@
1#pragma once
2
3#include "../../config.h"
4#include "../common.h"
5#include "../../lib/states.h"
6#include "../../lib/thresholds.h"
7#include <stddef.h>
8#include <string.h>
9#include <sys/socket.h>
10#include "curl/curl.h"
11#include "perfdata.h"
12#include "regex.h"
13
14enum {
15 MAX_RE_SIZE = 1024,
16 HTTP_PORT = 80,
17 HTTPS_PORT = 443,
18 MAX_PORT = 65535,
19 DEFAULT_MAX_REDIRS = 15
20};
21
22enum {
23 FOLLOW_HTTP_CURL = 0,
24 FOLLOW_LIBCURL = 1
25};
26
27enum {
28 STICKY_NONE = 0,
29 STICKY_HOST = 1,
30 STICKY_PORT = 2
31};
32
33#define HTTP_EXPECT "HTTP/"
34#define DEFAULT_BUFFER_SIZE 2048
35#define DEFAULT_SERVER_URL "/"
36
37typedef struct {
38 char *server_address;
39 char *server_url;
40 char *host_name;
41
42 char *http_method;
43
44 char *http_post_data;
45
46 unsigned short virtualPort;
47 unsigned short serverPort;
48
49 bool use_ssl;
50 bool no_body;
51
52 /* curl CURLOPT_PROXY option will be set to this value if not NULL */
53 char *curlopt_proxy;
54 /* curl CURLOPT_NOPROXY option will be set to this value if not NULL */
55 char *curlopt_noproxy;
56} check_curl_working_state;
57
58check_curl_working_state check_curl_working_state_init();
59
60typedef struct {
61 bool automatic_decompression;
62 bool haproxy_protocol;
63 long socket_timeout;
64 sa_family_t sin_family;
65 long curl_http_version;
66 char **http_opt_headers;
67 size_t http_opt_headers_count;
68 long ssl_version;
69 char *client_cert;
70 char *client_privkey;
71 char *ca_cert;
72 bool verify_peer_and_host;
73 char proxy[DEFAULT_BUFFER_SIZE];
74 char no_proxy[DEFAULT_BUFFER_SIZE];
75 char user_agent[DEFAULT_BUFFER_SIZE];
76 char proxy_auth[MAX_INPUT_BUFFER];
77 char user_auth[MAX_INPUT_BUFFER];
78 char *http_content_type;
79 char *cookie_jar_file;
80} check_curl_static_curl_config;
81
82typedef struct {
83 check_curl_working_state initial_config;
84
85 check_curl_static_curl_config curl_config;
86 long max_depth;
87 int followmethod;
88 int followsticky;
89
90 int maximum_age;
91
92 // the original regex string from the command line
93 char regexp[MAX_RE_SIZE];
94
95 // the compiled regex for usage later
96 regex_t compiled_regex;
97
98 mp_state_enum state_regex;
99 bool invert_regex;
100 bool check_cert;
101 bool continue_after_check_cert;
102 int days_till_exp_warn;
103 int days_till_exp_crit;
104 mp_thresholds thlds;
105 mp_range page_length_limits;
106 bool page_length_limits_is_set;
107 struct {
108 char string[MAX_INPUT_BUFFER];
109 bool is_present;
110 } server_expect;
111 char string_expect[MAX_INPUT_BUFFER];
112 char header_expect[MAX_INPUT_BUFFER];
113 mp_state_enum on_redirect_result_state;
114 bool on_redirect_dependent;
115
116 bool show_extended_perfdata;
117 bool show_body;
118
119 bool output_format_is_set;
120 mp_output_format output_format;
121} check_curl_config;
122
123check_curl_config check_curl_config_init();