summaryrefslogtreecommitdiffstats
path: root/plugins/check_curl.d/check_curl_helpers.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/check_curl.d/check_curl_helpers.c')
-rw-r--r--plugins/check_curl.d/check_curl_helpers.c1295
1 files changed, 1295 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..e4e7bef6
--- /dev/null
+++ b/plugins/check_curl.d/check_curl_helpers.c
@@ -0,0 +1,1295 @@
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 <stdlib.h>
7#include <string.h>
8#include "../utils.h"
9#include "check_curl.d/config.h"
10#include "output.h"
11#include "perfdata.h"
12#include "states.h"
13
14extern int verbose;
15char errbuf[MAX_INPUT_BUFFER];
16bool is_openssl_callback = false;
17bool add_sslctx_verify_fun = false;
18
19check_curl_configure_curl_wrapper
20check_curl_configure_curl(const check_curl_static_curl_config config,
21 check_curl_working_state working_state, bool check_cert,
22 bool on_redirect_dependent, int follow_method, int max_depth) {
23 check_curl_configure_curl_wrapper result = {
24 .errorcode = OK,
25 .curl_state =
26 {
27 .curl_global_initialized = false,
28 .curl_easy_initialized = false,
29 .curl = NULL,
30
31 .body_buf_initialized = false,
32 .body_buf = NULL,
33 .header_buf_initialized = false,
34 .header_buf = NULL,
35 .status_line_initialized = false,
36 .status_line = NULL,
37 .put_buf_initialized = false,
38 .put_buf = NULL,
39
40 .header_list = NULL,
41 .host = NULL,
42 },
43 };
44
45 if ((result.curl_state.status_line = calloc(1, sizeof(curlhelp_statusline))) == NULL) {
46 die(STATE_UNKNOWN, "HTTP UNKNOWN - allocation of statusline failed\n");
47 }
48
49 if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
50 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_global_init failed\n");
51 }
52 result.curl_state.curl_global_initialized = true;
53
54 if ((result.curl_state.curl = curl_easy_init()) == NULL) {
55 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_easy_init failed\n");
56 }
57 result.curl_state.curl_easy_initialized = true;
58
59 if (verbose >= 1) {
60 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_VERBOSE, 1),
61 "CURLOPT_VERBOSE");
62 }
63
64 /* print everything on stdout like check_http would do */
65 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_STDERR, stdout),
66 "CURLOPT_STDERR");
67
68 if (config.automatic_decompression) {
69#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6)
70 handle_curl_option_return_code(
71 curl_easy_setopt(result.curl_state.curl, CURLOPT_ACCEPT_ENCODING, ""),
72 "CURLOPT_ACCEPT_ENCODING");
73#else
74 handle_curl_option_return_code(
75 curl_easy_setopt(result.curl_state.curl, CURLOPT_ENCODING, ""), "CURLOPT_ENCODING");
76#endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6) */
77 }
78
79 /* initialize buffer for body of the answer */
80 if (curlhelp_initwritebuffer(&result.curl_state.body_buf) < 0) {
81 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for body\n");
82 }
83 result.curl_state.body_buf_initialized = true;
84
85 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEFUNCTION,
86 curlhelp_buffer_write_callback),
87 "CURLOPT_WRITEFUNCTION");
88 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEDATA,
89 (void *)result.curl_state.body_buf),
90 "CURLOPT_WRITEDATA");
91
92 /* initialize buffer for header of the answer */
93 if (curlhelp_initwritebuffer(&result.curl_state.header_buf) < 0) {
94 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for header\n");
95 }
96 result.curl_state.header_buf_initialized = true;
97
98 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_HEADERFUNCTION,
99 curlhelp_buffer_write_callback),
100 "CURLOPT_HEADERFUNCTION");
101 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEHEADER,
102 (void *)result.curl_state.header_buf),
103 "CURLOPT_WRITEHEADER");
104
105 /* set the error buffer */
106 handle_curl_option_return_code(
107 curl_easy_setopt(result.curl_state.curl, CURLOPT_ERRORBUFFER, errbuf),
108 "CURLOPT_ERRORBUFFER");
109
110 /* set timeouts */
111 handle_curl_option_return_code(
112 curl_easy_setopt(result.curl_state.curl, CURLOPT_CONNECTTIMEOUT, config.socket_timeout),
113 "CURLOPT_CONNECTTIMEOUT");
114
115 handle_curl_option_return_code(
116 curl_easy_setopt(result.curl_state.curl, CURLOPT_TIMEOUT, config.socket_timeout),
117 "CURLOPT_TIMEOUT");
118
119 /* enable haproxy protocol */
120 if (config.haproxy_protocol) {
121 handle_curl_option_return_code(
122 curl_easy_setopt(result.curl_state.curl, CURLOPT_HAPROXYPROTOCOL, 1L),
123 "CURLOPT_HAPROXYPROTOCOL");
124 }
125
126 // fill dns resolve cache to make curl connect to the given server_address instead of the
127 // host_name, only required for ssl, because we use the host_name later on to make SNI happy
128 char dnscache[DEFAULT_BUFFER_SIZE];
129 char addrstr[DEFAULT_BUFFER_SIZE / 2];
130 if (working_state.use_ssl && working_state.host_name != NULL) {
131 char *tmp_mod_address;
132
133 /* lookup_host() requires an IPv6 address without the brackets. */
134 if ((strnlen(working_state.server_address, MAX_IPV4_HOSTLENGTH) > 2) &&
135 (working_state.server_address[0] == '[')) {
136 // Duplicate and strip the leading '['
137 tmp_mod_address =
138 strndup(working_state.server_address + 1, strlen(working_state.server_address) - 2);
139 } else {
140 tmp_mod_address = working_state.server_address;
141 }
142
143 int res;
144 if ((res = lookup_host(tmp_mod_address, addrstr, DEFAULT_BUFFER_SIZE / 2,
145 config.sin_family)) != 0) {
146 die(STATE_CRITICAL,
147 _("Unable to lookup IP address for '%s': getaddrinfo returned %d - %s"),
148 working_state.server_address, res, gai_strerror(res));
149 }
150
151 snprintf(dnscache, DEFAULT_BUFFER_SIZE, "%s:%d:%s", working_state.host_name,
152 working_state.serverPort, addrstr);
153 result.curl_state.host = curl_slist_append(NULL, dnscache);
154 curl_easy_setopt(result.curl_state.curl, CURLOPT_RESOLVE, result.curl_state.host);
155
156 if (verbose >= 1) {
157 printf("* curl CURLOPT_RESOLVE: %s\n", dnscache);
158 }
159 }
160
161 // If server_address is an IPv6 address it must be surround by square brackets
162 struct in6_addr tmp_in_addr;
163 if (inet_pton(AF_INET6, working_state.server_address, &tmp_in_addr) == 1) {
164 char *new_server_address = calloc(strlen(working_state.server_address) + 3, sizeof(char));
165 if (new_server_address == NULL) {
166 die(STATE_UNKNOWN, "HTTP UNKNOWN - Unable to allocate memory\n");
167 }
168 snprintf(new_server_address, strlen(working_state.server_address) + 3, "[%s]",
169 working_state.server_address);
170 working_state.server_address = new_server_address;
171 }
172
173 /* compose URL: use the address we want to connect to, set Host: header later */
174 char *url = fmt_url(working_state);
175
176 if (verbose >= 1) {
177 printf("* curl CURLOPT_URL: %s\n", url);
178 }
179 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, url),
180 "CURLOPT_URL");
181
182 free(url);
183
184 /* extract proxy information for legacy proxy https requests */
185 if (!strcmp(working_state.http_method, "CONNECT") ||
186 strstr(working_state.server_url, "http") == working_state.server_url) {
187 handle_curl_option_return_code(
188 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXY, working_state.server_address),
189 "CURLOPT_PROXY");
190 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYPORT,
191 (long)working_state.serverPort),
192 "CURLOPT_PROXYPORT");
193 if (verbose >= 2) {
194 printf("* curl CURLOPT_PROXY: %s:%d\n", working_state.server_address,
195 working_state.serverPort);
196 }
197 working_state.http_method = "GET";
198 handle_curl_option_return_code(
199 curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, working_state.server_url),
200 "CURLOPT_URL");
201 }
202
203 /* disable body for HEAD request */
204 if (working_state.http_method && !strcmp(working_state.http_method, "HEAD")) {
205 working_state.no_body = true;
206 }
207
208 /* set HTTP protocol version */
209 handle_curl_option_return_code(
210 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTP_VERSION, config.curl_http_version),
211 "CURLOPT_HTTP_VERSION");
212
213 /* set HTTP method */
214 if (working_state.http_method) {
215 if (!strcmp(working_state.http_method, "POST")) {
216 handle_curl_option_return_code(
217 curl_easy_setopt(result.curl_state.curl, CURLOPT_POST, 1), "CURLOPT_POST");
218 } else if (!strcmp(working_state.http_method, "PUT")) {
219 handle_curl_option_return_code(
220 curl_easy_setopt(result.curl_state.curl, CURLOPT_UPLOAD, 1), "CURLOPT_UPLOAD");
221 } else {
222 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
223 CURLOPT_CUSTOMREQUEST,
224 working_state.http_method),
225 "CURLOPT_CUSTOMREQUEST");
226 }
227 }
228
229 char *force_host_header = NULL;
230 /* check if Host header is explicitly set in options */
231 if (config.http_opt_headers_count) {
232 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
233 if (strncmp(config.http_opt_headers[i], "Host:", 5) == 0) {
234 force_host_header = config.http_opt_headers[i];
235 }
236 }
237 }
238
239 /* set hostname (virtual hosts), not needed if CURLOPT_CONNECT_TO is used, but left in
240 * anyway */
241 char http_header[DEFAULT_BUFFER_SIZE];
242 if (working_state.host_name != NULL && force_host_header == NULL) {
243 if ((working_state.virtualPort != HTTP_PORT && !working_state.use_ssl) ||
244 (working_state.virtualPort != HTTPS_PORT && working_state.use_ssl)) {
245 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s:%d", working_state.host_name,
246 working_state.virtualPort);
247 } else {
248 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s", working_state.host_name);
249 }
250 result.curl_state.header_list =
251 curl_slist_append(result.curl_state.header_list, http_header);
252 }
253
254 /* always close connection, be nice to servers */
255 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Connection: close");
256 result.curl_state.header_list = curl_slist_append(result.curl_state.header_list, http_header);
257
258 /* attach additional headers supplied by the user */
259 /* optionally send any other header tag */
260 if (config.http_opt_headers_count) {
261 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
262 result.curl_state.header_list =
263 curl_slist_append(result.curl_state.header_list, config.http_opt_headers[i]);
264 }
265 }
266
267 /* set HTTP headers */
268 handle_curl_option_return_code(
269 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTPHEADER, result.curl_state.header_list),
270 "CURLOPT_HTTPHEADER");
271
272#ifdef LIBCURL_FEATURE_SSL
273 /* set SSL version, warn about insecure or unsupported versions */
274 if (working_state.use_ssl) {
275 handle_curl_option_return_code(
276 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLVERSION, config.ssl_version),
277 "CURLOPT_SSLVERSION");
278 }
279
280 /* client certificate and key to present to server (SSL) */
281 if (config.client_cert) {
282 handle_curl_option_return_code(
283 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLCERT, config.client_cert),
284 "CURLOPT_SSLCERT");
285 }
286
287 if (config.client_privkey) {
288 handle_curl_option_return_code(
289 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLKEY, config.client_privkey),
290 "CURLOPT_SSLKEY");
291 }
292
293 if (config.ca_cert) {
294 handle_curl_option_return_code(
295 curl_easy_setopt(result.curl_state.curl, CURLOPT_CAINFO, config.ca_cert),
296 "CURLOPT_CAINFO");
297 }
298
299 if (config.ca_cert || config.verify_peer_and_host) {
300 /* per default if we have a CA verify both the peer and the
301 * hostname in the certificate, can be switched off later */
302 handle_curl_option_return_code(
303 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 1),
304 "CURLOPT_SSL_VERIFYPEER");
305 handle_curl_option_return_code(
306 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 2),
307 "CURLOPT_SSL_VERIFYHOST");
308 } else {
309 /* backward-compatible behaviour, be tolerant in checks
310 * TODO: depending on more options have aspects we want
311 * to be less tolerant about ssl verfications
312 */
313 handle_curl_option_return_code(
314 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 0),
315 "CURLOPT_SSL_VERIFYPEER");
316 handle_curl_option_return_code(
317 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 0),
318 "CURLOPT_SSL_VERIFYHOST");
319 }
320
321 /* detect SSL library used by libcurl */
322 curlhelp_ssl_library ssl_library = curlhelp_get_ssl_library();
323
324 /* try hard to get a stack of certificates to verify against */
325 if (check_cert) {
326# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1)
327 /* inform curl to report back certificates */
328 switch (ssl_library) {
329 case CURLHELP_SSL_LIBRARY_OPENSSL:
330 case CURLHELP_SSL_LIBRARY_LIBRESSL:
331 /* set callback to extract certificate with OpenSSL context function (works with
332 * OpenSSL-style libraries only!) */
333# ifdef USE_OPENSSL
334 /* libcurl and monitoring plugins built with OpenSSL, good */
335 add_sslctx_verify_fun = true;
336 is_openssl_callback = true;
337# endif /* USE_OPENSSL */
338 /* libcurl is built with OpenSSL, monitoring plugins, so falling
339 * back to manually extracting certificate information */
340 handle_curl_option_return_code(
341 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
342 break;
343
344 case CURLHELP_SSL_LIBRARY_NSS:
345# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0)
346 /* NSS: support for CERTINFO is implemented since 7.34.0 */
347 handle_curl_option_return_code(
348 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
349# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
350 die(STATE_CRITICAL,
351 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
352 "'%s' is too old)\n",
353 curlhelp_get_ssl_library_string(ssl_library));
354# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
355 break;
356
357 case CURLHELP_SSL_LIBRARY_GNUTLS:
358# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0)
359 /* GnuTLS: support for CERTINFO is implemented since 7.42.0 */
360 handle_curl_option_return_code(
361 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
362# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
363 die(STATE_CRITICAL,
364 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
365 "'%s' is too old)\n",
366 curlhelp_get_ssl_library_string(ssl_library));
367# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
368 break;
369
370 case CURLHELP_SSL_LIBRARY_UNKNOWN:
371 default:
372 die(STATE_CRITICAL,
373 "HTTP CRITICAL - Cannot retrieve certificates (unknown SSL library '%s', must "
374 "implement first)\n",
375 curlhelp_get_ssl_library_string(ssl_library));
376 break;
377 }
378# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
379 /* old libcurl, our only hope is OpenSSL, otherwise we are out of luck */
380 if (ssl_library == CURLHELP_SSL_LIBRARY_OPENSSL ||
381 ssl_library == CURLHELP_SSL_LIBRARY_LIBRESSL) {
382 add_sslctx_verify_fun = true;
383 } else {
384 die(STATE_CRITICAL, "HTTP CRITICAL - Cannot retrieve certificates (no "
385 "CURLOPT_SSL_CTX_FUNCTION, no OpenSSL library or libcurl "
386 "too old and has no CURLOPT_CERTINFO)\n");
387 }
388# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
389 }
390
391# if LIBCURL_VERSION_NUM >= \
392 MAKE_LIBCURL_VERSION(7, 10, 6) /* required for CURLOPT_SSL_CTX_FUNCTION */
393 // ssl ctx function is not available with all ssl backends
394 if (curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, NULL) !=
395 CURLE_UNKNOWN_OPTION) {
396 handle_curl_option_return_code(
397 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun),
398 "CURLOPT_SSL_CTX_FUNCTION");
399 }
400# endif
401#endif /* LIBCURL_FEATURE_SSL */
402
403 /* set default or user-given user agent identification */
404 handle_curl_option_return_code(
405 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERAGENT, config.user_agent),
406 "CURLOPT_USERAGENT");
407
408 /* proxy-authentication */
409 if (strcmp(config.proxy_auth, "")) {
410 handle_curl_option_return_code(
411 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYUSERPWD, config.proxy_auth),
412 "CURLOPT_PROXYUSERPWD");
413 }
414
415 /* authentication */
416 if (strcmp(config.user_auth, "")) {
417 handle_curl_option_return_code(
418 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERPWD, config.user_auth),
419 "CURLOPT_USERPWD");
420 }
421 /* TODO: parameter auth method, bitfield of following methods:
422 * CURLAUTH_BASIC (default)
423 * CURLAUTH_DIGEST
424 * CURLAUTH_DIGEST_IE
425 * CURLAUTH_NEGOTIATE
426 * CURLAUTH_NTLM
427 * CURLAUTH_NTLM_WB
428 *
429 * convenience tokens for typical sets of methods:
430 * CURLAUTH_ANYSAFE: most secure, without BASIC
431 * or CURLAUTH_ANY: most secure, even BASIC if necessary
432 *
433 * handle_curl_option_return_code (curl_easy_setopt( curl, CURLOPT_HTTPAUTH,
434 * (long)CURLAUTH_DIGEST ), "CURLOPT_HTTPAUTH");
435 */
436
437 /* handle redirections */
438 if (on_redirect_dependent) {
439 if (follow_method == FOLLOW_LIBCURL) {
440 handle_curl_option_return_code(
441 curl_easy_setopt(result.curl_state.curl, CURLOPT_FOLLOWLOCATION, 1),
442 "CURLOPT_FOLLOWLOCATION");
443
444 /* default -1 is infinite, not good, could lead to zombie plugins!
445 Setting it to one bigger than maximal limit to handle errors nicely below
446 */
447 handle_curl_option_return_code(
448 curl_easy_setopt(result.curl_state.curl, CURLOPT_MAXREDIRS, max_depth + 1),
449 "CURLOPT_MAXREDIRS");
450
451 /* for now allow only http and https (we are a http(s) check plugin in the end) */
452#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 85, 0)
453 handle_curl_option_return_code(
454 curl_easy_setopt(result.curl_state.curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https"),
455 "CURLOPT_REDIR_PROTOCOLS_STR");
456#elif LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 4)
457 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
458 CURLOPT_REDIR_PROTOCOLS,
459 CURLPROTO_HTTP | CURLPROTO_HTTPS),
460 "CURLOPT_REDIRECT_PROTOCOLS");
461#endif
462
463 /* TODO: handle the following aspects of redirection, make them
464 * command line options too later:
465 CURLOPT_POSTREDIR: method switch
466 CURLINFO_REDIRECT_URL: custom redirect option
467 CURLOPT_REDIRECT_PROTOCOLS: allow people to step outside safe protocols
468 CURLINFO_REDIRECT_COUNT: get the number of redirects, print it, maybe a range
469 option here is nice like for expected page size?
470 */
471 } else {
472 /* old style redirection*/
473 }
474 }
475 /* no-body */
476 if (working_state.no_body) {
477 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_NOBODY, 1),
478 "CURLOPT_NOBODY");
479 }
480
481 /* IPv4 or IPv6 forced DNS resolution */
482 if (config.sin_family == AF_UNSPEC) {
483 handle_curl_option_return_code(
484 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER),
485 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_WHATEVER)");
486 } else if (config.sin_family == AF_INET) {
487 handle_curl_option_return_code(
488 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4),
489 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V4)");
490 }
491#if defined(USE_IPV6) && defined(LIBCURL_FEATURE_IPV6)
492 else if (config.sin_family == AF_INET6) {
493 handle_curl_option_return_code(
494 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6),
495 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V6)");
496 }
497#endif
498
499 /* either send http POST data (any data, not only POST)*/
500 if (!strcmp(working_state.http_method, "POST") || !strcmp(working_state.http_method, "PUT")) {
501 /* set content of payload for POST and PUT */
502 if (config.http_content_type) {
503 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Content-Type: %s",
504 config.http_content_type);
505 result.curl_state.header_list =
506 curl_slist_append(result.curl_state.header_list, http_header);
507 }
508 /* NULL indicates "HTTP Continue" in libcurl, provide an empty string
509 * in case of no POST/PUT data */
510 if (!working_state.http_post_data) {
511 working_state.http_post_data = "";
512 }
513
514 if (!strcmp(working_state.http_method, "POST")) {
515 /* POST method, set payload with CURLOPT_POSTFIELDS */
516 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
517 CURLOPT_POSTFIELDS,
518 working_state.http_post_data),
519 "CURLOPT_POSTFIELDS");
520 } else if (!strcmp(working_state.http_method, "PUT")) {
521 handle_curl_option_return_code(
522 curl_easy_setopt(result.curl_state.curl, CURLOPT_READFUNCTION,
523 (curl_read_callback)curlhelp_buffer_read_callback),
524 "CURLOPT_READFUNCTION");
525 if (curlhelp_initreadbuffer(&result.curl_state.put_buf, working_state.http_post_data,
526 strlen(working_state.http_post_data)) < 0) {
527 die(STATE_UNKNOWN,
528 "HTTP CRITICAL - out of memory allocating read buffer for PUT\n");
529 }
530 result.curl_state.put_buf_initialized = true;
531 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
532 CURLOPT_READDATA,
533 (void *)result.curl_state.put_buf),
534 "CURLOPT_READDATA");
535 handle_curl_option_return_code(
536 curl_easy_setopt(result.curl_state.curl, CURLOPT_INFILESIZE,
537 (curl_off_t)strlen(working_state.http_post_data)),
538 "CURLOPT_INFILESIZE");
539 }
540 }
541
542 /* cookie handling */
543 if (config.cookie_jar_file != NULL) {
544 /* enable reading cookies from a file, and if the filename is an empty string, only
545 * enable the curl cookie engine */
546 handle_curl_option_return_code(
547 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEFILE, config.cookie_jar_file),
548 "CURLOPT_COOKIEFILE");
549 /* now enable saving cookies to a file, but only if the filename is not an empty string,
550 * since writing it would fail */
551 if (*config.cookie_jar_file) {
552 handle_curl_option_return_code(
553 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEJAR, config.cookie_jar_file),
554 "CURLOPT_COOKIEJAR");
555 }
556 }
557
558 result.working_state = working_state;
559
560 return result;
561}
562
563void handle_curl_option_return_code(CURLcode res, const char *option) {
564 if (res != CURLE_OK) {
565 die(STATE_CRITICAL, _("Error while setting cURL option '%s': cURL returned %d - %s"),
566 option, res, curl_easy_strerror(res));
567 }
568}
569
570char *get_header_value(const struct phr_header *headers, const size_t nof_headers,
571 const char *header) {
572 for (size_t i = 0; i < nof_headers; i++) {
573 if (headers[i].name != NULL &&
574 strncasecmp(header, headers[i].name, max(headers[i].name_len, 4)) == 0) {
575 return strndup(headers[i].value, headers[i].value_len);
576 }
577 }
578 return NULL;
579}
580
581check_curl_working_state check_curl_working_state_init() {
582 check_curl_working_state result = {
583 .server_address = NULL,
584 .server_url = DEFAULT_SERVER_URL,
585 .host_name = NULL,
586 .http_method = NULL,
587 .http_post_data = NULL,
588 .virtualPort = 0,
589 .serverPort = HTTP_PORT,
590 .use_ssl = false,
591 .no_body = false,
592 };
593 return result;
594}
595
596check_curl_config check_curl_config_init() {
597 check_curl_config tmp = {
598 .initial_config = check_curl_working_state_init(),
599
600 .curl_config =
601 {
602 .automatic_decompression = false,
603 .socket_timeout = DEFAULT_SOCKET_TIMEOUT,
604 .haproxy_protocol = false,
605 .sin_family = AF_UNSPEC,
606 .curl_http_version = CURL_HTTP_VERSION_NONE,
607 .http_opt_headers = NULL,
608 .http_opt_headers_count = 0,
609 .ssl_version = CURL_SSLVERSION_DEFAULT,
610 .client_cert = NULL,
611 .client_privkey = NULL,
612 .ca_cert = NULL,
613 .verify_peer_and_host = false,
614 .user_agent = {'\0'},
615 .proxy_auth = "",
616 .user_auth = "",
617 .http_content_type = NULL,
618 .cookie_jar_file = NULL,
619 },
620 .max_depth = DEFAULT_MAX_REDIRS,
621 .followmethod = FOLLOW_HTTP_CURL,
622 .followsticky = STICKY_NONE,
623
624 .maximum_age = -1,
625 .regexp = {},
626 .compiled_regex = {},
627 .state_regex = STATE_CRITICAL,
628 .invert_regex = false,
629 .check_cert = false,
630 .continue_after_check_cert = false,
631 .days_till_exp_warn = 0,
632 .days_till_exp_crit = 0,
633 .thlds = mp_thresholds_init(),
634 .page_length_limits = mp_range_init(),
635 .page_length_limits_is_set = false,
636 .server_expect =
637 {
638 .string = HTTP_EXPECT,
639 .is_present = false,
640 },
641 .string_expect = "",
642 .header_expect = "",
643 .on_redirect_result_state = STATE_OK,
644 .on_redirect_dependent = false,
645
646 .show_extended_perfdata = false,
647 .show_body = false,
648
649 .output_format_is_set = false,
650 };
651
652 snprintf(tmp.curl_config.user_agent, DEFAULT_BUFFER_SIZE, "%s/v%s (monitoring-plugins %s, %s)",
653 "check_curl", NP_VERSION, VERSION, curl_version());
654
655 return tmp;
656}
657
658/* TODO: is there a better way in libcurl to check for the SSL library? */
659curlhelp_ssl_library curlhelp_get_ssl_library(void) {
660 curlhelp_ssl_library ssl_library = CURLHELP_SSL_LIBRARY_UNKNOWN;
661
662 curl_version_info_data *version_data = curl_version_info(CURLVERSION_NOW);
663 if (version_data == NULL) {
664 return CURLHELP_SSL_LIBRARY_UNKNOWN;
665 }
666
667 char *ssl_version = strdup(version_data->ssl_version);
668 if (ssl_version == NULL) {
669 return CURLHELP_SSL_LIBRARY_UNKNOWN;
670 }
671
672 char *library = strtok(ssl_version, "/");
673 if (library == NULL) {
674 return CURLHELP_SSL_LIBRARY_UNKNOWN;
675 }
676
677 if (strcmp(library, "OpenSSL") == 0) {
678 ssl_library = CURLHELP_SSL_LIBRARY_OPENSSL;
679 } else if (strcmp(library, "LibreSSL") == 0) {
680 ssl_library = CURLHELP_SSL_LIBRARY_LIBRESSL;
681 } else if (strcmp(library, "GnuTLS") == 0) {
682 ssl_library = CURLHELP_SSL_LIBRARY_GNUTLS;
683 } else if (strcmp(library, "NSS") == 0) {
684 ssl_library = CURLHELP_SSL_LIBRARY_NSS;
685 }
686
687 if (verbose >= 2) {
688 printf("* SSL library string is : %s %s (%d)\n", version_data->ssl_version, library,
689 ssl_library);
690 }
691
692 free(ssl_version);
693
694 return ssl_library;
695}
696
697const char *curlhelp_get_ssl_library_string(const curlhelp_ssl_library ssl_library) {
698 switch (ssl_library) {
699 case CURLHELP_SSL_LIBRARY_OPENSSL:
700 return "OpenSSL";
701 case CURLHELP_SSL_LIBRARY_LIBRESSL:
702 return "LibreSSL";
703 case CURLHELP_SSL_LIBRARY_GNUTLS:
704 return "GnuTLS";
705 case CURLHELP_SSL_LIBRARY_NSS:
706 return "NSS";
707 case CURLHELP_SSL_LIBRARY_UNKNOWN:
708 default:
709 return "unknown";
710 }
711}
712
713size_t get_content_length(const curlhelp_write_curlbuf *header_buf,
714 const curlhelp_write_curlbuf *body_buf) {
715 struct phr_header headers[255];
716 size_t nof_headers = 255;
717 size_t msglen;
718 curlhelp_statusline status_line;
719 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
720 &status_line.http_minor, &status_line.http_code, &status_line.msg,
721 &msglen, headers, &nof_headers, 0);
722
723 if (res == -1) {
724 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
725 }
726
727 char *content_length_s = get_header_value(headers, nof_headers, "content-length");
728 if (!content_length_s) {
729 return header_buf->buflen + body_buf->buflen;
730 }
731
732 content_length_s += strspn(content_length_s, " \t");
733 size_t content_length = atoi(content_length_s);
734 if (content_length != body_buf->buflen) {
735 /* TODO: should we warn if the actual and the reported body length don't match? */
736 }
737
738 if (content_length_s) {
739 free(content_length_s);
740 }
741
742 return header_buf->buflen + body_buf->buflen;
743}
744
745mp_subcheck check_document_dates(const curlhelp_write_curlbuf *header_buf, const int maximum_age) {
746 struct phr_header headers[255];
747 size_t nof_headers = 255;
748 curlhelp_statusline status_line;
749 size_t msglen;
750 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
751 &status_line.http_minor, &status_line.http_code, &status_line.msg,
752 &msglen, headers, &nof_headers, 0);
753
754 if (res == -1) {
755 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
756 }
757
758 char *server_date = get_header_value(headers, nof_headers, "date");
759 char *document_date = get_header_value(headers, nof_headers, "last-modified");
760
761 mp_subcheck sc_document_dates = mp_subcheck_init();
762 if (!server_date || !*server_date) {
763 xasprintf(&sc_document_dates.output, _("Server date unknown"));
764 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_UNKNOWN);
765 } else if (!document_date || !*document_date) {
766 xasprintf(&sc_document_dates.output, _("Document modification date unknown, "));
767 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
768 } else {
769 time_t srv_data = curl_getdate(server_date, NULL);
770 time_t doc_data = curl_getdate(document_date, NULL);
771
772 if (verbose >= 2) {
773 printf("* server date: '%s' (%d), doc_date: '%s' (%d)\n", server_date, (int)srv_data,
774 document_date, (int)doc_data);
775 }
776
777 if (srv_data <= 0) {
778 xasprintf(&sc_document_dates.output, _("Server date \"%100s\" unparsable"),
779 server_date);
780 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
781 } else if (doc_data <= 0) {
782
783 xasprintf(&sc_document_dates.output, _("Document date \"%100s\" unparsable"),
784 document_date);
785 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
786 } else if (doc_data > srv_data + 30) {
787
788 xasprintf(&sc_document_dates.output, _("Document is %d seconds in the future"),
789 (int)doc_data - (int)srv_data);
790
791 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
792 } else if (doc_data < srv_data - maximum_age) {
793 time_t last_modified = (srv_data - doc_data);
794 if (last_modified > (60 * 60 * 24 * 2)) { // two days hardcoded?
795 xasprintf(&sc_document_dates.output, _("Last modified %.1f days ago"),
796 ((float)last_modified) / (60 * 60 * 24));
797 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
798 } else {
799 xasprintf(&sc_document_dates.output, _("Last modified %ld:%02ld:%02ld ago"),
800 last_modified / (60 * 60), (last_modified / 60) % 60, last_modified % 60);
801 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_CRITICAL);
802 }
803 } else {
804 // TODO is this the OK case?
805 time_t last_modified = (srv_data - doc_data);
806 xasprintf(&sc_document_dates.output, _("Last modified %ld:%02ld:%02ld ago"),
807 last_modified / (60 * 60), (last_modified / 60) % 60, last_modified % 60);
808 sc_document_dates = mp_set_subcheck_state(sc_document_dates, STATE_OK);
809 }
810 }
811
812 if (server_date) {
813 free(server_date);
814 }
815 if (document_date) {
816 free(document_date);
817 }
818
819 return sc_document_dates;
820}
821
822void curlhelp_free_statusline(curlhelp_statusline *status_line) { free(status_line->first_line); }
823
824int curlhelp_parse_statusline(const char *buf, curlhelp_statusline *status_line) {
825 /* find last start of a new header */
826 const char *start = strrstr2(buf, "\r\nHTTP/");
827 if (start != NULL) {
828 start += 2;
829 buf = start;
830 }
831
832 // Accept either LF or CRLF as end of line for the status line
833 // CRLF is the standard (RFC9112), but it is recommended to accept both
834 size_t length_of_first_line = strcspn(buf, "\r\n");
835 const char *first_line_end = &buf[length_of_first_line];
836 if (first_line_end == NULL) {
837 return -1;
838 }
839
840 size_t first_line_len = (size_t)(first_line_end - buf);
841 status_line->first_line = (char *)calloc(first_line_len + 1, sizeof(char));
842 if (status_line->first_line == NULL) {
843 return -1;
844 }
845
846 memcpy(status_line->first_line, buf, first_line_len);
847 status_line->first_line[first_line_len] = '\0';
848 char *first_line_buf = strdup(status_line->first_line);
849
850 /* protocol and version: "HTTP/x.x" SP or "HTTP/2" SP */
851 char *temp_string = strtok(first_line_buf, "/");
852 if (temp_string == NULL) {
853 if (verbose > 1) {
854 printf("%s: no / found\n", __func__);
855 }
856 free(first_line_buf);
857 return -1;
858 }
859
860 if (strcmp(temp_string, "HTTP") != 0) {
861 if (verbose > 1) {
862 printf("%s: string 'HTTP' not found\n", __func__);
863 }
864 free(first_line_buf);
865 return -1;
866 }
867
868 // try to find a space in the remaining string?
869 // the space after HTTP/1.1 probably
870 temp_string = strtok(NULL, " ");
871 if (temp_string == NULL) {
872 if (verbose > 1) {
873 printf("%s: no space after protocol definition\n", __func__);
874 }
875 free(first_line_buf);
876 return -1;
877 }
878
879 char *temp_string_2;
880 if (strchr(temp_string, '.') != NULL) {
881 /* HTTP 1.x case */
882 strtok(temp_string, ".");
883 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
884 if (*temp_string_2 != '\0') {
885 free(first_line_buf);
886 return -1;
887 }
888 strtok(NULL, " ");
889 status_line->http_minor = (int)strtol(temp_string, &temp_string_2, 10);
890 if (*temp_string_2 != '\0') {
891 free(first_line_buf);
892 return -1;
893 }
894 temp_string += 4; /* 1.x SP */
895 } else {
896 /* HTTP 2 case */
897 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
898 status_line->http_minor = 0;
899 temp_string += 2; /* 2 SP */
900 }
901
902 /* status code: "404" or "404.1", then SP */
903 temp_string = strtok(temp_string, " ");
904 if (temp_string == NULL) {
905 free(first_line_buf);
906 return -1;
907 }
908 if (strchr(temp_string, '.') != NULL) {
909 char *ppp;
910 ppp = strtok(temp_string, ".");
911 status_line->http_code = (int)strtol(ppp, &temp_string_2, 10);
912 if (*temp_string_2 != '\0') {
913 free(first_line_buf);
914 return -1;
915 }
916 ppp = strtok(NULL, "");
917 status_line->http_subcode = (int)strtol(ppp, &temp_string_2, 10);
918 if (*temp_string_2 != '\0') {
919 free(first_line_buf);
920 return -1;
921 }
922 temp_string += 6; /* 400.1 SP */
923 } else {
924 status_line->http_code = (int)strtol(temp_string, &temp_string_2, 10);
925 status_line->http_subcode = -1;
926 if (*temp_string_2 != '\0') {
927 free(first_line_buf);
928 return -1;
929 }
930 temp_string += 4; /* 400 SP */
931 }
932
933 /* Human readable message: "Not Found" CRLF */
934
935 temp_string = strtok(temp_string, "");
936 if (temp_string == NULL) {
937 status_line->msg = "";
938 return 0;
939 }
940 status_line->msg = status_line->first_line + (temp_string - first_line_buf);
941 free(first_line_buf);
942
943 return 0;
944}
945
946/* TODO: where to put this, it's actually part of sstrings2 (logically)?
947 */
948const char *strrstr2(const char *haystack, const char *needle) {
949 if (haystack == NULL || needle == NULL) {
950 return NULL;
951 }
952
953 if (haystack[0] == '\0' || needle[0] == '\0') {
954 return NULL;
955 }
956
957 int counter = 0;
958 const char *prev_pos = NULL;
959 const char *pos = haystack;
960 size_t len = strlen(needle);
961 for (;;) {
962 pos = strstr(pos, needle);
963 if (pos == NULL) {
964 if (counter == 0) {
965 return NULL;
966 }
967 return prev_pos;
968 }
969 counter++;
970 prev_pos = pos;
971 pos += len;
972 if (*pos == '\0') {
973 return prev_pos;
974 }
975 }
976}
977
978void curlhelp_freereadbuffer(curlhelp_read_curlbuf *buf) {
979 free(buf->buf);
980 buf->buf = NULL;
981}
982
983void curlhelp_freewritebuffer(curlhelp_write_curlbuf *buf) {
984 free(buf->buf);
985 buf->buf = NULL;
986}
987
988int curlhelp_initreadbuffer(curlhelp_read_curlbuf **buf, const char *data, size_t datalen) {
989 if ((*buf = calloc(1, sizeof(curlhelp_read_curlbuf))) == NULL) {
990 return 1;
991 }
992
993 (*buf)->buflen = datalen;
994 (*buf)->buf = (char *)calloc((*buf)->buflen, sizeof(char));
995 if ((*buf)->buf == NULL) {
996 return -1;
997 }
998 memcpy((*buf)->buf, data, datalen);
999 (*buf)->pos = 0;
1000 return 0;
1001}
1002
1003size_t curlhelp_buffer_read_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
1004 curlhelp_read_curlbuf *buf = (curlhelp_read_curlbuf *)stream;
1005
1006 size_t minimalSize = min(nmemb * size, buf->buflen - buf->pos);
1007
1008 memcpy(buffer, buf->buf + buf->pos, minimalSize);
1009 buf->pos += minimalSize;
1010
1011 return minimalSize;
1012}
1013
1014int curlhelp_initwritebuffer(curlhelp_write_curlbuf **buf) {
1015 if ((*buf = calloc(1, sizeof(curlhelp_write_curlbuf))) == NULL) {
1016 return 1;
1017 }
1018 (*buf)->bufsize = DEFAULT_BUFFER_SIZE * sizeof(char);
1019 (*buf)->buflen = 0;
1020 (*buf)->buf = (char *)calloc((*buf)->bufsize, sizeof(char));
1021 if ((*buf)->buf == NULL) {
1022 return -1;
1023 }
1024 return 0;
1025}
1026
1027size_t curlhelp_buffer_write_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
1028 curlhelp_write_curlbuf *buf = (curlhelp_write_curlbuf *)stream;
1029
1030 while (buf->bufsize < buf->buflen + size * nmemb + 1) {
1031 buf->bufsize = buf->bufsize * 2;
1032 buf->buf = (char *)realloc(buf->buf, buf->bufsize);
1033 if (buf->buf == NULL) {
1034 fprintf(stderr, "malloc failed (%d) %s\n", errno, strerror(errno));
1035 return 0;
1036 }
1037 }
1038
1039 memcpy(buf->buf + buf->buflen, buffer, size * nmemb);
1040 buf->buflen += size * nmemb;
1041 buf->buf[buf->buflen] = '\0';
1042
1043 return size * nmemb;
1044}
1045
1046void cleanup(check_curl_global_state global_state) {
1047 if (global_state.status_line_initialized) {
1048 curlhelp_free_statusline(global_state.status_line);
1049 }
1050 global_state.status_line_initialized = false;
1051
1052 if (global_state.curl_easy_initialized) {
1053 curl_easy_cleanup(global_state.curl);
1054 }
1055 global_state.curl_easy_initialized = false;
1056
1057 if (global_state.curl_global_initialized) {
1058 curl_global_cleanup();
1059 }
1060 global_state.curl_global_initialized = false;
1061
1062 if (global_state.body_buf_initialized) {
1063 curlhelp_freewritebuffer(global_state.body_buf);
1064 }
1065 global_state.body_buf_initialized = false;
1066
1067 if (global_state.header_buf_initialized) {
1068 curlhelp_freewritebuffer(global_state.header_buf);
1069 }
1070 global_state.header_buf_initialized = false;
1071
1072 if (global_state.put_buf_initialized) {
1073 curlhelp_freereadbuffer(global_state.put_buf);
1074 }
1075 global_state.put_buf_initialized = false;
1076
1077 if (global_state.header_list) {
1078 curl_slist_free_all(global_state.header_list);
1079 }
1080
1081 if (global_state.host) {
1082 curl_slist_free_all(global_state.host);
1083 }
1084}
1085
1086int lookup_host(const char *host, char *buf, size_t buflen, sa_family_t addr_family) {
1087 struct addrinfo hints = {
1088 .ai_family = addr_family,
1089 .ai_socktype = SOCK_STREAM,
1090 .ai_flags = AI_CANONNAME,
1091 };
1092
1093 struct addrinfo *result;
1094 int errcode = getaddrinfo(host, NULL, &hints, &result);
1095 if (errcode != 0) {
1096 return errcode;
1097 }
1098
1099 strcpy(buf, "");
1100 struct addrinfo *res = result;
1101
1102 size_t buflen_remaining = buflen - 1;
1103 size_t addrstr_len;
1104 char addrstr[100];
1105 void *ptr = {0};
1106 while (res) {
1107 switch (res->ai_family) {
1108 case AF_INET:
1109 ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
1110 break;
1111 case AF_INET6:
1112 ptr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
1113 break;
1114 }
1115
1116 inet_ntop(res->ai_family, ptr, addrstr, 100);
1117 if (verbose >= 1) {
1118 printf("* getaddrinfo IPv%d address: %s\n", res->ai_family == PF_INET6 ? 6 : 4,
1119 addrstr);
1120 }
1121
1122 // Append all IPs to buf as a comma-separated string
1123 addrstr_len = strlen(addrstr);
1124 if (buflen_remaining > addrstr_len + 1) {
1125 if (buf[0] != '\0') {
1126 strncat(buf, ",", buflen_remaining);
1127 buflen_remaining -= 1;
1128 }
1129 strncat(buf, addrstr, buflen_remaining);
1130 buflen_remaining -= addrstr_len;
1131 }
1132
1133 res = res->ai_next;
1134 }
1135
1136 freeaddrinfo(result);
1137
1138 return 0;
1139}
1140
1141/* Checks if the server 'reply' is one of the expected 'statuscodes' */
1142bool expected_statuscode(const char *reply, const char *statuscodes) {
1143 char *expected;
1144
1145 if ((expected = strdup(statuscodes)) == NULL) {
1146 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
1147 }
1148
1149 bool result = false;
1150 for (char *code = strtok(expected, ","); code != NULL; code = strtok(NULL, ",")) {
1151 if (strstr(reply, code) != NULL) {
1152 result = true;
1153 break;
1154 }
1155 }
1156
1157 free(expected);
1158 return result;
1159}
1160
1161/* returns a string "HTTP/1.x" or "HTTP/2" */
1162char *string_statuscode(int major, int minor) {
1163 static char buf[10];
1164
1165 switch (major) {
1166 case 1:
1167 snprintf(buf, sizeof(buf), "HTTP/%d.%d", major, minor);
1168 break;
1169 case 2:
1170 case 3:
1171 snprintf(buf, sizeof(buf), "HTTP/%d", major);
1172 break;
1173 default:
1174 /* assuming here HTTP/N with N>=4 */
1175 snprintf(buf, sizeof(buf), "HTTP/%d", major);
1176 break;
1177 }
1178
1179 return buf;
1180}
1181
1182/* check whether a file exists */
1183void test_file(char *path) {
1184 if (access(path, R_OK) == 0) {
1185 return;
1186 }
1187 usage2(_("file does not exist or is not readable"), path);
1188}
1189
1190mp_subcheck mp_net_ssl_check_certificate(X509 *certificate, int days_till_exp_warn,
1191 int days_till_exp_crit);
1192
1193mp_subcheck check_curl_certificate_checks(CURL *curl, X509 *cert, int warn_days_till_exp,
1194 int crit_days_till_exp) {
1195 mp_subcheck sc_cert_result = mp_subcheck_init();
1196 sc_cert_result = mp_set_subcheck_default_state(sc_cert_result, STATE_OK);
1197
1198#ifdef LIBCURL_FEATURE_SSL
1199 if (is_openssl_callback) {
1200# ifdef USE_OPENSSL
1201 /* check certificate with OpenSSL functions, curl has been built against OpenSSL
1202 * and we actually have OpenSSL in the monitoring tools
1203 */
1204 return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp);
1205# else /* USE_OPENSSL */
1206 xasprintf(&result.output, "HTTP CRITICAL - Cannot retrieve certificates - OpenSSL "
1207 "callback used and not linked against OpenSSL\n");
1208 mp_set_subcheck_state(result, STATE_CRITICAL);
1209# endif /* USE_OPENSSL */
1210 } else {
1211 struct curl_slist *slist;
1212
1213 cert_ptr_union cert_ptr = {0};
1214 cert_ptr.to_info = NULL;
1215 CURLcode res = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &cert_ptr.to_info);
1216 if (!res && cert_ptr.to_info) {
1217# ifdef USE_OPENSSL
1218 /* We have no OpenSSL in libcurl, but we can use OpenSSL for X509 cert
1219 * parsing We only check the first certificate and assume it's the one of
1220 * the server
1221 */
1222 char *raw_cert = NULL;
1223 bool got_first_cert = false;
1224 for (int i = 0; i < cert_ptr.to_certinfo->num_of_certs; i++) {
1225 if (got_first_cert) {
1226 break;
1227 }
1228
1229 for (slist = cert_ptr.to_certinfo->certinfo[i]; slist; slist = slist->next) {
1230 if (verbose >= 2) {
1231 printf("%d ** %s\n", i, slist->data);
1232 }
1233 if (strncmp(slist->data, "Cert:", 5) == 0) {
1234 raw_cert = &slist->data[5];
1235 got_first_cert = true;
1236 break;
1237 }
1238 }
1239 }
1240
1241 if (!raw_cert) {
1242
1243 xasprintf(&sc_cert_result.output,
1244 _("Cannot retrieve certificates from CERTINFO information - "
1245 "certificate data was empty"));
1246 sc_cert_result = mp_set_subcheck_state(sc_cert_result, STATE_CRITICAL);
1247 return sc_cert_result;
1248 }
1249
1250 BIO *cert_BIO = BIO_new(BIO_s_mem());
1251 BIO_write(cert_BIO, raw_cert, (int)strlen(raw_cert));
1252
1253 cert = PEM_read_bio_X509(cert_BIO, NULL, NULL, NULL);
1254 if (!cert) {
1255 xasprintf(&sc_cert_result.output,
1256 _("Cannot read certificate from CERTINFO information - BIO error"));
1257 sc_cert_result = mp_set_subcheck_state(sc_cert_result, STATE_CRITICAL);
1258 return sc_cert_result;
1259 }
1260
1261 BIO_free(cert_BIO);
1262 return mp_net_ssl_check_certificate(cert, warn_days_till_exp, crit_days_till_exp);
1263# else /* USE_OPENSSL */
1264 /* We assume we don't have OpenSSL and np_net_ssl_check_certificate at our
1265 * disposal, so we use the libcurl CURLINFO data
1266 */
1267 return net_noopenssl_check_certificate(&cert_ptr, days_till_exp_warn,
1268 days_till_exp_crit);
1269# endif /* USE_OPENSSL */
1270 } else {
1271 xasprintf(&sc_cert_result.output,
1272 _("Cannot retrieve certificates - cURL returned %d - %s"), res,
1273 curl_easy_strerror(res));
1274 mp_set_subcheck_state(sc_cert_result, STATE_CRITICAL);
1275 }
1276 }
1277#endif /* LIBCURL_FEATURE_SSL */
1278
1279 return sc_cert_result;
1280}
1281
1282char *fmt_url(check_curl_working_state workingState) {
1283 char *url = calloc(DEFAULT_BUFFER_SIZE, sizeof(char));
1284 if (url == NULL) {
1285 die(STATE_UNKNOWN, "memory allocation failed");
1286 }
1287
1288 snprintf(url, DEFAULT_BUFFER_SIZE, "%s://%s:%d%s", workingState.use_ssl ? "https" : "http",
1289 (workingState.use_ssl & (workingState.host_name != NULL))
1290 ? workingState.host_name
1291 : workingState.server_address,
1292 workingState.serverPort, workingState.server_url);
1293
1294 return url;
1295}