summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-09-12 01:14:14 +0200
committerLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-09-12 01:14:14 +0200
commitab66b41d235c3ec38eeb8dfd2091e3b76fc1ffa3 (patch)
tree94d048bc490950d3c6101e2275e3fdd7d1a42693
parent684602ddec1ae3c136eec47aa078aa3fc69bbbcf (diff)
downloadmonitoring-plugins-ab66b41d235c3ec38eeb8dfd2091e3b76fc1ffa3.tar.gz
check_curl: create outsourced helpers in extra files
-rw-r--r--plugins/Makefile.am2
-rw-r--r--plugins/check_curl.c1312
-rw-r--r--plugins/check_curl.d/check_curl_helpers.c1217
-rw-r--r--plugins/check_curl.d/check_curl_helpers.h125
-rw-r--r--plugins/check_curl.d/config.h76
5 files changed, 1397 insertions, 1335 deletions
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 1f24d923..1a9399f0 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -66,6 +66,7 @@ EXTRA_DIST = t \
66 check_hpjd.d \ 66 check_hpjd.d \
67 check_game.d \ 67 check_game.d \
68 check_radius.d \ 68 check_radius.d \
69 check_curl.d \
69 check_disk.d \ 70 check_disk.d \
70 check_time.d \ 71 check_time.d \
71 check_users.d \ 72 check_users.d \
@@ -134,6 +135,7 @@ check_cluster_LDADD = $(BASEOBJS)
134check_curl_CFLAGS = $(AM_CFLAGS) $(LIBCURLCFLAGS) $(URIPARSERCFLAGS) $(LIBCURLINCLUDE) $(URIPARSERINCLUDE) -Ipicohttpparser 135check_curl_CFLAGS = $(AM_CFLAGS) $(LIBCURLCFLAGS) $(URIPARSERCFLAGS) $(LIBCURLINCLUDE) $(URIPARSERINCLUDE) -Ipicohttpparser
135check_curl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCURLCFLAGS) $(URIPARSERCFLAGS) $(LIBCURLINCLUDE) $(URIPARSERINCLUDE) -Ipicohttpparser 136check_curl_CPPFLAGS = $(AM_CPPFLAGS) $(LIBCURLCFLAGS) $(URIPARSERCFLAGS) $(LIBCURLINCLUDE) $(URIPARSERINCLUDE) -Ipicohttpparser
136check_curl_LDADD = $(NETLIBS) $(LIBCURLLIBS) $(SSLOBJS) $(URIPARSERLIBS) picohttpparser/libpicohttpparser.a 137check_curl_LDADD = $(NETLIBS) $(LIBCURLLIBS) $(SSLOBJS) $(URIPARSERLIBS) picohttpparser/libpicohttpparser.a
138check_curl_SOURCES = check_curl.c check_curl.d/check_curl_helpers.c
137check_dbi_LDADD = $(NETLIBS) $(DBILIBS) 139check_dbi_LDADD = $(NETLIBS) $(DBILIBS)
138check_dig_LDADD = $(NETLIBS) 140check_dig_LDADD = $(NETLIBS)
139check_disk_LDADD = $(BASEOBJS) 141check_disk_LDADD = $(BASEOBJS)
diff --git a/plugins/check_curl.c b/plugins/check_curl.c
index 7f3bcdb1..b747ec99 100644
--- a/plugins/check_curl.c
+++ b/plugins/check_curl.c
@@ -45,6 +45,7 @@ const char *email = "devel@monitoring-plugins.org";
45 45
46#include "common.h" 46#include "common.h"
47#include "utils.h" 47#include "utils.h"
48#include "./check_curl.d/check_curl_helpers.h"
48 49
49#ifndef LIBCURL_PROTOCOL_HTTP 50#ifndef LIBCURL_PROTOCOL_HTTP
50# error libcurl compiled without HTTP support, compiling check_curl plugin does not makes a lot of sense 51# error libcurl compiled without HTTP support, compiling check_curl plugin does not makes a lot of sense
@@ -53,8 +54,6 @@ const char *email = "devel@monitoring-plugins.org";
53#include "curl/curl.h" 54#include "curl/curl.h"
54#include "curl/easy.h" 55#include "curl/easy.h"
55 56
56#include "./picohttpparser/picohttpparser.h"
57
58#include "uriparser/Uri.h" 57#include "uriparser/Uri.h"
59 58
60#include <arpa/inet.h> 59#include <arpa/inet.h>
@@ -66,48 +65,10 @@ const char *email = "devel@monitoring-plugins.org";
66 65
67#include <netdb.h> 66#include <netdb.h>
68 67
69#define MAKE_LIBCURL_VERSION(major, minor, patch) ((major) * 0x10000 + (minor) * 0x100 + (patch))
70
71#define INET_ADDR_MAX_SIZE INET6_ADDRSTRLEN
72enum { 68enum {
73 MAX_IPV4_HOSTLENGTH = 255, 69 MAX_IPV4_HOSTLENGTH = 255,
74}; 70};
75 71
76/* for buffers for header and body */
77typedef struct {
78 char *buf;
79 size_t buflen;
80 size_t bufsize;
81} curlhelp_write_curlbuf;
82
83/* for buffering the data sent in PUT */
84typedef struct {
85 char *buf;
86 size_t buflen;
87 off_t pos;
88} curlhelp_read_curlbuf;
89
90/* for parsing the HTTP status line */
91typedef struct {
92 int http_major; /* major version of the protocol, always 1 (HTTP/0.9
93 * never reached the big internet most likely) */
94 int http_minor; /* minor version of the protocol, usually 0 or 1 */
95 int http_code; /* HTTP return code as in RFC 2145 */
96 int http_subcode; /* Microsoft IIS extension, HTTP subcodes, see
97 * http://support.microsoft.com/kb/318380/en-us */
98 const char *msg; /* the human readable message */
99 char *first_line; /* a copy of the first line */
100} curlhelp_statusline;
101
102/* to know the underlying SSL library used by libcurl */
103typedef enum curlhelp_ssl_library {
104 CURLHELP_SSL_LIBRARY_UNKNOWN,
105 CURLHELP_SSL_LIBRARY_OPENSSL,
106 CURLHELP_SSL_LIBRARY_LIBRESSL,
107 CURLHELP_SSL_LIBRARY_GNUTLS,
108 CURLHELP_SSL_LIBRARY_NSS
109} curlhelp_ssl_library;
110
111enum { 72enum {
112 REGS = 2, 73 REGS = 2,
113}; 74};
@@ -115,33 +76,12 @@ enum {
115#include "regex.h" 76#include "regex.h"
116 77
117// Globals 78// Globals
118static int verbose = 0; 79int verbose = 0;
119 80
120typedef struct { 81extern char msg[DEFAULT_BUFFER_SIZE];
121 bool curl_global_initialized; 82extern char errbuf[MAX_INPUT_BUFFER];
122 bool curl_easy_initialized; 83extern bool is_openssl_callback;
123 bool body_buf_initialized; 84extern bool add_sslctx_verify_fun;
124 curlhelp_write_curlbuf body_buf;
125 bool header_buf_initialized;
126 curlhelp_write_curlbuf header_buf;
127 bool status_line_initialized;
128 curlhelp_statusline status_line;
129 bool put_buf_initialized;
130 curlhelp_read_curlbuf put_buf;
131 CURL *curl;
132
133 struct curl_slist *header_list;
134 struct curl_slist *host;
135} check_curl_global_state;
136
137static char errbuf[MAX_INPUT_BUFFER];
138static char msg[DEFAULT_BUFFER_SIZE];
139typedef union {
140 struct curl_slist *to_info;
141 struct curl_certinfo *to_certinfo;
142} cert_ptr_union;
143static bool is_openssl_callback = false;
144static bool add_sslctx_verify_fun = false;
145 85
146#if defined(HAVE_SSL) && defined(USE_OPENSSL) 86#if defined(HAVE_SSL) && defined(USE_OPENSSL)
147static X509 *cert = NULL; 87static X509 *cert = NULL;
@@ -153,7 +93,6 @@ typedef struct {
153} check_curl_config_wrapper; 93} check_curl_config_wrapper;
154static check_curl_config_wrapper process_arguments(int /*argc*/, char ** /*argv*/); 94static check_curl_config_wrapper process_arguments(int /*argc*/, char ** /*argv*/);
155 95
156static void handle_curl_option_return_code(CURLcode res, const char *option);
157static mp_state_enum check_http(check_curl_config /*config*/, check_curl_working_state workingState, 96static mp_state_enum check_http(check_curl_config /*config*/, check_curl_working_state workingState,
158 int redir_depth); 97 int redir_depth);
159 98
@@ -166,590 +105,22 @@ typedef struct {
166static redir_wrapper redir(curlhelp_write_curlbuf * /*header_buf*/, check_curl_config /*config*/, 105static redir_wrapper redir(curlhelp_write_curlbuf * /*header_buf*/, check_curl_config /*config*/,
167 int redir_depth, check_curl_working_state working_state); 106 int redir_depth, check_curl_working_state working_state);
168 107
169static char *perfd_time(double elapsed_time, thresholds * /*thlds*/, long /*socket_timeout*/);
170static char *perfd_time_connect(double elapsed_time_connect, long /*socket_timeout*/);
171static char *perfd_time_ssl(double elapsed_time_ssl, long /*socket_timeout*/);
172static char *perfd_time_firstbyte(double elapsed_time_firstbyte, long /*socket_timeout*/);
173static char *perfd_time_headers(double elapsed_time_headers, long /*socket_timeout*/);
174static char *perfd_time_transfer(double elapsed_time_transfer, long /*socket_timeout*/);
175static char *perfd_size(size_t page_len, int /*min_page_len*/);
176
177static void print_help(void); 108static void print_help(void);
178void print_usage(void); 109void print_usage(void);
179 110
180static void print_curl_version(void); 111static void print_curl_version(void);
181 112
182static int curlhelp_initwritebuffer(curlhelp_write_curlbuf * /*buf*/);
183static size_t curlhelp_buffer_write_callback(void * /*buffer*/, size_t /*size*/, size_t /*nmemb*/,
184 void * /*stream*/);
185static void curlhelp_freewritebuffer(curlhelp_write_curlbuf * /*buf*/);
186
187static int curlhelp_initreadbuffer(curlhelp_read_curlbuf * /*buf*/, const char * /*data*/,
188 size_t /*datalen*/);
189static size_t curlhelp_buffer_read_callback(void * /*buffer*/, size_t /*size*/, size_t /*nmemb*/,
190 void * /*stream*/);
191static void curlhelp_freereadbuffer(curlhelp_read_curlbuf * /*buf*/);
192
193static curlhelp_ssl_library curlhelp_get_ssl_library(void);
194static const char *curlhelp_get_ssl_library_string(curlhelp_ssl_library /*ssl_library*/);
195
196int net_noopenssl_check_certificate(cert_ptr_union *, int, int);
197
198static int curlhelp_parse_statusline(const char * /*buf*/, curlhelp_statusline * /*status_line*/);
199static void curlhelp_free_statusline(curlhelp_statusline * /*status_line*/);
200
201static char *get_header_value(const struct phr_header *headers, size_t nof_headers,
202 const char *header);
203static mp_state_enum check_document_dates(const curlhelp_write_curlbuf * /*header_buf*/,
204 char (*msg)[DEFAULT_BUFFER_SIZE], int /*maximum_age*/);
205static size_t get_content_length(const curlhelp_write_curlbuf *header_buf,
206 const curlhelp_write_curlbuf *body_buf);
207static int lookup_host(const char *host, char *buf, size_t buflen, sa_family_t addr_family);
208
209// typedef struct { 113// typedef struct {
210// int errorcode; 114// int errorcode;
211// } check_curl_evaluation_wrapper; 115// } check_curl_evaluation_wrapper;
212// check_curl_evaluation_wrapper check_curl_evaluate(check_curl_config config, 116// check_curl_evaluation_wrapper check_curl_evaluate(check_curl_config config,
213// mp_check overall[static 1]) {} 117// mp_check overall[static 1]) {}
214 118
215CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm);
216
217typedef struct {
218 int errorcode;
219 check_curl_global_state curl_state;
220} check_curl_configure_curl_wrapper;
221check_curl_configure_curl_wrapper
222check_curl_configure_curl(const check_curl_static_curl_config config,
223 check_curl_working_state working_state, bool check_cert,
224 bool on_redirect_dependent, int follow_method, int max_depth) {
225 check_curl_configure_curl_wrapper result = {
226 .errorcode = OK,
227 .curl_state =
228 {
229 .curl_global_initialized = false,
230 .curl_easy_initialized = false,
231 .curl = NULL,
232
233 .body_buf_initialized = false,
234 .body_buf = {},
235 .header_buf_initialized = false,
236 .header_buf = {},
237 .status_line_initialized = false,
238 .status_line = {},
239 .put_buf_initialized = false,
240 .put_buf = {},
241
242 .header_list = NULL,
243 .host = NULL,
244 },
245 };
246
247 if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
248 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_global_init failed\n");
249 }
250 result.curl_state.curl_global_initialized = true;
251
252 if ((result.curl_state.curl = curl_easy_init()) == NULL) {
253 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_easy_init failed\n");
254 }
255 result.curl_state.curl_easy_initialized = true;
256
257 if (verbose >= 1) {
258 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_VERBOSE, 1),
259 "CURLOPT_VERBOSE");
260 }
261 /* print everything on stdout like check_http would do */
262 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_STDERR, stdout),
263 "CURLOPT_STDERR");
264
265 if (config.automatic_decompression) {
266#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6)
267 handle_curl_option_return_code(
268 curl_easy_setopt(result.curl_state.curl, CURLOPT_ACCEPT_ENCODING, ""),
269 "CURLOPT_ACCEPT_ENCODING");
270#else
271 handle_curl_option_return_code(
272 curl_easy_setopt(result.curl_state.curl, CURLOPT_ENCODING, ""), "CURLOPT_ENCODING");
273#endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6) */
274 }
275
276 /* initialize buffer for body of the answer */
277 if (curlhelp_initwritebuffer(&result.curl_state.body_buf) < 0) {
278 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for body\n");
279 }
280 result.curl_state.body_buf_initialized = true;
281
282 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEFUNCTION,
283 curlhelp_buffer_write_callback),
284 "CURLOPT_WRITEFUNCTION");
285 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEDATA,
286 (void *)&result.curl_state.body_buf),
287 "CURLOPT_WRITEDATA");
288
289 /* initialize buffer for header of the answer */
290 if (curlhelp_initwritebuffer(&result.curl_state.header_buf) < 0) {
291 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for header\n");
292 }
293 result.curl_state.header_buf_initialized = true;
294
295 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_HEADERFUNCTION,
296 curlhelp_buffer_write_callback),
297 "CURLOPT_HEADERFUNCTION");
298 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEHEADER,
299 (void *)&result.curl_state.header_buf),
300 "CURLOPT_WRITEHEADER");
301
302 /* set the error buffer */
303 handle_curl_option_return_code(
304 curl_easy_setopt(result.curl_state.curl, CURLOPT_ERRORBUFFER, errbuf),
305 "CURLOPT_ERRORBUFFER");
306
307 /* set timeouts */
308 handle_curl_option_return_code(
309 curl_easy_setopt(result.curl_state.curl, CURLOPT_CONNECTTIMEOUT, config.socket_timeout),
310 "CURLOPT_CONNECTTIMEOUT");
311 handle_curl_option_return_code(
312 curl_easy_setopt(result.curl_state.curl, CURLOPT_TIMEOUT, config.socket_timeout),
313 "CURLOPT_TIMEOUT");
314
315 /* enable haproxy protocol */
316 if (config.haproxy_protocol) {
317 handle_curl_option_return_code(
318 curl_easy_setopt(result.curl_state.curl, CURLOPT_HAPROXYPROTOCOL, 1L),
319 "CURLOPT_HAPROXYPROTOCOL");
320 }
321
322 // fill dns resolve cache to make curl connect to the given server_address instead of the
323 // host_name, only required for ssl, because we use the host_name later on to make SNI happy
324 char dnscache[DEFAULT_BUFFER_SIZE];
325 char addrstr[DEFAULT_BUFFER_SIZE / 2];
326 if (working_state.use_ssl && working_state.host_name != NULL) {
327 int res;
328 if ((res = lookup_host(working_state.server_address, addrstr, DEFAULT_BUFFER_SIZE / 2,
329 config.sin_family)) != 0) {
330 snprintf(msg, DEFAULT_BUFFER_SIZE,
331 _("Unable to lookup IP address for '%s': getaddrinfo returned %d - %s"),
332 working_state.server_address, res, gai_strerror(res));
333 die(STATE_CRITICAL, "HTTP CRITICAL - %s\n", msg);
334 }
335 snprintf(dnscache, DEFAULT_BUFFER_SIZE, "%s:%d:%s", working_state.host_name,
336 working_state.serverPort, addrstr);
337 result.curl_state.host = curl_slist_append(NULL, dnscache);
338 curl_easy_setopt(result.curl_state.curl, CURLOPT_RESOLVE, result.curl_state.host);
339 if (verbose >= 1) {
340 printf("* curl CURLOPT_RESOLVE: %s\n", dnscache);
341 }
342 }
343
344 // If server_address is an IPv6 address it must be surround by square brackets
345 struct in6_addr tmp_in_addr;
346 if (inet_pton(AF_INET6, working_state.server_address, &tmp_in_addr) == 1) {
347 char *new_server_address = malloc(strlen(working_state.server_address) + 3);
348 if (new_server_address == NULL) {
349 die(STATE_UNKNOWN, "HTTP UNKNOWN - Unable to allocate memory\n");
350 }
351 snprintf(new_server_address, strlen(working_state.server_address) + 3, "[%s]",
352 working_state.server_address);
353 working_state.server_address = new_server_address;
354 }
355
356 /* compose URL: use the address we want to connect to, set Host: header later */
357 char url[DEFAULT_BUFFER_SIZE];
358 snprintf(url, DEFAULT_BUFFER_SIZE, "%s://%s:%d%s", working_state.use_ssl ? "https" : "http",
359 (working_state.use_ssl & (working_state.host_name != NULL))
360 ? working_state.host_name
361 : working_state.server_address,
362 working_state.serverPort, working_state.server_url);
363
364 if (verbose >= 1) {
365 printf("* curl CURLOPT_URL: %s\n", url);
366 }
367 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, url),
368 "CURLOPT_URL");
369
370 /* extract proxy information for legacy proxy https requests */
371 if (!strcmp(working_state.http_method, "CONNECT") ||
372 strstr(working_state.server_url, "http") == working_state.server_url) {
373 handle_curl_option_return_code(
374 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXY, working_state.server_address),
375 "CURLOPT_PROXY");
376 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYPORT,
377 (long)working_state.serverPort),
378 "CURLOPT_PROXYPORT");
379 if (verbose >= 2) {
380 printf("* curl CURLOPT_PROXY: %s:%d\n", working_state.server_address,
381 working_state.serverPort);
382 }
383 working_state.http_method = "GET";
384 handle_curl_option_return_code(
385 curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, working_state.server_url),
386 "CURLOPT_URL");
387 }
388
389 /* disable body for HEAD request */
390 if (working_state.http_method && !strcmp(working_state.http_method, "HEAD")) {
391 working_state.no_body = true;
392 }
393
394 /* set HTTP protocol version */
395 handle_curl_option_return_code(
396 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTP_VERSION, config.curl_http_version),
397 "CURLOPT_HTTP_VERSION");
398
399 /* set HTTP method */
400 if (working_state.http_method) {
401 if (!strcmp(working_state.http_method, "POST")) {
402 handle_curl_option_return_code(
403 curl_easy_setopt(result.curl_state.curl, CURLOPT_POST, 1), "CURLOPT_POST");
404 } else if (!strcmp(working_state.http_method, "PUT")) {
405 handle_curl_option_return_code(
406 curl_easy_setopt(result.curl_state.curl, CURLOPT_UPLOAD, 1), "CURLOPT_UPLOAD");
407 } else {
408 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
409 CURLOPT_CUSTOMREQUEST,
410 working_state.http_method),
411 "CURLOPT_CUSTOMREQUEST");
412 }
413 }
414
415 char *force_host_header = NULL;
416 /* check if Host header is explicitly set in options */
417 if (config.http_opt_headers_count) {
418 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
419 if (strncmp(config.http_opt_headers[i], "Host:", 5) == 0) {
420 force_host_header = config.http_opt_headers[i];
421 }
422 }
423 }
424
425 /* set hostname (virtual hosts), not needed if CURLOPT_CONNECT_TO is used, but left in
426 * anyway */
427 char http_header[DEFAULT_BUFFER_SIZE];
428 if (working_state.host_name != NULL && force_host_header == NULL) {
429 if ((working_state.virtualPort != HTTP_PORT && !working_state.use_ssl) ||
430 (working_state.virtualPort != HTTPS_PORT && working_state.use_ssl)) {
431 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s:%d", working_state.host_name,
432 working_state.virtualPort);
433 } else {
434 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s", working_state.host_name);
435 }
436 result.curl_state.header_list =
437 curl_slist_append(result.curl_state.header_list, http_header);
438 }
439
440 /* always close connection, be nice to servers */
441 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Connection: close");
442 result.curl_state.header_list = curl_slist_append(result.curl_state.header_list, http_header);
443
444 /* attach additional headers supplied by the user */
445 /* optionally send any other header tag */
446 if (config.http_opt_headers_count) {
447 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
448 result.curl_state.header_list =
449 curl_slist_append(result.curl_state.header_list, config.http_opt_headers[i]);
450 }
451 }
452
453 /* set HTTP headers */
454 handle_curl_option_return_code(
455 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTPHEADER, result.curl_state.header_list),
456 "CURLOPT_HTTPHEADER");
457
458#ifdef LIBCURL_FEATURE_SSL
459 /* set SSL version, warn about insecure or unsupported versions */
460 if (working_state.use_ssl) {
461 handle_curl_option_return_code(
462 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLVERSION, config.ssl_version),
463 "CURLOPT_SSLVERSION");
464 }
465
466 /* client certificate and key to present to server (SSL) */
467 if (config.client_cert) {
468 handle_curl_option_return_code(
469 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLCERT, config.client_cert),
470 "CURLOPT_SSLCERT");
471 }
472
473 if (config.client_privkey) {
474 handle_curl_option_return_code(
475 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLKEY, config.client_privkey),
476 "CURLOPT_SSLKEY");
477 }
478
479 if (config.ca_cert) {
480 handle_curl_option_return_code(
481 curl_easy_setopt(result.curl_state.curl, CURLOPT_CAINFO, config.ca_cert),
482 "CURLOPT_CAINFO");
483 }
484
485 if (config.ca_cert || config.verify_peer_and_host) {
486 /* per default if we have a CA verify both the peer and the
487 * hostname in the certificate, can be switched off later */
488 handle_curl_option_return_code(
489 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 1),
490 "CURLOPT_SSL_VERIFYPEER");
491 handle_curl_option_return_code(
492 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 2),
493 "CURLOPT_SSL_VERIFYHOST");
494 } else {
495 /* backward-compatible behaviour, be tolerant in checks
496 * TODO: depending on more options have aspects we want
497 * to be less tolerant about ssl verfications
498 */
499 handle_curl_option_return_code(
500 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 0),
501 "CURLOPT_SSL_VERIFYPEER");
502 handle_curl_option_return_code(
503 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 0),
504 "CURLOPT_SSL_VERIFYHOST");
505 }
506
507 /* detect SSL library used by libcurl */
508 curlhelp_ssl_library ssl_library = curlhelp_get_ssl_library();
509
510 /* try hard to get a stack of certificates to verify against */
511 if (check_cert) {
512# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1)
513 /* inform curl to report back certificates */
514 switch (ssl_library) {
515 case CURLHELP_SSL_LIBRARY_OPENSSL:
516 case CURLHELP_SSL_LIBRARY_LIBRESSL:
517 /* set callback to extract certificate with OpenSSL context function (works with
518 * OpenSSL-style libraries only!) */
519# ifdef USE_OPENSSL
520 /* libcurl and monitoring plugins built with OpenSSL, good */
521 add_sslctx_verify_fun = true;
522 is_openssl_callback = true;
523# endif /* USE_OPENSSL */
524 /* libcurl is built with OpenSSL, monitoring plugins, so falling
525 * back to manually extracting certificate information */
526 handle_curl_option_return_code(
527 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
528 break;
529
530 case CURLHELP_SSL_LIBRARY_NSS:
531# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0)
532 /* NSS: support for CERTINFO is implemented since 7.34.0 */
533 handle_curl_option_return_code(
534 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
535# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
536 die(STATE_CRITICAL,
537 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
538 "'%s' is too old)\n",
539 curlhelp_get_ssl_library_string(ssl_library));
540# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
541 break;
542
543 case CURLHELP_SSL_LIBRARY_GNUTLS:
544# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0)
545 /* GnuTLS: support for CERTINFO is implemented since 7.42.0 */
546 handle_curl_option_return_code(
547 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
548# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
549 die(STATE_CRITICAL,
550 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
551 "'%s' is too old)\n",
552 curlhelp_get_ssl_library_string(ssl_library));
553# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
554 break;
555
556 case CURLHELP_SSL_LIBRARY_UNKNOWN:
557 default:
558 die(STATE_CRITICAL,
559 "HTTP CRITICAL - Cannot retrieve certificates (unknown SSL library '%s', must "
560 "implement first)\n",
561 curlhelp_get_ssl_library_string(ssl_library));
562 break;
563 }
564# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
565 /* old libcurl, our only hope is OpenSSL, otherwise we are out of luck */
566 if (ssl_library == CURLHELP_SSL_LIBRARY_OPENSSL ||
567 ssl_library == CURLHELP_SSL_LIBRARY_LIBRESSL) {
568 add_sslctx_verify_fun = true;
569 } else {
570 die(STATE_CRITICAL, "HTTP CRITICAL - Cannot retrieve certificates (no "
571 "CURLOPT_SSL_CTX_FUNCTION, no OpenSSL library or libcurl "
572 "too old and has no CURLOPT_CERTINFO)\n");
573 }
574# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
575 }
576
577# if LIBCURL_VERSION_NUM >= \
578 MAKE_LIBCURL_VERSION(7, 10, 6) /* required for CURLOPT_SSL_CTX_FUNCTION */
579 // ssl ctx function is not available with all ssl backends
580 if (curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, NULL) !=
581 CURLE_UNKNOWN_OPTION) {
582 handle_curl_option_return_code(
583 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun),
584 "CURLOPT_SSL_CTX_FUNCTION");
585 }
586# endif
587#endif /* LIBCURL_FEATURE_SSL */
588
589 /* set default or user-given user agent identification */
590 handle_curl_option_return_code(
591 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERAGENT, config.user_agent),
592 "CURLOPT_USERAGENT");
593
594 /* proxy-authentication */
595 if (strcmp(config.proxy_auth, "")) {
596 handle_curl_option_return_code(
597 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYUSERPWD, config.proxy_auth),
598 "CURLOPT_PROXYUSERPWD");
599 }
600
601 /* authentication */
602 if (strcmp(config.user_auth, "")) {
603 handle_curl_option_return_code(
604 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERPWD, config.user_auth),
605 "CURLOPT_USERPWD");
606 }
607 /* TODO: parameter auth method, bitfield of following methods:
608 * CURLAUTH_BASIC (default)
609 * CURLAUTH_DIGEST
610 * CURLAUTH_DIGEST_IE
611 * CURLAUTH_NEGOTIATE
612 * CURLAUTH_NTLM
613 * CURLAUTH_NTLM_WB
614 *
615 * convenience tokens for typical sets of methods:
616 * CURLAUTH_ANYSAFE: most secure, without BASIC
617 * or CURLAUTH_ANY: most secure, even BASIC if necessary
618 *
619 * handle_curl_option_return_code (curl_easy_setopt( curl, CURLOPT_HTTPAUTH,
620 * (long)CURLAUTH_DIGEST ), "CURLOPT_HTTPAUTH");
621 */
622
623 /* handle redirections */
624 if (on_redirect_dependent) {
625 if (follow_method == FOLLOW_LIBCURL) {
626 handle_curl_option_return_code(
627 curl_easy_setopt(result.curl_state.curl, CURLOPT_FOLLOWLOCATION, 1),
628 "CURLOPT_FOLLOWLOCATION");
629
630 /* default -1 is infinite, not good, could lead to zombie plugins!
631 Setting it to one bigger than maximal limit to handle errors nicely below
632 */
633 handle_curl_option_return_code(
634 curl_easy_setopt(result.curl_state.curl, CURLOPT_MAXREDIRS, max_depth + 1),
635 "CURLOPT_MAXREDIRS");
636
637 /* for now allow only http and https (we are a http(s) check plugin in the end) */
638#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 85, 0)
639 handle_curl_option_return_code(
640 curl_easy_setopt(result.curl_state.curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https"),
641 "CURLOPT_REDIR_PROTOCOLS_STR");
642#elif LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 4)
643 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
644 CURLOPT_REDIR_PROTOCOLS,
645 CURLPROTO_HTTP | CURLPROTO_HTTPS),
646 "CURLOPT_REDIRECT_PROTOCOLS");
647#endif
648
649 /* TODO: handle the following aspects of redirection, make them
650 * command line options too later:
651 CURLOPT_POSTREDIR: method switch
652 CURLINFO_REDIRECT_URL: custom redirect option
653 CURLOPT_REDIRECT_PROTOCOLS: allow people to step outside safe protocols
654 CURLINFO_REDIRECT_COUNT: get the number of redirects, print it, maybe a range
655 option here is nice like for expected page size?
656 */
657 } else {
658 /* old style redirection*/
659 }
660 }
661 /* no-body */
662 if (working_state.no_body) {
663 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_NOBODY, 1),
664 "CURLOPT_NOBODY");
665 }
666
667 /* IPv4 or IPv6 forced DNS resolution */
668 if (config.sin_family == AF_UNSPEC) {
669 handle_curl_option_return_code(
670 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER),
671 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_WHATEVER)");
672 } else if (config.sin_family == AF_INET) {
673 handle_curl_option_return_code(
674 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4),
675 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V4)");
676 }
677#if defined(USE_IPV6) && defined(LIBCURL_FEATURE_IPV6)
678 else if (config.sin_family == AF_INET6) {
679 handle_curl_option_return_code(
680 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6),
681 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V6)");
682 }
683#endif
684
685 /* either send http POST data (any data, not only POST)*/
686 if (!strcmp(working_state.http_method, "POST") || !strcmp(working_state.http_method, "PUT")) {
687 /* set content of payload for POST and PUT */
688 if (config.http_content_type) {
689 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Content-Type: %s",
690 config.http_content_type);
691 result.curl_state.header_list =
692 curl_slist_append(result.curl_state.header_list, http_header);
693 }
694 /* NULL indicates "HTTP Continue" in libcurl, provide an empty string
695 * in case of no POST/PUT data */
696 if (!working_state.http_post_data) {
697 working_state.http_post_data = "";
698 }
699
700 if (!strcmp(working_state.http_method, "POST")) {
701 /* POST method, set payload with CURLOPT_POSTFIELDS */
702 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
703 CURLOPT_POSTFIELDS,
704 working_state.http_post_data),
705 "CURLOPT_POSTFIELDS");
706 } else if (!strcmp(working_state.http_method, "PUT")) {
707 handle_curl_option_return_code(
708 curl_easy_setopt(result.curl_state.curl, CURLOPT_READFUNCTION,
709 (curl_read_callback)curlhelp_buffer_read_callback),
710 "CURLOPT_READFUNCTION");
711 if (curlhelp_initreadbuffer(&result.curl_state.put_buf, working_state.http_post_data,
712 strlen(working_state.http_post_data)) < 0) {
713 die(STATE_UNKNOWN,
714 "HTTP CRITICAL - out of memory allocating read buffer for PUT\n");
715 }
716 result.curl_state.put_buf_initialized = true;
717 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
718 CURLOPT_READDATA,
719 (void *)&result.curl_state.put_buf),
720 "CURLOPT_READDATA");
721 handle_curl_option_return_code(
722 curl_easy_setopt(result.curl_state.curl, CURLOPT_INFILESIZE,
723 (curl_off_t)strlen(working_state.http_post_data)),
724 "CURLOPT_INFILESIZE");
725 }
726 }
727
728 /* cookie handling */
729 if (config.cookie_jar_file != NULL) {
730 /* enable reading cookies from a file, and if the filename is an empty string, only
731 * enable the curl cookie engine */
732 handle_curl_option_return_code(
733 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEFILE, config.cookie_jar_file),
734 "CURLOPT_COOKIEFILE");
735 /* now enable saving cookies to a file, but only if the filename is not an empty string,
736 * since writing it would fail */
737 if (*config.cookie_jar_file) {
738 handle_curl_option_return_code(
739 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEJAR, config.cookie_jar_file),
740 "CURLOPT_COOKIEJAR");
741 }
742 }
743 return result;
744}
745
746#if defined(HAVE_SSL) && defined(USE_OPENSSL) 119#if defined(HAVE_SSL) && defined(USE_OPENSSL)
747mp_state_enum np_net_ssl_check_certificate(X509 *certificate, int days_till_exp_warn, 120mp_state_enum np_net_ssl_check_certificate(X509 *certificate, int days_till_exp_warn,
748 int days_till_exp_crit); 121 int days_till_exp_crit);
749#endif /* defined(HAVE_SSL) && defined(USE_OPENSSL) */ 122#endif /* defined(HAVE_SSL) && defined(USE_OPENSSL) */
750 123
751static void test_file(char * /*path*/);
752
753int main(int argc, char **argv) { 124int main(int argc, char **argv) {
754 setlocale(LC_ALL, ""); 125 setlocale(LC_ALL, "");
755 bindtextdomain(PACKAGE, LOCALEDIR); 126 bindtextdomain(PACKAGE, LOCALEDIR);
@@ -805,7 +176,11 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) {
805 } 176 }
806 return 1; 177 return 1;
807} 178}
179# endif /* USE_OPENSSL */
180#endif /* HAVE_SSL */
808 181
182#ifdef HAVE_SSL
183# ifdef USE_OPENSSL
809CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm) { 184CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm) {
810 (void)curl; // ignore unused parameter 185 (void)curl; // ignore unused parameter
811 (void)parm; // ignore unused parameter 186 (void)parm; // ignore unused parameter
@@ -825,151 +200,6 @@ CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm) {
825# endif /* USE_OPENSSL */ 200# endif /* USE_OPENSSL */
826#endif /* HAVE_SSL */ 201#endif /* HAVE_SSL */
827 202
828/* returns a string "HTTP/1.x" or "HTTP/2" */
829static char *string_statuscode(int major, int minor) {
830 static char buf[10];
831
832 switch (major) {
833 case 1:
834 snprintf(buf, sizeof(buf), "HTTP/%d.%d", major, minor);
835 break;
836 case 2:
837 case 3:
838 snprintf(buf, sizeof(buf), "HTTP/%d", major);
839 break;
840 default:
841 /* assuming here HTTP/N with N>=4 */
842 snprintf(buf, sizeof(buf), "HTTP/%d", major);
843 break;
844 }
845
846 return buf;
847}
848
849/* Checks if the server 'reply' is one of the expected 'statuscodes' */
850static bool expected_statuscode(const char *reply, const char *statuscodes) {
851 char *expected;
852
853 if ((expected = strdup(statuscodes)) == NULL) {
854 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
855 }
856
857 bool result = false;
858 for (char *code = strtok(expected, ","); code != NULL; code = strtok(NULL, ",")) {
859 if (strstr(reply, code) != NULL) {
860 result = true;
861 break;
862 }
863 }
864
865 free(expected);
866 return result;
867}
868
869void handle_curl_option_return_code(CURLcode res, const char *option) {
870 if (res != CURLE_OK) {
871 snprintf(msg, DEFAULT_BUFFER_SIZE,
872 _("Error while setting cURL option '%s': cURL returned %d - %s"), option, res,
873 curl_easy_strerror(res));
874 die(STATE_CRITICAL, "HTTP CRITICAL - %s\n", msg);
875 }
876}
877
878int lookup_host(const char *host, char *buf, size_t buflen, sa_family_t addr_family) {
879 struct addrinfo hints = {
880 .ai_family = addr_family,
881 .ai_socktype = SOCK_STREAM,
882 .ai_flags = AI_CANONNAME,
883 };
884
885 struct addrinfo *result;
886 int errcode = getaddrinfo(host, NULL, &hints, &result);
887 if (errcode != 0) {
888 return errcode;
889 }
890
891 strcpy(buf, "");
892 struct addrinfo *res = result;
893
894 size_t buflen_remaining = buflen - 1;
895 size_t addrstr_len;
896 char addrstr[100];
897 void *ptr = {0};
898 while (res) {
899 switch (res->ai_family) {
900 case AF_INET:
901 ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
902 break;
903 case AF_INET6:
904 ptr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
905 break;
906 }
907
908 inet_ntop(res->ai_family, ptr, addrstr, 100);
909 if (verbose >= 1) {
910 printf("* getaddrinfo IPv%d address: %s\n", res->ai_family == PF_INET6 ? 6 : 4,
911 addrstr);
912 }
913
914 // Append all IPs to buf as a comma-separated string
915 addrstr_len = strlen(addrstr);
916 if (buflen_remaining > addrstr_len + 1) {
917 if (buf[0] != '\0') {
918 strncat(buf, ",", buflen_remaining);
919 buflen_remaining -= 1;
920 }
921 strncat(buf, addrstr, buflen_remaining);
922 buflen_remaining -= addrstr_len;
923 }
924
925 res = res->ai_next;
926 }
927
928 freeaddrinfo(result);
929
930 return 0;
931}
932
933static void cleanup(check_curl_global_state global_state) {
934 if (global_state.status_line_initialized) {
935 curlhelp_free_statusline(&global_state.status_line);
936 }
937 global_state.status_line_initialized = false;
938
939 if (global_state.curl_easy_initialized) {
940 curl_easy_cleanup(global_state.curl);
941 }
942 global_state.curl_easy_initialized = false;
943
944 if (global_state.curl_global_initialized) {
945 curl_global_cleanup();
946 }
947 global_state.curl_global_initialized = false;
948
949 if (global_state.body_buf_initialized) {
950 curlhelp_freewritebuffer(&global_state.body_buf);
951 }
952 global_state.body_buf_initialized = false;
953
954 if (global_state.header_buf_initialized) {
955 curlhelp_freewritebuffer(&global_state.header_buf);
956 }
957 global_state.header_buf_initialized = false;
958
959 if (global_state.put_buf_initialized) {
960 curlhelp_freereadbuffer(&global_state.put_buf);
961 }
962 global_state.put_buf_initialized = false;
963
964 if (global_state.header_list) {
965 curl_slist_free_all(global_state.header_list);
966 }
967
968 if (global_state.host) {
969 curl_slist_free_all(global_state.host);
970 }
971}
972
973mp_state_enum check_http(const check_curl_config config, check_curl_working_state workingState, 203mp_state_enum check_http(const check_curl_config config, check_curl_working_state workingState,
974 int redir_depth) { 204 int redir_depth) {
975 205
@@ -981,6 +211,7 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
981 config.followmethod, config.max_depth); 211 config.followmethod, config.max_depth);
982 212
983 check_curl_global_state curl_state = conf_curl_struct.curl_state; 213 check_curl_global_state curl_state = conf_curl_struct.curl_state;
214 workingState = conf_curl_struct.working_state;
984 215
985 // ============== 216 // ==============
986 // do the request 217 // do the request
@@ -1103,7 +334,7 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1103 handle_curl_option_return_code( 334 handle_curl_option_return_code(
1104 curl_easy_getinfo(curl_state.curl, CURLINFO_TOTAL_TIME, &total_time), 335 curl_easy_getinfo(curl_state.curl, CURLINFO_TOTAL_TIME, &total_time),
1105 "CURLINFO_TOTAL_TIME"); 336 "CURLINFO_TOTAL_TIME");
1106 size_t page_len = get_content_length(&curl_state.header_buf, &curl_state.body_buf); 337 size_t page_len = get_content_length(curl_state.header_buf, curl_state.body_buf);
1107 char perfstring[DEFAULT_BUFFER_SIZE]; 338 char perfstring[DEFAULT_BUFFER_SIZE];
1108 if (config.show_extended_perfdata) { 339 if (config.show_extended_perfdata) {
1109 double time_connect; 340 double time_connect;
@@ -1144,12 +375,12 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1144 } 375 }
1145 376
1146 /* return a CRITICAL status if we couldn't read any data */ 377 /* return a CRITICAL status if we couldn't read any data */
1147 if (strlen(curl_state.header_buf.buf) == 0 && strlen(curl_state.body_buf.buf) == 0) { 378 if (strlen(curl_state.header_buf->buf) == 0 && strlen(curl_state.body_buf->buf) == 0) {
1148 die(STATE_CRITICAL, _("HTTP CRITICAL - No header received from host\n")); 379 die(STATE_CRITICAL, _("HTTP CRITICAL - No header received from host\n"));
1149 } 380 }
1150 381
1151 /* get status line of answer, check sanity of HTTP code */ 382 /* get status line of answer, check sanity of HTTP code */
1152 if (curlhelp_parse_statusline(curl_state.header_buf.buf, &curl_state.status_line) < 0) { 383 if (curlhelp_parse_statusline(curl_state.header_buf->buf, curl_state.status_line) < 0) {
1153 snprintf(msg, DEFAULT_BUFFER_SIZE, 384 snprintf(msg, DEFAULT_BUFFER_SIZE,
1154 "Unparsable status line in %.3g seconds response time|%s\n", total_time, 385 "Unparsable status line in %.3g seconds response time|%s\n", total_time,
1155 perfstring); 386 perfstring);
@@ -1170,22 +401,22 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1170 401
1171 /* print status line, header, body if verbose */ 402 /* print status line, header, body if verbose */
1172 if (verbose >= 2) { 403 if (verbose >= 2) {
1173 printf("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", curl_state.header_buf.buf, 404 printf("**** HEADER ****\n%s\n**** CONTENT ****\n%s\n", curl_state.header_buf->buf,
1174 (workingState.no_body ? " [[ skipped ]]" : curl_state.body_buf.buf)); 405 (workingState.no_body ? " [[ skipped ]]" : curl_state.body_buf->buf));
1175 } 406 }
1176 407
1177 /* make sure the status line matches the response we are looking for */ 408 /* make sure the status line matches the response we are looking for */
1178 if (!expected_statuscode(curl_state.status_line.first_line, config.server_expect.string)) { 409 if (!expected_statuscode(curl_state.status_line->first_line, config.server_expect.string)) {
1179 if (workingState.serverPort == HTTP_PORT) { 410 if (workingState.serverPort == HTTP_PORT) {
1180 snprintf(msg, DEFAULT_BUFFER_SIZE, _("Invalid HTTP response received from host: %s\n"), 411 snprintf(msg, DEFAULT_BUFFER_SIZE, _("Invalid HTTP response received from host: %s\n"),
1181 curl_state.status_line.first_line); 412 curl_state.status_line->first_line);
1182 } else { 413 } else {
1183 snprintf(msg, DEFAULT_BUFFER_SIZE, 414 snprintf(msg, DEFAULT_BUFFER_SIZE,
1184 _("Invalid HTTP response received from host on port %d: %s\n"), 415 _("Invalid HTTP response received from host on port %d: %s\n"),
1185 workingState.serverPort, curl_state.status_line.first_line); 416 workingState.serverPort, curl_state.status_line->first_line);
1186 } 417 }
1187 die(STATE_CRITICAL, "HTTP CRITICAL - %s%s%s", msg, config.show_body ? "\n" : "", 418 die(STATE_CRITICAL, "HTTP CRITICAL - %s%s%s", msg, config.show_body ? "\n" : "",
1188 config.show_body ? curl_state.body_buf.buf : ""); 419 config.show_body ? curl_state.body_buf->buf : "");
1189 } 420 }
1190 421
1191 mp_state_enum result = STATE_OK; 422 mp_state_enum result = STATE_OK;
@@ -1200,7 +431,7 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1200 /* illegal return codes result in a critical state */ 431 /* illegal return codes result in a critical state */
1201 if (httpReturnCode >= 600 || httpReturnCode < 100) { 432 if (httpReturnCode >= 600 || httpReturnCode < 100) {
1202 die(STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%d, %.40s)\n"), 433 die(STATE_CRITICAL, _("HTTP CRITICAL: Invalid Status (%d, %.40s)\n"),
1203 curl_state.status_line.http_code, curl_state.status_line.msg); 434 curl_state.status_line->http_code, curl_state.status_line->msg);
1204 /* server errors result in a critical state */ 435 /* server errors result in a critical state */
1205 } else if (httpReturnCode >= 500) { 436 } else if (httpReturnCode >= 500) {
1206 result = STATE_CRITICAL; 437 result = STATE_CRITICAL;
@@ -1211,14 +442,14 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1211 } else if (httpReturnCode >= 300) { 442 } else if (httpReturnCode >= 300) {
1212 if (config.on_redirect_dependent) { 443 if (config.on_redirect_dependent) {
1213 if (config.followmethod == FOLLOW_LIBCURL) { 444 if (config.followmethod == FOLLOW_LIBCURL) {
1214 httpReturnCode = curl_state.status_line.http_code; 445 httpReturnCode = curl_state.status_line->http_code;
1215 } else { 446 } else {
1216 /* old check_http style redirection, if we come 447 /* old check_http style redirection, if we come
1217 * back here, we are in the same status as with 448 * back here, we are in the same status as with
1218 * the libcurl method 449 * the libcurl method
1219 */ 450 */
1220 redir_wrapper redir_result = 451 redir_wrapper redir_result =
1221 redir(&curl_state.header_buf, config, redir_depth, workingState); 452 redir(curl_state.header_buf, config, redir_depth, workingState);
1222 cleanup(curl_state); 453 cleanup(curl_state);
1223 check_http(config, redir_result.working_state, redir_result.redir_depth); 454 check_http(config, redir_result.working_state, redir_result.redir_depth);
1224 } 455 }
@@ -1252,21 +483,21 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1252 } 483 }
1253 484
1254 /* check status codes, set exit status accordingly */ 485 /* check status codes, set exit status accordingly */
1255 if (curl_state.status_line.http_code != httpReturnCode) { 486 if (curl_state.status_line->http_code != httpReturnCode) {
1256 die(STATE_CRITICAL, _("HTTP CRITICAL %s %d %s - different HTTP codes (cUrl has %ld)\n"), 487 die(STATE_CRITICAL, _("HTTP CRITICAL %s %d %s - different HTTP codes (cUrl has %ld)\n"),
1257 string_statuscode(curl_state.status_line.http_major, curl_state.status_line.http_minor), 488 string_statuscode(curl_state.status_line->http_major, curl_state.status_line->http_minor),
1258 curl_state.status_line.http_code, curl_state.status_line.msg, httpReturnCode); 489 curl_state.status_line->http_code, curl_state.status_line->msg, httpReturnCode);
1259 } 490 }
1260 491
1261 if (config.maximum_age >= 0) { 492 if (config.maximum_age >= 0) {
1262 result = max_state_alt( 493 result = max_state_alt(
1263 check_document_dates(&curl_state.header_buf, &msg, config.maximum_age), result); 494 check_document_dates(curl_state.header_buf, msg, config.maximum_age), result);
1264 } 495 }
1265 496
1266 /* Page and Header content checks go here */ 497 /* Page and Header content checks go here */
1267 498
1268 if (strlen(config.header_expect)) { 499 if (strlen(config.header_expect)) {
1269 if (!strstr(curl_state.header_buf.buf, config.header_expect)) { 500 if (!strstr(curl_state.header_buf->buf, config.header_expect)) {
1270 501
1271 char output_header_search[30] = ""; 502 char output_header_search[30] = "";
1272 strncpy(&output_header_search[0], config.header_expect, sizeof(output_header_search)); 503 strncpy(&output_header_search[0], config.header_expect, sizeof(output_header_search));
@@ -1289,7 +520,7 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1289 } 520 }
1290 521
1291 if (strlen(config.string_expect)) { 522 if (strlen(config.string_expect)) {
1292 if (!strstr(curl_state.body_buf.buf, config.string_expect)) { 523 if (!strstr(curl_state.body_buf->buf, config.string_expect)) {
1293 524
1294 char output_string_search[30] = ""; 525 char output_string_search[30] = "";
1295 strncpy(&output_string_search[0], config.string_expect, sizeof(output_string_search)); 526 strncpy(&output_string_search[0], config.string_expect, sizeof(output_string_search));
@@ -1313,7 +544,7 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1313 544
1314 if (strlen(config.regexp)) { 545 if (strlen(config.regexp)) {
1315 regmatch_t pmatch[REGS]; 546 regmatch_t pmatch[REGS];
1316 int errcode = regexec(&config.compiled_regex, curl_state.body_buf.buf, REGS, pmatch, 0); 547 int errcode = regexec(&config.compiled_regex, curl_state.body_buf->buf, REGS, pmatch, 0);
1317 if ((errcode == 0 && !config.invert_regex) || 548 if ((errcode == 0 && !config.invert_regex) ||
1318 (errcode == REG_NOMATCH && config.invert_regex)) { 549 (errcode == REG_NOMATCH && config.invert_regex)) {
1319 /* OK - No-op to avoid changing the logic around it */ 550 /* OK - No-op to avoid changing the logic around it */
@@ -1379,10 +610,10 @@ mp_state_enum check_http(const check_curl_config config, check_curl_working_stat
1379 die((int)max_state_alt(result, result_ssl), 610 die((int)max_state_alt(result, result_ssl),
1380 "HTTP %s: %s %d %s%s%s - %zu bytes in %.3f second response time %s|%s\n%s%s", 611 "HTTP %s: %s %d %s%s%s - %zu bytes in %.3f second response time %s|%s\n%s%s",
1381 state_text(result), 612 state_text(result),
1382 string_statuscode(curl_state.status_line.http_major, curl_state.status_line.http_minor), 613 string_statuscode(curl_state.status_line->http_major, curl_state.status_line->http_minor),
1383 curl_state.status_line.http_code, curl_state.status_line.msg, strlen(msg) > 0 ? " - " : "", 614 curl_state.status_line->http_code, curl_state.status_line->msg, strlen(msg) > 0 ? " - " : "",
1384 msg, page_len, total_time, (config.display_html ? "</A>" : ""), perfstring, 615 msg, page_len, total_time, (config.display_html ? "</A>" : ""), perfstring,
1385 (config.show_body ? curl_state.body_buf.buf : ""), (config.show_body ? "\n" : "")); 616 (config.show_body ? curl_state.body_buf->buf : ""), (config.show_body ? "\n" : ""));
1386 617
1387 return max_state_alt(result, result_ssl); 618 return max_state_alt(result, result_ssl);
1388} 619}
@@ -1574,14 +805,6 @@ redir_wrapper redir(curlhelp_write_curlbuf *header_buf, const check_curl_config
1574 return result; 805 return result;
1575} 806}
1576 807
1577/* check whether a file exists */
1578void test_file(char *path) {
1579 if (access(path, R_OK) == 0) {
1580 return;
1581 }
1582 usage2(_("file does not exist or is not readable"), path);
1583}
1584
1585check_curl_config_wrapper process_arguments(int argc, char **argv) { 808check_curl_config_wrapper process_arguments(int argc, char **argv) {
1586 enum { 809 enum {
1587 INVERT_REGEX = CHAR_MAX + 1, 810 INVERT_REGEX = CHAR_MAX + 1,
@@ -2212,42 +1435,6 @@ check_curl_config_wrapper process_arguments(int argc, char **argv) {
2212 return result; 1435 return result;
2213} 1436}
2214 1437
2215char *perfd_time(double elapsed_time, thresholds *thlds, long socket_timeout) {
2216 return fperfdata("time", elapsed_time, "s", (thlds->warning != NULL),
2217 thlds->warning ? thlds->warning->end : 0, (thlds->critical != NULL),
2218 thlds->critical ? thlds->critical->end : 0, true, 0, true, socket_timeout);
2219}
2220
2221char *perfd_time_connect(double elapsed_time_connect, long socket_timeout) {
2222 return fperfdata("time_connect", elapsed_time_connect, "s", false, 0, false, 0, false, 0, true,
2223 socket_timeout);
2224}
2225
2226char *perfd_time_ssl(double elapsed_time_ssl, long socket_timeout) {
2227 return fperfdata("time_ssl", elapsed_time_ssl, "s", false, 0, false, 0, false, 0, true,
2228 socket_timeout);
2229}
2230
2231char *perfd_time_headers(double elapsed_time_headers, long socket_timeout) {
2232 return fperfdata("time_headers", elapsed_time_headers, "s", false, 0, false, 0, false, 0, true,
2233 socket_timeout);
2234}
2235
2236char *perfd_time_firstbyte(double elapsed_time_firstbyte, long socket_timeout) {
2237 return fperfdata("time_firstbyte", elapsed_time_firstbyte, "s", false, 0, false, 0, false, 0,
2238 true, socket_timeout);
2239}
2240
2241char *perfd_time_transfer(double elapsed_time_transfer, long socket_timeout) {
2242 return fperfdata("time_transfer", elapsed_time_transfer, "s", false, 0, false, 0, false, 0,
2243 true, socket_timeout);
2244}
2245
2246char *perfd_size(size_t page_len, int min_page_len) {
2247 return perfdata("size", page_len, "B", (min_page_len > 0), min_page_len, (min_page_len > 0), 0,
2248 true, 0, false, 0);
2249}
2250
2251void print_help(void) { 1438void print_help(void) {
2252 print_revision(progname, NP_VERSION); 1439 print_revision(progname, NP_VERSION);
2253 1440
@@ -2523,403 +1710,6 @@ void print_usage(void) {
2523 1710
2524void print_curl_version(void) { printf("%s\n", curl_version()); } 1711void print_curl_version(void) { printf("%s\n", curl_version()); }
2525 1712
2526int curlhelp_initwritebuffer(curlhelp_write_curlbuf *buf) {
2527 buf->bufsize = DEFAULT_BUFFER_SIZE;
2528 buf->buflen = 0;
2529 buf->buf = (char *)malloc(buf->bufsize);
2530 if (buf->buf == NULL) {
2531 return -1;
2532 }
2533 return 0;
2534}
2535
2536size_t curlhelp_buffer_write_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
2537 curlhelp_write_curlbuf *buf = (curlhelp_write_curlbuf *)stream;
2538
2539 while (buf->bufsize < buf->buflen + size * nmemb + 1) {
2540 buf->bufsize = buf->bufsize * 2;
2541 buf->buf = (char *)realloc(buf->buf, buf->bufsize);
2542 if (buf->buf == NULL) {
2543 fprintf(stderr, "malloc failed (%d) %s\n", errno, strerror(errno));
2544 return 0;
2545 }
2546 }
2547
2548 memcpy(buf->buf + buf->buflen, buffer, size * nmemb);
2549 buf->buflen += size * nmemb;
2550 buf->buf[buf->buflen] = '\0';
2551
2552 return size * nmemb;
2553}
2554
2555size_t curlhelp_buffer_read_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
2556 curlhelp_read_curlbuf *buf = (curlhelp_read_curlbuf *)stream;
2557
2558 size_t minimalSize = min(nmemb * size, buf->buflen - buf->pos);
2559
2560 memcpy(buffer, buf->buf + buf->pos, minimalSize);
2561 buf->pos += minimalSize;
2562
2563 return minimalSize;
2564}
2565
2566void curlhelp_freewritebuffer(curlhelp_write_curlbuf *buf) {
2567 free(buf->buf);
2568 buf->buf = NULL;
2569}
2570
2571int curlhelp_initreadbuffer(curlhelp_read_curlbuf *buf, const char *data, size_t datalen) {
2572 buf->buflen = datalen;
2573 buf->buf = (char *)malloc(buf->buflen);
2574 if (buf->buf == NULL) {
2575 return -1;
2576 }
2577 memcpy(buf->buf, data, datalen);
2578 buf->pos = 0;
2579 return 0;
2580}
2581
2582void curlhelp_freereadbuffer(curlhelp_read_curlbuf *buf) {
2583 free(buf->buf);
2584 buf->buf = NULL;
2585}
2586
2587/* TODO: where to put this, it's actually part of sstrings2 (logically)?
2588 */
2589const char *strrstr2(const char *haystack, const char *needle) {
2590 if (haystack == NULL || needle == NULL) {
2591 return NULL;
2592 }
2593
2594 if (haystack[0] == '\0' || needle[0] == '\0') {
2595 return NULL;
2596 }
2597
2598 int counter = 0;
2599 const char *prev_pos = NULL;
2600 const char *pos = haystack;
2601 size_t len = strlen(needle);
2602 for (;;) {
2603 pos = strstr(pos, needle);
2604 if (pos == NULL) {
2605 if (counter == 0) {
2606 return NULL;
2607 }
2608 return prev_pos;
2609 }
2610 counter++;
2611 prev_pos = pos;
2612 pos += len;
2613 if (*pos == '\0') {
2614 return prev_pos;
2615 }
2616 }
2617}
2618
2619int curlhelp_parse_statusline(const char *buf, curlhelp_statusline *status_line) {
2620 /* find last start of a new header */
2621 const char *start = strrstr2(buf, "\r\nHTTP/");
2622 if (start != NULL) {
2623 start += 2;
2624 buf = start;
2625 }
2626
2627 char *first_line_end = strstr(buf, "\r\n");
2628 if (first_line_end == NULL) {
2629 return -1;
2630 }
2631
2632 size_t first_line_len = (size_t)(first_line_end - buf);
2633 status_line->first_line = (char *)malloc(first_line_len + 1);
2634 if (status_line->first_line == NULL) {
2635 return -1;
2636 }
2637 memcpy(status_line->first_line, buf, first_line_len);
2638 status_line->first_line[first_line_len] = '\0';
2639 char *first_line_buf = strdup(status_line->first_line);
2640
2641 /* protocol and version: "HTTP/x.x" SP or "HTTP/2" SP */
2642 char *temp_string = strtok(first_line_buf, "/");
2643 if (temp_string == NULL) {
2644 free(first_line_buf);
2645 return -1;
2646 }
2647 if (strcmp(temp_string, "HTTP") != 0) {
2648 free(first_line_buf);
2649 return -1;
2650 }
2651
2652 temp_string = strtok(NULL, " ");
2653 if (temp_string == NULL) {
2654 free(first_line_buf);
2655 return -1;
2656 }
2657
2658 char *temp_string_2;
2659 if (strchr(temp_string, '.') != NULL) {
2660
2661 /* HTTP 1.x case */
2662 strtok(temp_string, ".");
2663 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
2664 if (*temp_string_2 != '\0') {
2665 free(first_line_buf);
2666 return -1;
2667 }
2668 strtok(NULL, " ");
2669 status_line->http_minor = (int)strtol(temp_string, &temp_string_2, 10);
2670 if (*temp_string_2 != '\0') {
2671 free(first_line_buf);
2672 return -1;
2673 }
2674 temp_string += 4; /* 1.x SP */
2675 } else {
2676 /* HTTP 2 case */
2677 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
2678 status_line->http_minor = 0;
2679 temp_string += 2; /* 2 SP */
2680 }
2681
2682 /* status code: "404" or "404.1", then SP */
2683 temp_string = strtok(temp_string, " ");
2684 if (temp_string == NULL) {
2685 free(first_line_buf);
2686 return -1;
2687 }
2688 if (strchr(temp_string, '.') != NULL) {
2689 char *ppp;
2690 ppp = strtok(temp_string, ".");
2691 status_line->http_code = (int)strtol(ppp, &temp_string_2, 10);
2692 if (*temp_string_2 != '\0') {
2693 free(first_line_buf);
2694 return -1;
2695 }
2696 ppp = strtok(NULL, "");
2697 status_line->http_subcode = (int)strtol(ppp, &temp_string_2, 10);
2698 if (*temp_string_2 != '\0') {
2699 free(first_line_buf);
2700 return -1;
2701 }
2702 temp_string += 6; /* 400.1 SP */
2703 } else {
2704 status_line->http_code = (int)strtol(temp_string, &temp_string_2, 10);
2705 status_line->http_subcode = -1;
2706 if (*temp_string_2 != '\0') {
2707 free(first_line_buf);
2708 return -1;
2709 }
2710 temp_string += 4; /* 400 SP */
2711 }
2712
2713 /* Human readable message: "Not Found" CRLF */
2714
2715 temp_string = strtok(temp_string, "");
2716 if (temp_string == NULL) {
2717 status_line->msg = "";
2718 return 0;
2719 }
2720 status_line->msg = status_line->first_line + (temp_string - first_line_buf);
2721 free(first_line_buf);
2722
2723 return 0;
2724}
2725
2726void curlhelp_free_statusline(curlhelp_statusline *status_line) { free(status_line->first_line); }
2727
2728char *get_header_value(const struct phr_header *headers, const size_t nof_headers,
2729 const char *header) {
2730 for (size_t i = 0; i < nof_headers; i++) {
2731 if (headers[i].name != NULL &&
2732 strncasecmp(header, headers[i].name, max(headers[i].name_len, 4)) == 0) {
2733 return strndup(headers[i].value, headers[i].value_len);
2734 }
2735 }
2736 return NULL;
2737}
2738
2739mp_state_enum check_document_dates(const curlhelp_write_curlbuf *header_buf,
2740 char (*msg)[DEFAULT_BUFFER_SIZE], int maximum_age) {
2741 struct phr_header headers[255];
2742 size_t nof_headers = 255;
2743 curlhelp_statusline status_line;
2744 size_t msglen;
2745 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
2746 &status_line.http_minor, &status_line.http_code, &status_line.msg,
2747 &msglen, headers, &nof_headers, 0);
2748
2749 if (res == -1) {
2750 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
2751 }
2752
2753 char *server_date = get_header_value(headers, nof_headers, "date");
2754 char *document_date = get_header_value(headers, nof_headers, "last-modified");
2755
2756 mp_state_enum date_result = STATE_OK;
2757 if (!server_date || !*server_date) {
2758 char tmp[DEFAULT_BUFFER_SIZE];
2759
2760 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sServer date unknown, "), *msg);
2761 strcpy(*msg, tmp);
2762
2763 date_result = max_state_alt(STATE_UNKNOWN, date_result);
2764
2765 } else if (!document_date || !*document_date) {
2766 char tmp[DEFAULT_BUFFER_SIZE];
2767
2768 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sDocument modification date unknown, "), *msg);
2769 strcpy(*msg, tmp);
2770
2771 date_result = max_state_alt(STATE_CRITICAL, date_result);
2772
2773 } else {
2774 time_t srv_data = curl_getdate(server_date, NULL);
2775 time_t doc_data = curl_getdate(document_date, NULL);
2776 if (verbose >= 2) {
2777 printf("* server date: '%s' (%d), doc_date: '%s' (%d)\n", server_date, (int)srv_data,
2778 document_date, (int)doc_data);
2779 }
2780 if (srv_data <= 0) {
2781 char tmp[DEFAULT_BUFFER_SIZE];
2782
2783 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sServer date \"%100s\" unparsable, "), *msg,
2784 server_date);
2785 strcpy(*msg, tmp);
2786
2787 date_result = max_state_alt(STATE_CRITICAL, date_result);
2788 } else if (doc_data <= 0) {
2789 char tmp[DEFAULT_BUFFER_SIZE];
2790
2791 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sDocument date \"%100s\" unparsable, "), *msg,
2792 document_date);
2793 strcpy(*msg, tmp);
2794
2795 date_result = max_state_alt(STATE_CRITICAL, date_result);
2796 } else if (doc_data > srv_data + 30) {
2797 char tmp[DEFAULT_BUFFER_SIZE];
2798
2799 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sDocument is %d seconds in the future, "), *msg,
2800 (int)doc_data - (int)srv_data);
2801 strcpy(*msg, tmp);
2802
2803 date_result = max_state_alt(STATE_CRITICAL, date_result);
2804 } else if (doc_data < srv_data - maximum_age) {
2805 time_t last_modified = (srv_data - doc_data);
2806 if (last_modified > (60 * 60 * 24 * 2)) {
2807 char tmp[DEFAULT_BUFFER_SIZE];
2808
2809 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sLast modified %.1f days ago, "), *msg,
2810 ((float)last_modified) / (60 * 60 * 24));
2811 strcpy(*msg, tmp);
2812
2813 date_result = max_state_alt(STATE_CRITICAL, date_result);
2814 } else {
2815 char tmp[DEFAULT_BUFFER_SIZE];
2816
2817 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sLast modified %ld:%02ld:%02ld ago, "), *msg,
2818 last_modified / (60 * 60), (last_modified / 60) % 60, last_modified % 60);
2819 strcpy(*msg, tmp);
2820
2821 date_result = max_state_alt(STATE_CRITICAL, date_result);
2822 }
2823 }
2824 }
2825
2826 if (server_date) {
2827 free(server_date);
2828 }
2829 if (document_date) {
2830 free(document_date);
2831 }
2832
2833 return date_result;
2834}
2835
2836size_t get_content_length(const curlhelp_write_curlbuf *header_buf,
2837 const curlhelp_write_curlbuf *body_buf) {
2838 struct phr_header headers[255];
2839 size_t nof_headers = 255;
2840 size_t msglen;
2841 curlhelp_statusline status_line;
2842 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
2843 &status_line.http_minor, &status_line.http_code, &status_line.msg,
2844 &msglen, headers, &nof_headers, 0);
2845
2846 if (res == -1) {
2847 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
2848 }
2849
2850 char *content_length_s = get_header_value(headers, nof_headers, "content-length");
2851 if (!content_length_s) {
2852 return header_buf->buflen + body_buf->buflen;
2853 }
2854
2855 content_length_s += strspn(content_length_s, " \t");
2856 size_t content_length = atoi(content_length_s);
2857 if (content_length != body_buf->buflen) {
2858 /* TODO: should we warn if the actual and the reported body length don't match? */
2859 }
2860
2861 if (content_length_s) {
2862 free(content_length_s);
2863 }
2864
2865 return header_buf->buflen + body_buf->buflen;
2866}
2867
2868/* TODO: is there a better way in libcurl to check for the SSL library? */
2869curlhelp_ssl_library curlhelp_get_ssl_library(void) {
2870 curlhelp_ssl_library ssl_library = CURLHELP_SSL_LIBRARY_UNKNOWN;
2871
2872 curl_version_info_data *version_data = curl_version_info(CURLVERSION_NOW);
2873 if (version_data == NULL) {
2874 return CURLHELP_SSL_LIBRARY_UNKNOWN;
2875 }
2876
2877 char *ssl_version = strdup(version_data->ssl_version);
2878 if (ssl_version == NULL) {
2879 return CURLHELP_SSL_LIBRARY_UNKNOWN;
2880 }
2881
2882 char *library = strtok(ssl_version, "/");
2883 if (library == NULL) {
2884 return CURLHELP_SSL_LIBRARY_UNKNOWN;
2885 }
2886
2887 if (strcmp(library, "OpenSSL") == 0) {
2888 ssl_library = CURLHELP_SSL_LIBRARY_OPENSSL;
2889 } else if (strcmp(library, "LibreSSL") == 0) {
2890 ssl_library = CURLHELP_SSL_LIBRARY_LIBRESSL;
2891 } else if (strcmp(library, "GnuTLS") == 0) {
2892 ssl_library = CURLHELP_SSL_LIBRARY_GNUTLS;
2893 } else if (strcmp(library, "NSS") == 0) {
2894 ssl_library = CURLHELP_SSL_LIBRARY_NSS;
2895 }
2896
2897 if (verbose >= 2) {
2898 printf("* SSL library string is : %s %s (%d)\n", version_data->ssl_version, library,
2899 ssl_library);
2900 }
2901
2902 free(ssl_version);
2903
2904 return ssl_library;
2905}
2906
2907const char *curlhelp_get_ssl_library_string(curlhelp_ssl_library ssl_library) {
2908 switch (ssl_library) {
2909 case CURLHELP_SSL_LIBRARY_OPENSSL:
2910 return "OpenSSL";
2911 case CURLHELP_SSL_LIBRARY_LIBRESSL:
2912 return "LibreSSL";
2913 case CURLHELP_SSL_LIBRARY_GNUTLS:
2914 return "GnuTLS";
2915 case CURLHELP_SSL_LIBRARY_NSS:
2916 return "NSS";
2917 case CURLHELP_SSL_LIBRARY_UNKNOWN:
2918 default:
2919 return "unknown";
2920 }
2921}
2922
2923#ifdef LIBCURL_FEATURE_SSL 1713#ifdef LIBCURL_FEATURE_SSL
2924# ifndef USE_OPENSSL 1714# ifndef USE_OPENSSL
2925time_t parse_cert_date(const char *s) { 1715time_t parse_cert_date(const char *s) {
@@ -2938,35 +1728,31 @@ time_t parse_cert_date(const char *s) {
2938 1728
2939 return date; 1729 return date;
2940} 1730}
1731# endif /* USE_OPENSSL */
1732#endif /* LIBCURL_FEATURE_SSL */
2941 1733
1734#ifdef LIBCURL_FEATURE_SSL
1735# ifndef USE_OPENSSL
2942/* TODO: this needs cleanup in the sslutils.c, maybe we the #else case to 1736/* TODO: this needs cleanup in the sslutils.c, maybe we the #else case to
2943 * OpenSSL could be this function 1737 * OpenSSL could be this function
2944 */ 1738 */
2945int net_noopenssl_check_certificate(cert_ptr_union *cert_ptr, int days_till_exp_warn, 1739int net_noopenssl_check_certificate(cert_ptr_union *cert_ptr, int days_till_exp_warn,
2946 int days_till_exp_crit) { 1740 int days_till_exp_crit) {
2947 struct curl_slist *slist;
2948 int cname_found = 0;
2949 char *start_date_str = NULL;
2950 char *end_date_str = NULL;
2951 time_t start_date;
2952 time_t end_date;
2953 char *tz;
2954 float time_left;
2955 int days_left;
2956 int time_remaining;
2957 char timestamp[50] = "";
2958 int status = STATE_UNKNOWN;
2959 1741
2960 if (verbose >= 2) { 1742 if (verbose >= 2) {
2961 printf("**** REQUEST CERTIFICATES ****\n"); 1743 printf("**** REQUEST CERTIFICATES ****\n");
2962 } 1744 }
2963 1745
1746 char *start_date_str = NULL;
1747 char *end_date_str = NULL;
2964 bool have_first_cert = false; 1748 bool have_first_cert = false;
1749 bool cname_found = false;
2965 for (int i = 0; i < cert_ptr->to_certinfo->num_of_certs; i++) { 1750 for (int i = 0; i < cert_ptr->to_certinfo->num_of_certs; i++) {
2966 if (have_first_cert) { 1751 if (have_first_cert) {
2967 break; 1752 break;
2968 } 1753 }
2969 1754
1755 struct curl_slist *slist;
2970 for (slist = cert_ptr->to_certinfo->certinfo[i]; slist; slist = slist->next) { 1756 for (slist = cert_ptr->to_certinfo->certinfo[i]; slist; slist = slist->next) {
2971 /* find first common name in subject, 1757 /* find first common name in subject,
2972 * TODO: check alternative subjects for 1758 * TODO: check alternative subjects for
@@ -2982,7 +1768,7 @@ int net_noopenssl_check_certificate(cert_ptr_union *cert_ptr, int days_till_exp_
2982 } 1768 }
2983 if (p != NULL) { 1769 if (p != NULL) {
2984 if (strncmp(host_name, p + d, strlen(host_name)) == 0) { 1770 if (strncmp(host_name, p + d, strlen(host_name)) == 0) {
2985 cname_found = 1; 1771 cname_found = true;
2986 } 1772 }
2987 } 1773 }
2988 } else if (strncasecmp(slist->data, "Start Date:", 11) == 0) { 1774 } else if (strncasecmp(slist->data, "Start Date:", 11) == 0) {
@@ -3008,7 +1794,7 @@ int net_noopenssl_check_certificate(cert_ptr_union *cert_ptr, int days_till_exp_
3008 return STATE_CRITICAL; 1794 return STATE_CRITICAL;
3009 } 1795 }
3010 1796
3011 start_date = parse_cert_date(start_date_str); 1797 time_t start_date = parse_cert_date(start_date_str);
3012 if (start_date <= 0) { 1798 if (start_date <= 0) {
3013 snprintf(msg, DEFAULT_BUFFER_SIZE, 1799 snprintf(msg, DEFAULT_BUFFER_SIZE,
3014 _("WARNING - Unparsable 'Start Date' in certificate: '%s'"), start_date_str); 1800 _("WARNING - Unparsable 'Start Date' in certificate: '%s'"), start_date_str);
@@ -3016,7 +1802,7 @@ int net_noopenssl_check_certificate(cert_ptr_union *cert_ptr, int days_till_exp_
3016 return STATE_WARNING; 1802 return STATE_WARNING;
3017 } 1803 }
3018 1804
3019 end_date = parse_cert_date(end_date_str); 1805 time_t end_date = parse_cert_date(end_date_str);
3020 if (end_date <= 0) { 1806 if (end_date <= 0) {
3021 snprintf(msg, DEFAULT_BUFFER_SIZE, 1807 snprintf(msg, DEFAULT_BUFFER_SIZE,
3022 _("WARNING - Unparsable 'Expire Date' in certificate: '%s'"), start_date_str); 1808 _("WARNING - Unparsable 'Expire Date' in certificate: '%s'"), start_date_str);
@@ -3024,11 +1810,13 @@ int net_noopenssl_check_certificate(cert_ptr_union *cert_ptr, int days_till_exp_
3024 return STATE_WARNING; 1810 return STATE_WARNING;
3025 } 1811 }
3026 1812
3027 time_left = difftime(end_date, time(NULL)); 1813 float time_left = difftime(end_date, time(NULL));
3028 days_left = time_left / 86400; 1814 int days_left = time_left / 86400;
3029 tz = getenv("TZ"); 1815 char *tz = getenv("TZ");
3030 setenv("TZ", "GMT", 1); 1816 setenv("TZ", "GMT", 1);
3031 tzset(); 1817 tzset();
1818
1819 char timestamp[50] = "";
3032 strftime(timestamp, 50, "%c %z", localtime(&end_date)); 1820 strftime(timestamp, 50, "%c %z", localtime(&end_date));
3033 if (tz) { 1821 if (tz) {
3034 setenv("TZ", tz, 1); 1822 setenv("TZ", tz, 1);
@@ -3037,6 +1825,8 @@ int net_noopenssl_check_certificate(cert_ptr_union *cert_ptr, int days_till_exp_
3037 } 1825 }
3038 tzset(); 1826 tzset();
3039 1827
1828 mp_state_enum status = STATE_UNKNOWN;
1829 int time_remaining;
3040 if (days_left > 0 && days_left <= days_till_exp_warn) { 1830 if (days_left > 0 && days_left <= days_till_exp_warn) {
3041 printf(_("%s - Certificate '%s' expires in %d day(s) (%s).\n"), 1831 printf(_("%s - Certificate '%s' expires in %d day(s) (%s).\n"),
3042 (days_left > days_till_exp_crit) ? "WARNING" : "CRITICAL", host_name, days_left, 1832 (days_left > days_till_exp_crit) ? "WARNING" : "CRITICAL", host_name, days_left,
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..42d63bc4
--- /dev/null
+++ b/plugins/check_curl.d/check_curl_helpers.c
@@ -0,0 +1,1217 @@
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 "../utils.h"
8
9extern int verbose;
10char msg[DEFAULT_BUFFER_SIZE];
11char errbuf[MAX_INPUT_BUFFER];
12bool is_openssl_callback = false;
13bool add_sslctx_verify_fun = false;
14
15check_curl_configure_curl_wrapper
16check_curl_configure_curl(const check_curl_static_curl_config config,
17 check_curl_working_state working_state, bool check_cert,
18 bool on_redirect_dependent, int follow_method, int max_depth) {
19 check_curl_configure_curl_wrapper result = {
20 .errorcode = OK,
21 .curl_state =
22 {
23 .curl_global_initialized = false,
24 .curl_easy_initialized = false,
25 .curl = NULL,
26
27 .body_buf_initialized = false,
28 .body_buf = NULL,
29 .header_buf_initialized = false,
30 .header_buf = NULL,
31 .status_line_initialized = false,
32 .status_line = NULL,
33 .put_buf_initialized = false,
34 .put_buf = NULL,
35
36 .header_list = NULL,
37 .host = NULL,
38 },
39 };
40
41 if ((result.curl_state.status_line = calloc(1, sizeof(curlhelp_statusline))) == NULL) {
42 die(STATE_UNKNOWN, "HTTP UNKNOWN - allocation of statusline failed\n");
43 }
44
45 if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
46 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_global_init failed\n");
47 }
48 result.curl_state.curl_global_initialized = true;
49
50 if ((result.curl_state.curl = curl_easy_init()) == NULL) {
51 die(STATE_UNKNOWN, "HTTP UNKNOWN - curl_easy_init failed\n");
52 }
53 result.curl_state.curl_easy_initialized = true;
54
55 if (verbose >= 1) {
56 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_VERBOSE, 1),
57 "CURLOPT_VERBOSE");
58 }
59
60 /* print everything on stdout like check_http would do */
61 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_STDERR, stdout),
62 "CURLOPT_STDERR");
63
64 if (config.automatic_decompression) {
65#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6)
66 handle_curl_option_return_code(
67 curl_easy_setopt(result.curl_state.curl, CURLOPT_ACCEPT_ENCODING, ""),
68 "CURLOPT_ACCEPT_ENCODING");
69#else
70 handle_curl_option_return_code(
71 curl_easy_setopt(result.curl_state.curl, CURLOPT_ENCODING, ""), "CURLOPT_ENCODING");
72#endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 21, 6) */
73 }
74
75 /* initialize buffer for body of the answer */
76 if (curlhelp_initwritebuffer(&result.curl_state.body_buf) < 0) {
77 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for body\n");
78 }
79 result.curl_state.body_buf_initialized = true;
80
81 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEFUNCTION,
82 curlhelp_buffer_write_callback),
83 "CURLOPT_WRITEFUNCTION");
84 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEDATA,
85 (void *)result.curl_state.body_buf),
86 "CURLOPT_WRITEDATA");
87
88 /* initialize buffer for header of the answer */
89 if (curlhelp_initwritebuffer(&result.curl_state.header_buf) < 0) {
90 die(STATE_UNKNOWN, "HTTP CRITICAL - out of memory allocating buffer for header\n");
91 }
92 result.curl_state.header_buf_initialized = true;
93
94 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_HEADERFUNCTION,
95 curlhelp_buffer_write_callback),
96 "CURLOPT_HEADERFUNCTION");
97 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_WRITEHEADER,
98 (void *)result.curl_state.header_buf),
99 "CURLOPT_WRITEHEADER");
100
101 /* set the error buffer */
102 handle_curl_option_return_code(
103 curl_easy_setopt(result.curl_state.curl, CURLOPT_ERRORBUFFER, errbuf),
104 "CURLOPT_ERRORBUFFER");
105
106 /* set timeouts */
107 handle_curl_option_return_code(
108 curl_easy_setopt(result.curl_state.curl, CURLOPT_CONNECTTIMEOUT, config.socket_timeout),
109 "CURLOPT_CONNECTTIMEOUT");
110
111 handle_curl_option_return_code(
112 curl_easy_setopt(result.curl_state.curl, CURLOPT_TIMEOUT, config.socket_timeout),
113 "CURLOPT_TIMEOUT");
114
115 /* enable haproxy protocol */
116 if (config.haproxy_protocol) {
117 handle_curl_option_return_code(
118 curl_easy_setopt(result.curl_state.curl, CURLOPT_HAPROXYPROTOCOL, 1L),
119 "CURLOPT_HAPROXYPROTOCOL");
120 }
121
122 // fill dns resolve cache to make curl connect to the given server_address instead of the
123 // host_name, only required for ssl, because we use the host_name later on to make SNI happy
124 char dnscache[DEFAULT_BUFFER_SIZE];
125 char addrstr[DEFAULT_BUFFER_SIZE / 2];
126 if (working_state.use_ssl && working_state.host_name != NULL) {
127 int res;
128 if ((res = lookup_host(working_state.server_address, addrstr, DEFAULT_BUFFER_SIZE / 2,
129 config.sin_family)) != 0) {
130 snprintf(msg, DEFAULT_BUFFER_SIZE,
131 _("Unable to lookup IP address for '%s': getaddrinfo returned %d - %s"),
132 working_state.server_address, res, gai_strerror(res));
133 die(STATE_CRITICAL, "HTTP CRITICAL - %s\n", msg);
134 }
135 snprintf(dnscache, DEFAULT_BUFFER_SIZE, "%s:%d:%s", working_state.host_name,
136 working_state.serverPort, addrstr);
137 result.curl_state.host = curl_slist_append(NULL, dnscache);
138 curl_easy_setopt(result.curl_state.curl, CURLOPT_RESOLVE, result.curl_state.host);
139 if (verbose >= 1) {
140 printf("* curl CURLOPT_RESOLVE: %s\n", dnscache);
141 }
142 }
143
144 // If server_address is an IPv6 address it must be surround by square brackets
145 struct in6_addr tmp_in_addr;
146 if (inet_pton(AF_INET6, working_state.server_address, &tmp_in_addr) == 1) {
147 char *new_server_address = calloc(strlen(working_state.server_address) + 3, sizeof(char));
148 if (new_server_address == NULL) {
149 die(STATE_UNKNOWN, "HTTP UNKNOWN - Unable to allocate memory\n");
150 }
151 snprintf(new_server_address, strlen(working_state.server_address) + 3, "[%s]",
152 working_state.server_address);
153 working_state.server_address = new_server_address;
154 }
155
156 /* compose URL: use the address we want to connect to, set Host: header later */
157 char url[DEFAULT_BUFFER_SIZE];
158 snprintf(url, DEFAULT_BUFFER_SIZE, "%s://%s:%d%s", working_state.use_ssl ? "https" : "http",
159 (working_state.use_ssl & (working_state.host_name != NULL))
160 ? working_state.host_name
161 : working_state.server_address,
162 working_state.serverPort, working_state.server_url);
163
164 if (verbose >= 1) {
165 printf("* curl CURLOPT_URL: %s\n", url);
166 }
167 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, url),
168 "CURLOPT_URL");
169
170 /* extract proxy information for legacy proxy https requests */
171 if (!strcmp(working_state.http_method, "CONNECT") ||
172 strstr(working_state.server_url, "http") == working_state.server_url) {
173 handle_curl_option_return_code(
174 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXY, working_state.server_address),
175 "CURLOPT_PROXY");
176 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYPORT,
177 (long)working_state.serverPort),
178 "CURLOPT_PROXYPORT");
179 if (verbose >= 2) {
180 printf("* curl CURLOPT_PROXY: %s:%d\n", working_state.server_address,
181 working_state.serverPort);
182 }
183 working_state.http_method = "GET";
184 handle_curl_option_return_code(
185 curl_easy_setopt(result.curl_state.curl, CURLOPT_URL, working_state.server_url),
186 "CURLOPT_URL");
187 }
188
189 /* disable body for HEAD request */
190 if (working_state.http_method && !strcmp(working_state.http_method, "HEAD")) {
191 working_state.no_body = true;
192 }
193
194 /* set HTTP protocol version */
195 handle_curl_option_return_code(
196 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTP_VERSION, config.curl_http_version),
197 "CURLOPT_HTTP_VERSION");
198
199 /* set HTTP method */
200 if (working_state.http_method) {
201 if (!strcmp(working_state.http_method, "POST")) {
202 handle_curl_option_return_code(
203 curl_easy_setopt(result.curl_state.curl, CURLOPT_POST, 1), "CURLOPT_POST");
204 } else if (!strcmp(working_state.http_method, "PUT")) {
205 handle_curl_option_return_code(
206 curl_easy_setopt(result.curl_state.curl, CURLOPT_UPLOAD, 1), "CURLOPT_UPLOAD");
207 } else {
208 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
209 CURLOPT_CUSTOMREQUEST,
210 working_state.http_method),
211 "CURLOPT_CUSTOMREQUEST");
212 }
213 }
214
215 char *force_host_header = NULL;
216 /* check if Host header is explicitly set in options */
217 if (config.http_opt_headers_count) {
218 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
219 if (strncmp(config.http_opt_headers[i], "Host:", 5) == 0) {
220 force_host_header = config.http_opt_headers[i];
221 }
222 }
223 }
224
225 /* set hostname (virtual hosts), not needed if CURLOPT_CONNECT_TO is used, but left in
226 * anyway */
227 char http_header[DEFAULT_BUFFER_SIZE];
228 if (working_state.host_name != NULL && force_host_header == NULL) {
229 if ((working_state.virtualPort != HTTP_PORT && !working_state.use_ssl) ||
230 (working_state.virtualPort != HTTPS_PORT && working_state.use_ssl)) {
231 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s:%d", working_state.host_name,
232 working_state.virtualPort);
233 } else {
234 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Host: %s", working_state.host_name);
235 }
236 result.curl_state.header_list =
237 curl_slist_append(result.curl_state.header_list, http_header);
238 }
239
240 /* always close connection, be nice to servers */
241 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Connection: close");
242 result.curl_state.header_list = curl_slist_append(result.curl_state.header_list, http_header);
243
244 /* attach additional headers supplied by the user */
245 /* optionally send any other header tag */
246 if (config.http_opt_headers_count) {
247 for (size_t i = 0; i < config.http_opt_headers_count; i++) {
248 result.curl_state.header_list =
249 curl_slist_append(result.curl_state.header_list, config.http_opt_headers[i]);
250 }
251 }
252
253 /* set HTTP headers */
254 handle_curl_option_return_code(
255 curl_easy_setopt(result.curl_state.curl, CURLOPT_HTTPHEADER, result.curl_state.header_list),
256 "CURLOPT_HTTPHEADER");
257
258#ifdef LIBCURL_FEATURE_SSL
259 /* set SSL version, warn about insecure or unsupported versions */
260 if (working_state.use_ssl) {
261 handle_curl_option_return_code(
262 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLVERSION, config.ssl_version),
263 "CURLOPT_SSLVERSION");
264 }
265
266 /* client certificate and key to present to server (SSL) */
267 if (config.client_cert) {
268 handle_curl_option_return_code(
269 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLCERT, config.client_cert),
270 "CURLOPT_SSLCERT");
271 }
272
273 if (config.client_privkey) {
274 handle_curl_option_return_code(
275 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSLKEY, config.client_privkey),
276 "CURLOPT_SSLKEY");
277 }
278
279 if (config.ca_cert) {
280 handle_curl_option_return_code(
281 curl_easy_setopt(result.curl_state.curl, CURLOPT_CAINFO, config.ca_cert),
282 "CURLOPT_CAINFO");
283 }
284
285 if (config.ca_cert || config.verify_peer_and_host) {
286 /* per default if we have a CA verify both the peer and the
287 * hostname in the certificate, can be switched off later */
288 handle_curl_option_return_code(
289 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 1),
290 "CURLOPT_SSL_VERIFYPEER");
291 handle_curl_option_return_code(
292 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 2),
293 "CURLOPT_SSL_VERIFYHOST");
294 } else {
295 /* backward-compatible behaviour, be tolerant in checks
296 * TODO: depending on more options have aspects we want
297 * to be less tolerant about ssl verfications
298 */
299 handle_curl_option_return_code(
300 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYPEER, 0),
301 "CURLOPT_SSL_VERIFYPEER");
302 handle_curl_option_return_code(
303 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_VERIFYHOST, 0),
304 "CURLOPT_SSL_VERIFYHOST");
305 }
306
307 /* detect SSL library used by libcurl */
308 curlhelp_ssl_library ssl_library = curlhelp_get_ssl_library();
309
310 /* try hard to get a stack of certificates to verify against */
311 if (check_cert) {
312# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1)
313 /* inform curl to report back certificates */
314 switch (ssl_library) {
315 case CURLHELP_SSL_LIBRARY_OPENSSL:
316 case CURLHELP_SSL_LIBRARY_LIBRESSL:
317 /* set callback to extract certificate with OpenSSL context function (works with
318 * OpenSSL-style libraries only!) */
319# ifdef USE_OPENSSL
320 /* libcurl and monitoring plugins built with OpenSSL, good */
321 add_sslctx_verify_fun = true;
322 is_openssl_callback = true;
323# endif /* USE_OPENSSL */
324 /* libcurl is built with OpenSSL, monitoring plugins, so falling
325 * back to manually extracting certificate information */
326 handle_curl_option_return_code(
327 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
328 break;
329
330 case CURLHELP_SSL_LIBRARY_NSS:
331# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0)
332 /* NSS: support for CERTINFO is implemented since 7.34.0 */
333 handle_curl_option_return_code(
334 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
335# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
336 die(STATE_CRITICAL,
337 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
338 "'%s' is too old)\n",
339 curlhelp_get_ssl_library_string(ssl_library));
340# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 34, 0) */
341 break;
342
343 case CURLHELP_SSL_LIBRARY_GNUTLS:
344# if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0)
345 /* GnuTLS: support for CERTINFO is implemented since 7.42.0 */
346 handle_curl_option_return_code(
347 curl_easy_setopt(result.curl_state.curl, CURLOPT_CERTINFO, 1L), "CURLOPT_CERTINFO");
348# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
349 die(STATE_CRITICAL,
350 "HTTP CRITICAL - Cannot retrieve certificates (libcurl linked with SSL library "
351 "'%s' is too old)\n",
352 curlhelp_get_ssl_library_string(ssl_library));
353# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 42, 0) */
354 break;
355
356 case CURLHELP_SSL_LIBRARY_UNKNOWN:
357 default:
358 die(STATE_CRITICAL,
359 "HTTP CRITICAL - Cannot retrieve certificates (unknown SSL library '%s', must "
360 "implement first)\n",
361 curlhelp_get_ssl_library_string(ssl_library));
362 break;
363 }
364# else /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
365 /* old libcurl, our only hope is OpenSSL, otherwise we are out of luck */
366 if (ssl_library == CURLHELP_SSL_LIBRARY_OPENSSL ||
367 ssl_library == CURLHELP_SSL_LIBRARY_LIBRESSL) {
368 add_sslctx_verify_fun = true;
369 } else {
370 die(STATE_CRITICAL, "HTTP CRITICAL - Cannot retrieve certificates (no "
371 "CURLOPT_SSL_CTX_FUNCTION, no OpenSSL library or libcurl "
372 "too old and has no CURLOPT_CERTINFO)\n");
373 }
374# endif /* LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 1) */
375 }
376
377# if LIBCURL_VERSION_NUM >= \
378 MAKE_LIBCURL_VERSION(7, 10, 6) /* required for CURLOPT_SSL_CTX_FUNCTION */
379 // ssl ctx function is not available with all ssl backends
380 if (curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, NULL) !=
381 CURLE_UNKNOWN_OPTION) {
382 handle_curl_option_return_code(
383 curl_easy_setopt(result.curl_state.curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun),
384 "CURLOPT_SSL_CTX_FUNCTION");
385 }
386# endif
387#endif /* LIBCURL_FEATURE_SSL */
388
389 /* set default or user-given user agent identification */
390 handle_curl_option_return_code(
391 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERAGENT, config.user_agent),
392 "CURLOPT_USERAGENT");
393
394 /* proxy-authentication */
395 if (strcmp(config.proxy_auth, "")) {
396 handle_curl_option_return_code(
397 curl_easy_setopt(result.curl_state.curl, CURLOPT_PROXYUSERPWD, config.proxy_auth),
398 "CURLOPT_PROXYUSERPWD");
399 }
400
401 /* authentication */
402 if (strcmp(config.user_auth, "")) {
403 handle_curl_option_return_code(
404 curl_easy_setopt(result.curl_state.curl, CURLOPT_USERPWD, config.user_auth),
405 "CURLOPT_USERPWD");
406 }
407 /* TODO: parameter auth method, bitfield of following methods:
408 * CURLAUTH_BASIC (default)
409 * CURLAUTH_DIGEST
410 * CURLAUTH_DIGEST_IE
411 * CURLAUTH_NEGOTIATE
412 * CURLAUTH_NTLM
413 * CURLAUTH_NTLM_WB
414 *
415 * convenience tokens for typical sets of methods:
416 * CURLAUTH_ANYSAFE: most secure, without BASIC
417 * or CURLAUTH_ANY: most secure, even BASIC if necessary
418 *
419 * handle_curl_option_return_code (curl_easy_setopt( curl, CURLOPT_HTTPAUTH,
420 * (long)CURLAUTH_DIGEST ), "CURLOPT_HTTPAUTH");
421 */
422
423 /* handle redirections */
424 if (on_redirect_dependent) {
425 if (follow_method == FOLLOW_LIBCURL) {
426 handle_curl_option_return_code(
427 curl_easy_setopt(result.curl_state.curl, CURLOPT_FOLLOWLOCATION, 1),
428 "CURLOPT_FOLLOWLOCATION");
429
430 /* default -1 is infinite, not good, could lead to zombie plugins!
431 Setting it to one bigger than maximal limit to handle errors nicely below
432 */
433 handle_curl_option_return_code(
434 curl_easy_setopt(result.curl_state.curl, CURLOPT_MAXREDIRS, max_depth + 1),
435 "CURLOPT_MAXREDIRS");
436
437 /* for now allow only http and https (we are a http(s) check plugin in the end) */
438#if LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 85, 0)
439 handle_curl_option_return_code(
440 curl_easy_setopt(result.curl_state.curl, CURLOPT_REDIR_PROTOCOLS_STR, "http,https"),
441 "CURLOPT_REDIR_PROTOCOLS_STR");
442#elif LIBCURL_VERSION_NUM >= MAKE_LIBCURL_VERSION(7, 19, 4)
443 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
444 CURLOPT_REDIR_PROTOCOLS,
445 CURLPROTO_HTTP | CURLPROTO_HTTPS),
446 "CURLOPT_REDIRECT_PROTOCOLS");
447#endif
448
449 /* TODO: handle the following aspects of redirection, make them
450 * command line options too later:
451 CURLOPT_POSTREDIR: method switch
452 CURLINFO_REDIRECT_URL: custom redirect option
453 CURLOPT_REDIRECT_PROTOCOLS: allow people to step outside safe protocols
454 CURLINFO_REDIRECT_COUNT: get the number of redirects, print it, maybe a range
455 option here is nice like for expected page size?
456 */
457 } else {
458 /* old style redirection*/
459 }
460 }
461 /* no-body */
462 if (working_state.no_body) {
463 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl, CURLOPT_NOBODY, 1),
464 "CURLOPT_NOBODY");
465 }
466
467 /* IPv4 or IPv6 forced DNS resolution */
468 if (config.sin_family == AF_UNSPEC) {
469 handle_curl_option_return_code(
470 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER),
471 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_WHATEVER)");
472 } else if (config.sin_family == AF_INET) {
473 handle_curl_option_return_code(
474 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4),
475 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V4)");
476 }
477#if defined(USE_IPV6) && defined(LIBCURL_FEATURE_IPV6)
478 else if (config.sin_family == AF_INET6) {
479 handle_curl_option_return_code(
480 curl_easy_setopt(result.curl_state.curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6),
481 "CURLOPT_IPRESOLVE(CURL_IPRESOLVE_V6)");
482 }
483#endif
484
485 /* either send http POST data (any data, not only POST)*/
486 if (!strcmp(working_state.http_method, "POST") || !strcmp(working_state.http_method, "PUT")) {
487 /* set content of payload for POST and PUT */
488 if (config.http_content_type) {
489 snprintf(http_header, DEFAULT_BUFFER_SIZE, "Content-Type: %s",
490 config.http_content_type);
491 result.curl_state.header_list =
492 curl_slist_append(result.curl_state.header_list, http_header);
493 }
494 /* NULL indicates "HTTP Continue" in libcurl, provide an empty string
495 * in case of no POST/PUT data */
496 if (!working_state.http_post_data) {
497 working_state.http_post_data = "";
498 }
499
500 if (!strcmp(working_state.http_method, "POST")) {
501 /* POST method, set payload with CURLOPT_POSTFIELDS */
502 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
503 CURLOPT_POSTFIELDS,
504 working_state.http_post_data),
505 "CURLOPT_POSTFIELDS");
506 } else if (!strcmp(working_state.http_method, "PUT")) {
507 handle_curl_option_return_code(
508 curl_easy_setopt(result.curl_state.curl, CURLOPT_READFUNCTION,
509 (curl_read_callback)curlhelp_buffer_read_callback),
510 "CURLOPT_READFUNCTION");
511 if (curlhelp_initreadbuffer(&result.curl_state.put_buf, working_state.http_post_data,
512 strlen(working_state.http_post_data)) < 0) {
513 die(STATE_UNKNOWN,
514 "HTTP CRITICAL - out of memory allocating read buffer for PUT\n");
515 }
516 result.curl_state.put_buf_initialized = true;
517 handle_curl_option_return_code(curl_easy_setopt(result.curl_state.curl,
518 CURLOPT_READDATA,
519 (void *)&result.curl_state.put_buf),
520 "CURLOPT_READDATA");
521 handle_curl_option_return_code(
522 curl_easy_setopt(result.curl_state.curl, CURLOPT_INFILESIZE,
523 (curl_off_t)strlen(working_state.http_post_data)),
524 "CURLOPT_INFILESIZE");
525 }
526 }
527
528 /* cookie handling */
529 if (config.cookie_jar_file != NULL) {
530 /* enable reading cookies from a file, and if the filename is an empty string, only
531 * enable the curl cookie engine */
532 handle_curl_option_return_code(
533 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEFILE, config.cookie_jar_file),
534 "CURLOPT_COOKIEFILE");
535 /* now enable saving cookies to a file, but only if the filename is not an empty string,
536 * since writing it would fail */
537 if (*config.cookie_jar_file) {
538 handle_curl_option_return_code(
539 curl_easy_setopt(result.curl_state.curl, CURLOPT_COOKIEJAR, config.cookie_jar_file),
540 "CURLOPT_COOKIEJAR");
541 }
542 }
543
544 result.working_state = working_state;
545
546 return result;
547}
548
549void handle_curl_option_return_code(CURLcode res, const char *option) {
550 if (res != CURLE_OK) {
551 snprintf(msg, DEFAULT_BUFFER_SIZE,
552 _("Error while setting cURL option '%s': cURL returned %d - %s"), option, res,
553 curl_easy_strerror(res));
554 die(STATE_CRITICAL, "HTTP CRITICAL - %s\n", msg);
555 }
556}
557
558char *get_header_value(const struct phr_header *headers, const size_t nof_headers,
559 const char *header) {
560 for (size_t i = 0; i < nof_headers; i++) {
561 if (headers[i].name != NULL &&
562 strncasecmp(header, headers[i].name, max(headers[i].name_len, 4)) == 0) {
563 return strndup(headers[i].value, headers[i].value_len);
564 }
565 }
566 return NULL;
567}
568
569check_curl_working_state check_curl_working_state_init() {
570 check_curl_working_state result = {
571 .server_address = NULL,
572 .server_url = DEFAULT_SERVER_URL,
573 .host_name = NULL,
574 .http_method = NULL,
575 .http_post_data = NULL,
576 .virtualPort = 0,
577 .serverPort = HTTP_PORT,
578 .use_ssl = false,
579 .no_body = false,
580 };
581 return result;
582}
583
584check_curl_config check_curl_config_init() {
585 check_curl_config tmp = {
586 .initial_config = check_curl_working_state_init(),
587
588 .curl_config =
589 {
590 .automatic_decompression = false,
591 .socket_timeout = DEFAULT_SOCKET_TIMEOUT,
592 .haproxy_protocol = false,
593 .sin_family = AF_UNSPEC,
594 .curl_http_version = CURL_HTTP_VERSION_NONE,
595 .http_opt_headers = NULL,
596 .http_opt_headers_count = 0,
597 .ssl_version = CURL_SSLVERSION_DEFAULT,
598 .client_cert = NULL,
599 .client_privkey = NULL,
600 .ca_cert = NULL,
601 .verify_peer_and_host = false,
602 .user_agent = {'\0'},
603 .proxy_auth = "",
604 .user_auth = "",
605 .http_content_type = NULL,
606 .cookie_jar_file = NULL,
607 },
608 .max_depth = DEFAULT_MAX_REDIRS,
609 .followmethod = FOLLOW_HTTP_CURL,
610 .followsticky = STICKY_NONE,
611
612 .maximum_age = -1,
613 .regexp = {},
614 .compiled_regex = {},
615 .state_regex = STATE_CRITICAL,
616 .invert_regex = false,
617 .check_cert = false,
618 .continue_after_check_cert = false,
619 .days_till_exp_warn = 0,
620 .days_till_exp_crit = 0,
621 .thlds = NULL,
622 .min_page_len = 0,
623 .max_page_len = 0,
624 .server_expect =
625 {
626 .string = HTTP_EXPECT,
627 .is_present = false,
628 },
629 .string_expect = "",
630 .header_expect = "",
631 .on_redirect_result_state = STATE_OK,
632 .on_redirect_dependent = true,
633
634 .show_extended_perfdata = false,
635 .show_body = false,
636 .display_html = false,
637 };
638
639 snprintf(tmp.curl_config.user_agent, DEFAULT_BUFFER_SIZE, "%s/v%s (monitoring-plugins %s, %s)",
640 "check_curl", NP_VERSION, VERSION, curl_version());
641
642 return tmp;
643}
644
645/* TODO: is there a better way in libcurl to check for the SSL library? */
646curlhelp_ssl_library curlhelp_get_ssl_library(void) {
647 curlhelp_ssl_library ssl_library = CURLHELP_SSL_LIBRARY_UNKNOWN;
648
649 curl_version_info_data *version_data = curl_version_info(CURLVERSION_NOW);
650 if (version_data == NULL) {
651 return CURLHELP_SSL_LIBRARY_UNKNOWN;
652 }
653
654 char *ssl_version = strdup(version_data->ssl_version);
655 if (ssl_version == NULL) {
656 return CURLHELP_SSL_LIBRARY_UNKNOWN;
657 }
658
659 char *library = strtok(ssl_version, "/");
660 if (library == NULL) {
661 return CURLHELP_SSL_LIBRARY_UNKNOWN;
662 }
663
664 if (strcmp(library, "OpenSSL") == 0) {
665 ssl_library = CURLHELP_SSL_LIBRARY_OPENSSL;
666 } else if (strcmp(library, "LibreSSL") == 0) {
667 ssl_library = CURLHELP_SSL_LIBRARY_LIBRESSL;
668 } else if (strcmp(library, "GnuTLS") == 0) {
669 ssl_library = CURLHELP_SSL_LIBRARY_GNUTLS;
670 } else if (strcmp(library, "NSS") == 0) {
671 ssl_library = CURLHELP_SSL_LIBRARY_NSS;
672 }
673
674 if (verbose >= 2) {
675 printf("* SSL library string is : %s %s (%d)\n", version_data->ssl_version, library,
676 ssl_library);
677 }
678
679 free(ssl_version);
680
681 return ssl_library;
682}
683
684const char *curlhelp_get_ssl_library_string(const curlhelp_ssl_library ssl_library) {
685 switch (ssl_library) {
686 case CURLHELP_SSL_LIBRARY_OPENSSL:
687 return "OpenSSL";
688 case CURLHELP_SSL_LIBRARY_LIBRESSL:
689 return "LibreSSL";
690 case CURLHELP_SSL_LIBRARY_GNUTLS:
691 return "GnuTLS";
692 case CURLHELP_SSL_LIBRARY_NSS:
693 return "NSS";
694 case CURLHELP_SSL_LIBRARY_UNKNOWN:
695 default:
696 return "unknown";
697 }
698}
699
700size_t get_content_length(const curlhelp_write_curlbuf *header_buf,
701 const curlhelp_write_curlbuf *body_buf) {
702 struct phr_header headers[255];
703 size_t nof_headers = 255;
704 size_t msglen;
705 curlhelp_statusline status_line;
706 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
707 &status_line.http_minor, &status_line.http_code, &status_line.msg,
708 &msglen, headers, &nof_headers, 0);
709
710 if (res == -1) {
711 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
712 }
713
714 char *content_length_s = get_header_value(headers, nof_headers, "content-length");
715 if (!content_length_s) {
716 return header_buf->buflen + body_buf->buflen;
717 }
718
719 content_length_s += strspn(content_length_s, " \t");
720 size_t content_length = atoi(content_length_s);
721 if (content_length != body_buf->buflen) {
722 /* TODO: should we warn if the actual and the reported body length don't match? */
723 }
724
725 if (content_length_s) {
726 free(content_length_s);
727 }
728
729 return header_buf->buflen + body_buf->buflen;
730}
731
732mp_state_enum check_document_dates(const curlhelp_write_curlbuf *header_buf,
733 const char msg[static DEFAULT_BUFFER_SIZE],
734 const int maximum_age) {
735 struct phr_header headers[255];
736 size_t nof_headers = 255;
737 curlhelp_statusline status_line;
738 size_t msglen;
739 int res = phr_parse_response(header_buf->buf, header_buf->buflen, &status_line.http_major,
740 &status_line.http_minor, &status_line.http_code, &status_line.msg,
741 &msglen, headers, &nof_headers, 0);
742
743 if (res == -1) {
744 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Failed to parse Response\n"));
745 }
746
747 char *server_date = get_header_value(headers, nof_headers, "date");
748 char *document_date = get_header_value(headers, nof_headers, "last-modified");
749
750 mp_state_enum date_result = STATE_OK;
751 if (!server_date || !*server_date) {
752 char tmp[DEFAULT_BUFFER_SIZE];
753
754 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sServer date unknown, "), msg);
755 strcpy(msg, tmp);
756
757 date_result = max_state_alt(STATE_UNKNOWN, date_result);
758
759 } else if (!document_date || !*document_date) {
760 char tmp[DEFAULT_BUFFER_SIZE];
761
762 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sDocument modification date unknown, "), msg);
763 strcpy(msg, tmp);
764
765 date_result = max_state_alt(STATE_CRITICAL, date_result);
766
767 } else {
768 time_t srv_data = curl_getdate(server_date, NULL);
769 time_t doc_data = curl_getdate(document_date, NULL);
770 if (verbose >= 2) {
771 printf("* server date: '%s' (%d), doc_date: '%s' (%d)\n", server_date, (int)srv_data,
772 document_date, (int)doc_data);
773 }
774 if (srv_data <= 0) {
775 char tmp[DEFAULT_BUFFER_SIZE];
776
777 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sServer date \"%100s\" unparsable, "), msg,
778 server_date);
779 strcpy(msg, tmp);
780
781 date_result = max_state_alt(STATE_CRITICAL, date_result);
782 } else if (doc_data <= 0) {
783 char tmp[DEFAULT_BUFFER_SIZE];
784
785 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sDocument date \"%100s\" unparsable, "), msg,
786 document_date);
787 strcpy(msg, tmp);
788
789 date_result = max_state_alt(STATE_CRITICAL, date_result);
790 } else if (doc_data > srv_data + 30) {
791 char tmp[DEFAULT_BUFFER_SIZE];
792
793 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sDocument is %d seconds in the future, "), msg,
794 (int)doc_data - (int)srv_data);
795 strcpy(msg, tmp);
796
797 date_result = max_state_alt(STATE_CRITICAL, date_result);
798 } else if (doc_data < srv_data - maximum_age) {
799 time_t last_modified = (srv_data - doc_data);
800 if (last_modified > (60 * 60 * 24 * 2)) {
801 char tmp[DEFAULT_BUFFER_SIZE];
802
803 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sLast modified %.1f days ago, "), msg,
804 ((float)last_modified) / (60 * 60 * 24));
805 strcpy(msg, tmp);
806
807 date_result = max_state_alt(STATE_CRITICAL, date_result);
808 } else {
809 char tmp[DEFAULT_BUFFER_SIZE];
810
811 snprintf(tmp, DEFAULT_BUFFER_SIZE, _("%sLast modified %ld:%02ld:%02ld ago, "), msg,
812 last_modified / (60 * 60), (last_modified / 60) % 60, last_modified % 60);
813 strcpy(msg, tmp);
814
815 date_result = max_state_alt(STATE_CRITICAL, date_result);
816 }
817 }
818 }
819
820 if (server_date) {
821 free(server_date);
822 }
823 if (document_date) {
824 free(document_date);
825 }
826
827 return date_result;
828}
829
830void curlhelp_free_statusline(curlhelp_statusline *status_line) { free(status_line->first_line); }
831
832int curlhelp_parse_statusline(const char *buf, curlhelp_statusline *status_line) {
833 /* find last start of a new header */
834 const char *start = strrstr2(buf, "\r\nHTTP/");
835 if (start != NULL) {
836 start += 2;
837 buf = start;
838 }
839
840 char *first_line_end = strstr(buf, "\r\n");
841 if (first_line_end == NULL) {
842 return -1;
843 }
844
845 size_t first_line_len = (size_t)(first_line_end - buf);
846 status_line->first_line = (char *)calloc(first_line_len + 1, sizeof(char));
847 if (status_line->first_line == NULL) {
848 return -1;
849 }
850 memcpy(status_line->first_line, buf, first_line_len);
851 status_line->first_line[first_line_len] = '\0';
852 char *first_line_buf = strdup(status_line->first_line);
853
854 /* protocol and version: "HTTP/x.x" SP or "HTTP/2" SP */
855 char *temp_string = strtok(first_line_buf, "/");
856 if (temp_string == NULL) {
857 free(first_line_buf);
858 return -1;
859 }
860 if (strcmp(temp_string, "HTTP") != 0) {
861 free(first_line_buf);
862 return -1;
863 }
864
865 temp_string = strtok(NULL, " ");
866 if (temp_string == NULL) {
867 free(first_line_buf);
868 return -1;
869 }
870
871 char *temp_string_2;
872 if (strchr(temp_string, '.') != NULL) {
873
874 /* HTTP 1.x case */
875 strtok(temp_string, ".");
876 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
877 if (*temp_string_2 != '\0') {
878 free(first_line_buf);
879 return -1;
880 }
881 strtok(NULL, " ");
882 status_line->http_minor = (int)strtol(temp_string, &temp_string_2, 10);
883 if (*temp_string_2 != '\0') {
884 free(first_line_buf);
885 return -1;
886 }
887 temp_string += 4; /* 1.x SP */
888 } else {
889 /* HTTP 2 case */
890 status_line->http_major = (int)strtol(temp_string, &temp_string_2, 10);
891 status_line->http_minor = 0;
892 temp_string += 2; /* 2 SP */
893 }
894
895 /* status code: "404" or "404.1", then SP */
896 temp_string = strtok(temp_string, " ");
897 if (temp_string == NULL) {
898 free(first_line_buf);
899 return -1;
900 }
901 if (strchr(temp_string, '.') != NULL) {
902 char *ppp;
903 ppp = strtok(temp_string, ".");
904 status_line->http_code = (int)strtol(ppp, &temp_string_2, 10);
905 if (*temp_string_2 != '\0') {
906 free(first_line_buf);
907 return -1;
908 }
909 ppp = strtok(NULL, "");
910 status_line->http_subcode = (int)strtol(ppp, &temp_string_2, 10);
911 if (*temp_string_2 != '\0') {
912 free(first_line_buf);
913 return -1;
914 }
915 temp_string += 6; /* 400.1 SP */
916 } else {
917 status_line->http_code = (int)strtol(temp_string, &temp_string_2, 10);
918 status_line->http_subcode = -1;
919 if (*temp_string_2 != '\0') {
920 free(first_line_buf);
921 return -1;
922 }
923 temp_string += 4; /* 400 SP */
924 }
925
926 /* Human readable message: "Not Found" CRLF */
927
928 temp_string = strtok(temp_string, "");
929 if (temp_string == NULL) {
930 status_line->msg = "";
931 return 0;
932 }
933 status_line->msg = status_line->first_line + (temp_string - first_line_buf);
934 free(first_line_buf);
935
936 return 0;
937}
938
939/* TODO: where to put this, it's actually part of sstrings2 (logically)?
940 */
941const char *strrstr2(const char *haystack, const char *needle) {
942 if (haystack == NULL || needle == NULL) {
943 return NULL;
944 }
945
946 if (haystack[0] == '\0' || needle[0] == '\0') {
947 return NULL;
948 }
949
950 int counter = 0;
951 const char *prev_pos = NULL;
952 const char *pos = haystack;
953 size_t len = strlen(needle);
954 for (;;) {
955 pos = strstr(pos, needle);
956 if (pos == NULL) {
957 if (counter == 0) {
958 return NULL;
959 }
960 return prev_pos;
961 }
962 counter++;
963 prev_pos = pos;
964 pos += len;
965 if (*pos == '\0') {
966 return prev_pos;
967 }
968 }
969}
970
971void curlhelp_freereadbuffer(curlhelp_read_curlbuf *buf) {
972 free(buf->buf);
973 buf->buf = NULL;
974}
975
976void curlhelp_freewritebuffer(curlhelp_write_curlbuf *buf) {
977 free(buf->buf);
978 buf->buf = NULL;
979}
980
981int curlhelp_initreadbuffer(curlhelp_read_curlbuf **buf, const char *data, size_t datalen) {
982 if ((*buf = calloc(1, sizeof(curlhelp_read_curlbuf))) == NULL) {
983 return 1;
984 }
985
986 (*buf)->buflen = datalen;
987 (*buf)->buf = (char *)calloc((*buf)->buflen, sizeof(char));
988 if ((*buf)->buf == NULL) {
989 return -1;
990 }
991 memcpy((*buf)->buf, data, datalen);
992 (*buf)->pos = 0;
993 return 0;
994}
995
996size_t curlhelp_buffer_read_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
997 curlhelp_read_curlbuf *buf = (curlhelp_read_curlbuf *)stream;
998
999 size_t minimalSize = min(nmemb * size, buf->buflen - buf->pos);
1000
1001 memcpy(buffer, buf->buf + buf->pos, minimalSize);
1002 buf->pos += minimalSize;
1003
1004 return minimalSize;
1005}
1006
1007int curlhelp_initwritebuffer(curlhelp_write_curlbuf **buf) {
1008 if ((*buf = calloc(1, sizeof(curlhelp_write_curlbuf))) == NULL) {
1009 return 1;
1010 }
1011 (*buf)->bufsize = DEFAULT_BUFFER_SIZE * sizeof(char);
1012 (*buf)->buflen = 0;
1013 (*buf)->buf = (char *)calloc((*buf)->bufsize, sizeof(char));
1014 if ((*buf)->buf == NULL) {
1015 return -1;
1016 }
1017 return 0;
1018}
1019
1020size_t curlhelp_buffer_write_callback(void *buffer, size_t size, size_t nmemb, void *stream) {
1021 curlhelp_write_curlbuf *buf = (curlhelp_write_curlbuf *)stream;
1022
1023 while (buf->bufsize < buf->buflen + size * nmemb + 1) {
1024 buf->bufsize = buf->bufsize * 2;
1025 buf->buf = (char *)realloc(buf->buf, buf->bufsize);
1026 if (buf->buf == NULL) {
1027 fprintf(stderr, "malloc failed (%d) %s\n", errno, strerror(errno));
1028 return 0;
1029 }
1030 }
1031
1032 memcpy(buf->buf + buf->buflen, buffer, size * nmemb);
1033 buf->buflen += size * nmemb;
1034 buf->buf[buf->buflen] = '\0';
1035
1036 return size * nmemb;
1037}
1038
1039void cleanup(check_curl_global_state global_state) {
1040 if (global_state.status_line_initialized) {
1041 curlhelp_free_statusline(global_state.status_line);
1042 }
1043 global_state.status_line_initialized = false;
1044
1045 if (global_state.curl_easy_initialized) {
1046 curl_easy_cleanup(global_state.curl);
1047 }
1048 global_state.curl_easy_initialized = false;
1049
1050 if (global_state.curl_global_initialized) {
1051 curl_global_cleanup();
1052 }
1053 global_state.curl_global_initialized = false;
1054
1055 if (global_state.body_buf_initialized) {
1056 curlhelp_freewritebuffer(global_state.body_buf);
1057 }
1058 global_state.body_buf_initialized = false;
1059
1060 if (global_state.header_buf_initialized) {
1061 curlhelp_freewritebuffer(global_state.header_buf);
1062 }
1063 global_state.header_buf_initialized = false;
1064
1065 if (global_state.put_buf_initialized) {
1066 curlhelp_freereadbuffer(global_state.put_buf);
1067 }
1068 global_state.put_buf_initialized = false;
1069
1070 if (global_state.header_list) {
1071 curl_slist_free_all(global_state.header_list);
1072 }
1073
1074 if (global_state.host) {
1075 curl_slist_free_all(global_state.host);
1076 }
1077}
1078
1079int lookup_host(const char *host, char *buf, size_t buflen, sa_family_t addr_family) {
1080 struct addrinfo hints = {
1081 .ai_family = addr_family,
1082 .ai_socktype = SOCK_STREAM,
1083 .ai_flags = AI_CANONNAME,
1084 };
1085
1086 struct addrinfo *result;
1087 int errcode = getaddrinfo(host, NULL, &hints, &result);
1088 if (errcode != 0) {
1089 return errcode;
1090 }
1091
1092 strcpy(buf, "");
1093 struct addrinfo *res = result;
1094
1095 size_t buflen_remaining = buflen - 1;
1096 size_t addrstr_len;
1097 char addrstr[100];
1098 void *ptr = {0};
1099 while (res) {
1100 switch (res->ai_family) {
1101 case AF_INET:
1102 ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
1103 break;
1104 case AF_INET6:
1105 ptr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
1106 break;
1107 }
1108
1109 inet_ntop(res->ai_family, ptr, addrstr, 100);
1110 if (verbose >= 1) {
1111 printf("* getaddrinfo IPv%d address: %s\n", res->ai_family == PF_INET6 ? 6 : 4,
1112 addrstr);
1113 }
1114
1115 // Append all IPs to buf as a comma-separated string
1116 addrstr_len = strlen(addrstr);
1117 if (buflen_remaining > addrstr_len + 1) {
1118 if (buf[0] != '\0') {
1119 strncat(buf, ",", buflen_remaining);
1120 buflen_remaining -= 1;
1121 }
1122 strncat(buf, addrstr, buflen_remaining);
1123 buflen_remaining -= addrstr_len;
1124 }
1125
1126 res = res->ai_next;
1127 }
1128
1129 freeaddrinfo(result);
1130
1131 return 0;
1132}
1133
1134/* Checks if the server 'reply' is one of the expected 'statuscodes' */
1135bool expected_statuscode(const char *reply, const char *statuscodes) {
1136 char *expected;
1137
1138 if ((expected = strdup(statuscodes)) == NULL) {
1139 die(STATE_UNKNOWN, _("HTTP UNKNOWN - Memory allocation error\n"));
1140 }
1141
1142 bool result = false;
1143 for (char *code = strtok(expected, ","); code != NULL; code = strtok(NULL, ",")) {
1144 if (strstr(reply, code) != NULL) {
1145 result = true;
1146 break;
1147 }
1148 }
1149
1150 free(expected);
1151 return result;
1152}
1153
1154/* returns a string "HTTP/1.x" or "HTTP/2" */
1155char *string_statuscode(int major, int minor) {
1156 static char buf[10];
1157
1158 switch (major) {
1159 case 1:
1160 snprintf(buf, sizeof(buf), "HTTP/%d.%d", major, minor);
1161 break;
1162 case 2:
1163 case 3:
1164 snprintf(buf, sizeof(buf), "HTTP/%d", major);
1165 break;
1166 default:
1167 /* assuming here HTTP/N with N>=4 */
1168 snprintf(buf, sizeof(buf), "HTTP/%d", major);
1169 break;
1170 }
1171
1172 return buf;
1173}
1174
1175char *perfd_time(double elapsed_time, thresholds *thlds, long socket_timeout) {
1176 return fperfdata("time", elapsed_time, "s", (thlds->warning != NULL),
1177 thlds->warning ? thlds->warning->end : 0, (thlds->critical != NULL),
1178 thlds->critical ? thlds->critical->end : 0, true, 0, true, socket_timeout);
1179}
1180
1181char *perfd_time_connect(double elapsed_time_connect, long socket_timeout) {
1182 return fperfdata("time_connect", elapsed_time_connect, "s", false, 0, false, 0, false, 0, true,
1183 socket_timeout);
1184}
1185
1186char *perfd_time_ssl(double elapsed_time_ssl, long socket_timeout) {
1187 return fperfdata("time_ssl", elapsed_time_ssl, "s", false, 0, false, 0, false, 0, true,
1188 socket_timeout);
1189}
1190
1191char *perfd_time_headers(double elapsed_time_headers, long socket_timeout) {
1192 return fperfdata("time_headers", elapsed_time_headers, "s", false, 0, false, 0, false, 0, true,
1193 socket_timeout);
1194}
1195
1196char *perfd_time_firstbyte(double elapsed_time_firstbyte, long socket_timeout) {
1197 return fperfdata("time_firstbyte", elapsed_time_firstbyte, "s", false, 0, false, 0, false, 0,
1198 true, socket_timeout);
1199}
1200
1201char *perfd_time_transfer(double elapsed_time_transfer, long socket_timeout) {
1202 return fperfdata("time_transfer", elapsed_time_transfer, "s", false, 0, false, 0, false, 0,
1203 true, socket_timeout);
1204}
1205
1206char *perfd_size(size_t page_len, int min_page_len) {
1207 return perfdata("size", page_len, "B", (min_page_len > 0), min_page_len, (min_page_len > 0), 0,
1208 true, 0, false, 0);
1209}
1210
1211/* check whether a file exists */
1212void test_file(char *path) {
1213 if (access(path, R_OK) == 0) {
1214 return;
1215 }
1216 usage2(_("file does not exist or is not readable"), path);
1217}
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..0f43ab90
--- /dev/null
+++ b/plugins/check_curl.d/check_curl_helpers.h
@@ -0,0 +1,125 @@
1#include "./config.h"
2#include <curl/curl.h>
3#include "../picohttpparser/picohttpparser.h"
4// #include "curl/easy.h"
5
6/* for buffers for header and body */
7typedef struct {
8 size_t buflen;
9 size_t bufsize;
10 char *buf;
11} curlhelp_write_curlbuf;
12
13/* for buffering the data sent in PUT */
14typedef struct {
15 size_t buflen;
16 off_t pos;
17 char *buf;
18} curlhelp_read_curlbuf;
19
20/* for parsing the HTTP status line */
21typedef struct {
22 int http_major; /* major version of the protocol, always 1 (HTTP/0.9
23 * never reached the big internet most likely) */
24 int http_minor; /* minor version of the protocol, usually 0 or 1 */
25 int http_code; /* HTTP return code as in RFC 2145 */
26 int http_subcode; /* Microsoft IIS extension, HTTP subcodes, see
27 * http://support.microsoft.com/kb/318380/en-us */
28 const char *msg; /* the human readable message */
29 char *first_line; /* a copy of the first line */
30} curlhelp_statusline;
31
32typedef struct {
33 bool curl_global_initialized;
34 bool curl_easy_initialized;
35
36 bool body_buf_initialized;
37 curlhelp_write_curlbuf *body_buf;
38
39 bool header_buf_initialized;
40 curlhelp_write_curlbuf *header_buf;
41
42 bool status_line_initialized;
43 curlhelp_statusline *status_line;
44
45 bool put_buf_initialized;
46 curlhelp_read_curlbuf *put_buf;
47
48 CURL *curl;
49
50 struct curl_slist *header_list;
51 struct curl_slist *host;
52} check_curl_global_state;
53
54/* to know the underlying SSL library used by libcurl */
55typedef enum curlhelp_ssl_library {
56 CURLHELP_SSL_LIBRARY_UNKNOWN,
57 CURLHELP_SSL_LIBRARY_OPENSSL,
58 CURLHELP_SSL_LIBRARY_LIBRESSL,
59 CURLHELP_SSL_LIBRARY_GNUTLS,
60 CURLHELP_SSL_LIBRARY_NSS
61} curlhelp_ssl_library;
62
63#define MAKE_LIBCURL_VERSION(major, minor, patch) ((major) * 0x10000 + (minor) * 0x100 + (patch))
64
65typedef struct {
66 int errorcode;
67 check_curl_global_state curl_state;
68 check_curl_working_state working_state;
69} check_curl_configure_curl_wrapper;
70
71check_curl_configure_curl_wrapper check_curl_configure_curl(check_curl_static_curl_config config,
72 check_curl_working_state working_state,
73 bool check_cert,
74 bool on_redirect_dependent,
75 int follow_method, int max_depth);
76
77void handle_curl_option_return_code(CURLcode res, const char *option);
78
79int curlhelp_initwritebuffer(curlhelp_write_curlbuf **buf);
80size_t curlhelp_buffer_write_callback(void * /*buffer*/, size_t /*size*/, size_t /*nmemb*/,
81 void * /*stream*/);
82void curlhelp_freewritebuffer(curlhelp_write_curlbuf * /*buf*/);
83
84int curlhelp_initreadbuffer(curlhelp_read_curlbuf **buf, const char * /*data*/, size_t /*datalen*/);
85size_t curlhelp_buffer_read_callback(void * /*buffer*/, size_t /*size*/, size_t /*nmemb*/,
86 void * /*stream*/);
87void curlhelp_freereadbuffer(curlhelp_read_curlbuf * /*buf*/);
88
89curlhelp_ssl_library curlhelp_get_ssl_library(void);
90const char *curlhelp_get_ssl_library_string(curlhelp_ssl_library /*ssl_library*/);
91
92typedef union {
93 struct curl_slist *to_info;
94 struct curl_certinfo *to_certinfo;
95} cert_ptr_union;
96int net_noopenssl_check_certificate(cert_ptr_union *, int, int);
97
98int curlhelp_parse_statusline(const char * /*buf*/, curlhelp_statusline * /*status_line*/);
99void curlhelp_free_statusline(curlhelp_statusline * /*status_line*/);
100
101char *get_header_value(const struct phr_header *headers, size_t nof_headers, const char *header);
102mp_state_enum check_document_dates(const curlhelp_write_curlbuf * /*header_buf*/,
103 const char msg[static DEFAULT_BUFFER_SIZE], int /*maximum_age*/);
104size_t get_content_length(const curlhelp_write_curlbuf *header_buf,
105 const curlhelp_write_curlbuf *body_buf);
106int lookup_host(const char *host, char *buf, size_t buflen, sa_family_t addr_family);
107CURLcode sslctxfun(CURL *curl, SSL_CTX *sslctx, void *parm);
108
109#define INET_ADDR_MAX_SIZE INET6_ADDRSTRLEN
110const char *strrstr2(const char *haystack, const char *needle);
111
112void cleanup(check_curl_global_state global_state);
113
114bool expected_statuscode(const char *reply, const char *statuscodes);
115char *string_statuscode(int major, int minor);
116
117char *perfd_time(double elapsed_time, thresholds * /*thlds*/, long /*socket_timeout*/);
118char *perfd_time_connect(double elapsed_time_connect, long /*socket_timeout*/);
119char *perfd_time_ssl(double elapsed_time_ssl, long /*socket_timeout*/);
120char *perfd_time_firstbyte(double elapsed_time_firstbyte, long /*socket_timeout*/);
121char *perfd_time_headers(double elapsed_time_headers, long /*socket_timeout*/);
122char *perfd_time_transfer(double elapsed_time_transfer, long /*socket_timeout*/);
123char *perfd_size(size_t page_len, int /*min_page_len*/);
124
125void test_file(char *path);
diff --git a/plugins/check_curl.d/config.h b/plugins/check_curl.d/config.h
index be25d1bb..a4b1eecf 100644
--- a/plugins/check_curl.d/config.h
+++ b/plugins/check_curl.d/config.h
@@ -49,20 +49,7 @@ typedef struct {
49 bool no_body; 49 bool no_body;
50} check_curl_working_state; 50} check_curl_working_state;
51 51
52check_curl_working_state check_curl_working_state_init() { 52check_curl_working_state check_curl_working_state_init();
53 check_curl_working_state result = {
54 .server_address = NULL,
55 .server_url = DEFAULT_SERVER_URL,
56 .host_name = NULL,
57 .http_method = NULL,
58 .http_post_data = NULL,
59 .virtualPort = 0,
60 .serverPort = HTTP_PORT,
61 .use_ssl = false,
62 .no_body = false,
63 };
64 return result;
65}
66 53
67typedef struct { 54typedef struct {
68 bool automatic_decompression; 55 bool automatic_decompression;
@@ -123,63 +110,4 @@ typedef struct {
123 bool display_html; 110 bool display_html;
124} check_curl_config; 111} check_curl_config;
125 112
126check_curl_config check_curl_config_init() { 113check_curl_config check_curl_config_init();
127 check_curl_config tmp = {
128 .initial_config = check_curl_working_state_init(),
129
130 .curl_config =
131 {
132 .automatic_decompression = false,
133 .socket_timeout = DEFAULT_SOCKET_TIMEOUT,
134 .haproxy_protocol = false,
135 .sin_family = AF_UNSPEC,
136 .curl_http_version = CURL_HTTP_VERSION_NONE,
137 .http_opt_headers = NULL,
138 .http_opt_headers_count = 0,
139 .ssl_version = CURL_SSLVERSION_DEFAULT,
140 .client_cert = NULL,
141 .client_privkey = NULL,
142 .ca_cert = NULL,
143 .verify_peer_and_host = false,
144 .user_agent = {'\0'},
145 .proxy_auth = "",
146 .user_auth = "",
147 .http_content_type = NULL,
148 .cookie_jar_file = NULL,
149 },
150 .max_depth = DEFAULT_MAX_REDIRS,
151 .followmethod = FOLLOW_HTTP_CURL,
152 .followsticky = STICKY_NONE,
153
154 .maximum_age = -1,
155 .regexp = {},
156 .compiled_regex = {},
157 .state_regex = STATE_CRITICAL,
158 .invert_regex = false,
159 .check_cert = false,
160 .continue_after_check_cert = false,
161 .days_till_exp_warn = 0,
162 .days_till_exp_crit = 0,
163 .thlds = NULL,
164 .min_page_len = 0,
165 .max_page_len = 0,
166 .server_expect =
167 {
168 .string = HTTP_EXPECT,
169 .is_present = false,
170 },
171 .string_expect = "",
172 .header_expect = "",
173 .on_redirect_result_state = STATE_OK,
174 .on_redirect_dependent = true,
175
176 .show_extended_perfdata = false,
177 .show_body = false,
178 .display_html = false,
179 };
180
181 snprintf(tmp.curl_config.user_agent, DEFAULT_BUFFER_SIZE, "%s/v%s (monitoring-plugins %s, %s)",
182 "check_curl", NP_VERSION, VERSION, curl_version());
183
184 return tmp;
185}