[monitoring-plugins] Refactor check_tcp and implement new output format

Lorenz Kästle git at monitoring-plugins.org
Thu Mar 13 15:20:13 CET 2025


 Module: monitoring-plugins
 Branch: master
 Commit: 554bf3e5256f5489aed0cd56f0c600bcb281a7f5
 Author: Lorenz Kästle <12514511+RincewindsHat at users.noreply.github.com>
   Date: Tue Mar  4 11:02:33 2025 +0100
    URL: https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=554bf3e5

Refactor check_tcp and implement new output format

---

 plugins/check_tcp.c          | 742 ++++++++++++++++++++++++-------------------
 plugins/check_tcp.d/config.h |  78 +++++
 2 files changed, 493 insertions(+), 327 deletions(-)

diff --git a/plugins/check_tcp.c b/plugins/check_tcp.c
index 49ad096c..f93152e5 100644
--- a/plugins/check_tcp.c
+++ b/plugins/check_tcp.c
@@ -3,7 +3,7 @@
  * Monitoring check_tcp plugin
  *
  * License: GPL
- * Copyright (c) 1999-2024 Monitoring Plugins Development Team
+ * Copyright (c) 1999-2025 Monitoring Plugins Development Team
  *
  * Description:
  *
@@ -28,75 +28,63 @@
  *****************************************************************************/
 
 /* progname "check_tcp" changes depending on symlink called */
+#include "states.h"
 char *progname;
-const char *copyright = "1999-2024";
+const char *copyright = "1999-2025";
 const char *email = "devel at monitoring-plugins.org";
 
-#include "common.h"
-#include "netutils.h"
-#include "utils.h"
-#include "utils_tcp.h"
+#include "./common.h"
+#include "./netutils.h"
+#include "./utils.h"
+#include "./check_tcp.d/config.h"
 
+#include <sys/types.h>
 #include <ctype.h>
 #include <sys/select.h>
 
+ssize_t my_recv(char *buf, size_t len) {
 #ifdef HAVE_SSL
-static bool check_cert = false;
-static int days_till_exp_warn, days_till_exp_crit;
-#	define my_recv(buf, len) ((flags & FLAG_SSL) ? np_net_ssl_read(buf, len) : read(sd, buf, len))
-#	define my_send(buf, len) ((flags & FLAG_SSL) ? np_net_ssl_write(buf, len) : send(sd, buf, len, 0))
+	return np_net_ssl_read(buf, (int)len);
 #else
-#	define my_recv(buf, len) read(sd, buf, len)
-#	define my_send(buf, len) send(sd, buf, len, 0)
-#endif
+	return read(socket_descriptor, buf, len);
+#endif // HAVE_SSL
+}
+
+ssize_t my_send(char *buf, size_t len) {
+#ifdef HAVE_SSL
+	return np_net_ssl_write(buf, (int)len);
+#else
+	return write(socket_descriptor, buf, len);
+#endif // HAVE_SSL
+}
+
+typedef struct process_arguments_wrapper {
+	int errorcode;
+	check_tcp_config config;
+} process_arguments_wrapper;
 
 /* int my_recv(char *, size_t); */
-static int process_arguments(int /*argc*/, char ** /*argv*/);
-static void print_help(void);
+static process_arguments_wrapper process_arguments(int /*argc*/, char ** /*argv*/, check_tcp_config /*config*/);
+void print_help(const char *service);
 void print_usage(void);
 
-#define EXPECT server_expect[0]
-static char *SERVICE = "TCP";
-static char *SEND = NULL;
-static char *QUIT = NULL;
-static int PROTOCOL = IPPROTO_TCP; /* most common is default */
-static int PORT = 0;
-static int READ_TIMEOUT = 2;
-
-static int server_port = 0;
-static char *server_address = NULL;
-static bool host_specified = false;
-static char *server_send = NULL;
-static char *server_quit = NULL;
-static char **server_expect;
-static size_t server_expect_count = 0;
-static ssize_t maxbytes = 0;
-static char **warn_codes = NULL;
-static size_t warn_codes_count = 0;
-static char **crit_codes = NULL;
-static size_t crit_codes_count = 0;
-static unsigned int delay = 0;
-static double warning_time = 0;
-static double critical_time = 0;
-static double elapsed_time = 0;
-static long microsec;
-static int sd = 0;
-#define MAXBUF 1024
-static char buffer[MAXBUF];
-static int expect_mismatch_state = STATE_WARNING;
-static int match_flags = NP_MATCH_EXACT;
+int verbosity = 0;
 
-#ifdef HAVE_SSL
-static char *sni = NULL;
-static bool sni_specified = false;
-#endif
+static const int READ_TIMEOUT = 2;
 
-#define FLAG_SSL         0x01
-#define FLAG_VERBOSE     0x02
-#define FLAG_TIME_WARN   0x04
-#define FLAG_TIME_CRIT   0x08
-#define FLAG_HIDE_OUTPUT 0x10
-static size_t flags;
+const int MAXBUF = 1024;
+
+const int DEFAULT_FTP_PORT = 21;
+const int DEFAULT_POP_PORT = 110;
+const int DEFAULT_SPOP_PORT = 995;
+const int DEFAULT_SMTP_PORT = 25;
+const int DEFAULT_SSMTP_PORT = 465;
+const int DEFAULT_IMAP_PORT = 143;
+const int DEFAULT_SIMAP_PORT = 993;
+const int DEFAULT_XMPP_C2S_PORT = 5222;
+const int DEFAULT_NNTP_PORT = 119;
+const int DEFAULT_NNTPS_PORT = 563;
+const int DEFAULT_CLAMD_PORT = 3310;
 
 int main(int argc, char **argv) {
 	setlocale(LC_ALL, "");
@@ -105,279 +93,371 @@ int main(int argc, char **argv) {
 
 	/* determine program- and service-name quickly */
 	progname = strrchr(argv[0], '/');
-	if (progname != NULL)
+	if (progname != NULL) {
 		progname++;
-	else
+	} else {
 		progname = argv[0];
+	}
+
+	// Initialize config here with values from above,
+	// might be changed by on disk config or cli commands
+	check_tcp_config config = check_tcp_config_init();
 
 	size_t prog_name_len = strlen(progname);
-	if (prog_name_len > 6 && !memcmp(progname, "check_", 6)) {
-		SERVICE = strdup(progname + 6);
-		for (size_t i = 0; i < prog_name_len - 6; i++)
-			SERVICE[i] = toupper(SERVICE[i]);
+	const size_t prefix_length = strlen("check_");
+
+	if (prog_name_len <= prefix_length) {
+		die(STATE_UNKNOWN, _("Weird progname"));
+	}
+
+	if (!memcmp(progname, "check_", prefix_length)) {
+		config.service = strdup(progname + prefix_length);
+		if (config.service == NULL) {
+			die(STATE_UNKNOWN, _("Allocation failed"));
+		}
+
+		for (size_t i = 0; i < prog_name_len - prefix_length; i++) {
+			config.service[i] = toupper(config.service[i]);
+		}
 	}
 
 	/* set up a reasonable buffer at first (will be realloc()'ed if
 	 * user specifies other options) */
-	server_expect = calloc(2, sizeof(char *));
+	config.server_expect = calloc(2, sizeof(char *));
+
+	if (config.server_expect == NULL) {
+		die(STATE_UNKNOWN, _("Allocation failed"));
+	}
 
 	/* determine defaults for this service's protocol */
-	if (!strncmp(SERVICE, "UDP", 3)) {
-		PROTOCOL = IPPROTO_UDP;
-	} else if (!strncmp(SERVICE, "FTP", 3)) {
-		EXPECT = "220";
-		QUIT = "QUIT\r\n";
-		PORT = 21;
-	} else if (!strncmp(SERVICE, "POP", 3) || !strncmp(SERVICE, "POP3", 4)) {
-		EXPECT = "+OK";
-		QUIT = "QUIT\r\n";
-		PORT = 110;
-	} else if (!strncmp(SERVICE, "SMTP", 4)) {
-		EXPECT = "220";
-		QUIT = "QUIT\r\n";
-		PORT = 25;
-	} else if (!strncmp(SERVICE, "IMAP", 4)) {
-		EXPECT = "* OK";
-		QUIT = "a1 LOGOUT\r\n";
-		PORT = 143;
+	if (!strncmp(config.service, "UDP", strlen("UDP"))) {
+		config.protocol = IPPROTO_UDP;
+	} else if (!strncmp(config.service, "FTP", strlen("FTP"))) {
+		config.server_expect[0] = "220";
+		config.quit = "QUIT\r\n";
+		config.server_port = DEFAULT_FTP_PORT;
+	} else if (!strncmp(config.service, "POP", strlen("POP")) || !strncmp(config.service, "POP3", strlen("POP3"))) {
+		config.server_expect[0] = "+OK";
+		config.quit = "QUIT\r\n";
+		config.server_port = DEFAULT_POP_PORT;
+	} else if (!strncmp(config.service, "SMTP", strlen("SMTP"))) {
+		config.server_expect[0] = "220";
+		config.quit = "QUIT\r\n";
+		config.server_port = DEFAULT_SMTP_PORT;
+	} else if (!strncmp(config.service, "IMAP", strlen("IMAP"))) {
+		config.server_expect[0] = "* OK";
+		config.quit = "a1 LOGOUT\r\n";
+		config.server_port = DEFAULT_IMAP_PORT;
 	}
 #ifdef HAVE_SSL
-	else if (!strncmp(SERVICE, "SIMAP", 5)) {
-		EXPECT = "* OK";
-		QUIT = "a1 LOGOUT\r\n";
-		flags |= FLAG_SSL;
-		PORT = 993;
-	} else if (!strncmp(SERVICE, "SPOP", 4)) {
-		EXPECT = "+OK";
-		QUIT = "QUIT\r\n";
-		flags |= FLAG_SSL;
-		PORT = 995;
-	} else if (!strncmp(SERVICE, "SSMTP", 5)) {
-		EXPECT = "220";
-		QUIT = "QUIT\r\n";
-		flags |= FLAG_SSL;
-		PORT = 465;
-	} else if (!strncmp(SERVICE, "JABBER", 6)) {
-		SEND = "<stream:stream to=\'host\' xmlns=\'jabber:client\' xmlns:stream=\'http://etherx.jabber.org/streams\'>\n";
-		EXPECT = "<?xml version=\'1.0\'";
-		QUIT = "</stream:stream>\n";
-		flags |= FLAG_HIDE_OUTPUT;
-		PORT = 5222;
-	} else if (!strncmp(SERVICE, "NNTPS", 5)) {
-		server_expect_count = 2;
-		server_expect[0] = "200";
-		server_expect[1] = "201";
-		QUIT = "QUIT\r\n";
-		flags |= FLAG_SSL;
-		PORT = 563;
+	else if (!strncmp(config.service, "SIMAP", strlen("SIMAP"))) {
+		config.server_expect[0] = "* OK";
+		config.quit = "a1 LOGOUT\r\n";
+		config.use_tls = true;
+		config.server_port = DEFAULT_SIMAP_PORT;
+	} else if (!strncmp(config.service, "SPOP", strlen("SPOP"))) {
+		config.server_expect[0] = "+OK";
+		config.quit = "QUIT\r\n";
+		config.use_tls = true;
+		config.server_port = DEFAULT_SPOP_PORT;
+	} else if (!strncmp(config.service, "SSMTP", strlen("SSMTP"))) {
+		config.server_expect[0] = "220";
+		config.quit = "QUIT\r\n";
+		config.use_tls = true;
+		config.server_port = DEFAULT_SSMTP_PORT;
+	} else if (!strncmp(config.service, "JABBER", strlen("JABBER"))) {
+		config.send = "<stream:stream to=\'host\' xmlns=\'jabber:client\' xmlns:stream=\'http://etherx.jabber.org/streams\'>\n";
+		config.server_expect[0] = "<?xml version=\'1.0\'";
+		config.quit = "</stream:stream>\n";
+		config.hide_output = true;
+		config.server_port = DEFAULT_XMPP_C2S_PORT;
+	} else if (!strncmp(config.service, "NNTPS", strlen("NNTPS"))) {
+		config.server_expect_count = 2;
+		config.server_expect[0] = "200";
+		config.server_expect[1] = "201";
+		config.quit = "QUIT\r\n";
+		config.use_tls = true;
+		config.server_port = DEFAULT_NNTPS_PORT;
 	}
 #endif
-	else if (!strncmp(SERVICE, "NNTP", 4)) {
-		server_expect_count = 2;
-		server_expect = malloc(sizeof(char *) * server_expect_count);
-		server_expect[0] = strdup("200");
-		server_expect[1] = strdup("201");
-		QUIT = "QUIT\r\n";
-		PORT = 119;
-	} else if (!strncmp(SERVICE, "CLAMD", 5)) {
-		SEND = "PING";
-		EXPECT = "PONG";
-		QUIT = NULL;
-		PORT = 3310;
+	else if (!strncmp(config.service, "NNTP", strlen("NNTP"))) {
+		config.server_expect_count = 2;
+		char **tmp = realloc(config.server_expect, config.server_expect_count * sizeof(char *));
+		if (tmp == NULL) {
+			free(config.server_expect);
+			die(STATE_UNKNOWN, _("Allocation failed"));
+		}
+		config.server_expect = tmp;
+
+		config.server_expect[0] = strdup("200");
+		config.server_expect[1] = strdup("201");
+		config.quit = "QUIT\r\n";
+		config.server_port = DEFAULT_NNTP_PORT;
+	} else if (!strncmp(config.service, "CLAMD", strlen("CLAMD"))) {
+		config.send = "PING";
+		config.server_expect[0] = "PONG";
+		config.quit = NULL;
+		config.server_port = DEFAULT_CLAMD_PORT;
 	}
 	/* fallthrough check, so it's supposed to use reverse matching */
-	else if (strcmp(SERVICE, "TCP"))
+	else if (strcmp(config.service, "TCP")) {
 		usage(_("CRITICAL - Generic check_tcp called with unknown service\n"));
-
-	server_address = "127.0.0.1";
-	server_port = PORT;
-	server_send = SEND;
-	server_quit = QUIT;
-	char *status = NULL;
+	}
 
 	/* Parse extra opts if any */
 	argv = np_extra_opts(&argc, argv, progname);
 
-	if (process_arguments(argc, argv) == ERROR)
+	process_arguments_wrapper paw = process_arguments(argc, argv, config);
+	if (paw.errorcode == ERROR) {
 		usage4(_("Could not parse arguments"));
+	}
+
+	config = paw.config;
 
-	if (flags & FLAG_VERBOSE) {
-		printf("Using service %s\n", SERVICE);
-		printf("Port: %d\n", server_port);
-		printf("flags: 0x%x\n", (int)flags);
+	if (verbosity > 0) {
+		printf("Using service %s\n", config.service);
+		printf("Port: %d\n", config.server_port);
 	}
 
-	if (EXPECT && !server_expect_count)
-		server_expect_count++;
+	if ((config.server_expect_count == 0) && config.server_expect[0]) {
+		config.server_expect_count++;
+	}
 
-	if (PROTOCOL == IPPROTO_UDP && !(server_expect_count && server_send)) {
+	if (config.protocol == IPPROTO_UDP && !(config.server_expect_count && config.send)) {
 		usage(_("With UDP checks, a send/expect string must be specified."));
 	}
 
+	// Initialize check stuff before setting timers
+	mp_check overall = mp_check_init();
+
 	/* set up the timer */
 	signal(SIGALRM, socket_timeout_alarm_handler);
 	alarm(socket_timeout);
 
 	/* try to connect to the host at the given port number */
-	struct timeval tv;
-	gettimeofday(&tv, NULL);
-
-	int result = STATE_UNKNOWN;
-	result = np_net_connect(server_address, server_port, &sd, PROTOCOL);
-	if (result == STATE_CRITICAL)
-		return econn_refuse_state;
+	struct timeval start_time;
+	gettimeofday(&start_time, NULL);
+
+	int socket_descriptor = 0;
+	mp_subcheck inital_connect_result = mp_subcheck_init();
+
+	// Try initial connection
+	if (np_net_connect(config.server_address, config.server_port, &socket_descriptor, config.protocol) == STATE_CRITICAL) {
+		// Early exit here, we got connection refused
+		inital_connect_result = mp_set_subcheck_state(inital_connect_result, config.econn_refuse_state);
+		xasprintf(&inital_connect_result.output, "Connection to %s on port %i was REFUSED", config.server_address, config.server_port);
+		mp_add_subcheck_to_check(&overall, inital_connect_result);
+		mp_exit(overall);
+	} else {
+		inital_connect_result = mp_set_subcheck_state(inital_connect_result, STATE_OK);
+		xasprintf(&inital_connect_result.output, "Connection to %s on port %i was a SUCCESS", config.server_address, config.server_port);
+		mp_add_subcheck_to_check(&overall, inital_connect_result);
+	}
 
 #ifdef HAVE_SSL
-	if (flags & FLAG_SSL) {
-		result = np_net_ssl_init_with_hostname(sd, (sni_specified ? sni : NULL));
-		if (result == STATE_OK && check_cert) {
-			result = np_net_ssl_check_cert(days_till_exp_warn, days_till_exp_crit);
+	if (config.use_tls) {
+		mp_subcheck tls_connection_result = mp_subcheck_init();
+		int result = np_net_ssl_init_with_hostname(socket_descriptor, (config.sni_specified ? config.sni : NULL));
+		tls_connection_result = mp_set_subcheck_state(tls_connection_result, result);
+
+		if (result == STATE_OK) {
+			xasprintf(&tls_connection_result.output, "TLS connection succeded");
+
+			if (config.check_cert) {
+				result = np_net_ssl_check_cert(config.days_till_exp_warn, config.days_till_exp_crit);
+
+				mp_subcheck tls_certificate_lifetime_result = mp_subcheck_init();
+				tls_certificate_lifetime_result = mp_set_subcheck_state(tls_certificate_lifetime_result, result);
+
+				if (result == STATE_OK) {
+					xasprintf(&tls_certificate_lifetime_result.output, "Certificate lifetime is within thresholds");
+				} else if (result == STATE_WARNING) {
+					xasprintf(&tls_certificate_lifetime_result.output, "Certificate lifetime is violating warning threshold (%i)",
+							  config.days_till_exp_warn);
+				} else if (result == STATE_CRITICAL) {
+					xasprintf(&tls_certificate_lifetime_result.output, "Certificate lifetime is violating critical threshold (%i)",
+							  config.days_till_exp_crit);
+				} else {
+					xasprintf(&tls_certificate_lifetime_result.output, "Certificate lifetime is somehow unknown");
+				}
+
+				mp_add_subcheck_to_subcheck(&tls_connection_result, tls_certificate_lifetime_result);
+			}
+
+			mp_add_subcheck_to_check(&overall, tls_connection_result);
+		} else {
+			xasprintf(&tls_connection_result.output, "TLS connection failed");
+			mp_add_subcheck_to_check(&overall, tls_connection_result);
+
+			if (socket_descriptor) {
+				close(socket_descriptor);
+			}
+			np_net_ssl_cleanup();
+
+			mp_exit(overall);
 		}
 	}
-	if (result != STATE_OK) {
-		if (sd)
-			close(sd);
-		np_net_ssl_cleanup();
-		return result;
-	}
 #endif /* HAVE_SSL */
 
-	if (server_send != NULL) { /* Something to send? */
-		my_send(server_send, strlen(server_send));
+	if (config.send != NULL) { /* Something to send? */
+		my_send(config.send, strlen(config.send));
 	}
 
-	if (delay > 0) {
-		tv.tv_sec += delay;
-		sleep(delay);
+	if (config.delay > 0) {
+		start_time.tv_sec += config.delay;
+		sleep(config.delay);
 	}
 
-	if (flags & FLAG_VERBOSE) {
-		if (server_send) {
-			printf("Send string: %s\n", server_send);
+	if (verbosity > 0) {
+		if (config.send) {
+			printf("Send string: %s\n", config.send);
 		}
-		if (server_quit) {
-			printf("Quit string: %s\n", server_quit);
+		if (config.quit) {
+			printf("Quit string: %s\n", config.quit);
+		}
+		printf("server_expect_count: %d\n", (int)config.server_expect_count);
+		for (size_t i = 0; i < config.server_expect_count; i++) {
+			printf("\t%zd: %s\n", i, config.server_expect[i]);
 		}
-		printf("server_expect_count: %d\n", (int)server_expect_count);
-		for (size_t i = 0; i < server_expect_count; i++)
-			printf("\t%zd: %s\n", i, server_expect[i]);
 	}
 
 	/* if(len) later on, we know we have a non-NULL response */
 	ssize_t len = 0;
-
+	char *status = NULL;
 	int match = -1;
-	struct timeval timeout;
-	fd_set rfds;
-	FD_ZERO(&rfds);
-	if (server_expect_count) {
+	mp_subcheck expected_data_result = mp_subcheck_init();
+
+	if (config.server_expect_count) {
 		ssize_t received = 0;
+		char buffer[MAXBUF];
 
 		/* watch for the expect string */
 		while ((received = my_recv(buffer, sizeof(buffer))) > 0) {
 			status = realloc(status, len + received + 1);
+
+			if (status == NULL) {
+				die(STATE_UNKNOWN, _("Allocation failed"));
+			}
+
 			memcpy(&status[len], buffer, received);
 			len += received;
 			status[len] = '\0';
 
 			/* stop reading if user-forced */
-			if (maxbytes && len >= maxbytes)
+			if (config.maxbytes && len >= config.maxbytes) {
 				break;
+			}
 
-			if ((match = np_expect_match(status, server_expect, server_expect_count, match_flags)) != NP_MATCH_RETRY)
+			if ((match = np_expect_match(status, config.server_expect, config.server_expect_count, config.match_flags)) != NP_MATCH_RETRY) {
 				break;
+			}
+
+			fd_set rfds;
+			FD_ZERO(&rfds);
+			FD_SET(socket_descriptor, &rfds);
 
 			/* some protocols wait for further input, so make sure we don't wait forever */
-			FD_SET(sd, &rfds);
+			struct timeval timeout;
 			timeout.tv_sec = READ_TIMEOUT;
 			timeout.tv_usec = 0;
-			if (select(sd + 1, &rfds, NULL, NULL, &timeout) <= 0)
+
+			if (select(socket_descriptor + 1, &rfds, NULL, NULL, &timeout) <= 0) {
 				break;
+			}
 		}
 
-		if (match == NP_MATCH_RETRY)
+		if (match == NP_MATCH_RETRY) {
 			match = NP_MATCH_FAILURE;
+		}
 
 		/* no data when expected, so return critical */
-		if (len == 0)
-			die(STATE_CRITICAL, _("No data received from host\n"));
+		if (len == 0) {
+			xasprintf(&expected_data_result.output, "Received no data when some was expected");
+			expected_data_result = mp_set_subcheck_state(expected_data_result, STATE_CRITICAL);
+			mp_add_subcheck_to_check(&overall, expected_data_result);
+			mp_exit(overall);
+		}
 
 		/* print raw output if we're debugging */
-		if (flags & FLAG_VERBOSE)
+		if (verbosity > 0) {
 			printf("received %d bytes from host\n#-raw-recv-------#\n%s\n#-raw-recv-------#\n", (int)len + 1, status);
+		}
 		/* strip whitespace from end of output */
-		while (--len > 0 && isspace(status[len]))
+		while (--len > 0 && isspace(status[len])) {
 			status[len] = '\0';
+		}
+	}
+
+	if (config.quit != NULL) {
+		my_send(config.quit, strlen(config.quit));
 	}
 
-	if (server_quit != NULL) {
-		my_send(server_quit, strlen(server_quit));
+	if (socket_descriptor) {
+		close(socket_descriptor);
 	}
-	if (sd)
-		close(sd);
 #ifdef HAVE_SSL
 	np_net_ssl_cleanup();
 #endif
 
-	microsec = deltime(tv);
-	elapsed_time = (double)microsec / 1.0e6;
+	long microsec = deltime(start_time);
+	double elapsed_time = (double)microsec / 1.0e6;
 
-	if (flags & FLAG_TIME_CRIT && elapsed_time > critical_time)
-		result = STATE_CRITICAL;
-	else if (flags & FLAG_TIME_WARN && elapsed_time > warning_time)
-		result = STATE_WARNING;
+	mp_subcheck elapsed_time_result = mp_subcheck_init();
 
-	/* did we get the response we hoped? */
-	if (match == NP_MATCH_FAILURE && result != STATE_CRITICAL)
-		result = expect_mismatch_state;
+	mp_perfdata time_pd = perfdata_init();
+	time_pd = mp_set_pd_value(time_pd, elapsed_time);
+	time_pd.label = "time";
+	time_pd.uom = "s";
 
-	/* reset the alarm */
-	alarm(0);
+	if (config.critical_time_set && elapsed_time > config.critical_time) {
+		xasprintf(&elapsed_time_result.output, "Connection time %fs exceeded critical threshold (%f)", elapsed_time, config.critical_time);
+
+		elapsed_time_result = mp_set_subcheck_state(elapsed_time_result, STATE_CRITICAL);
+		time_pd.crit_present = true;
+		mp_range crit_val = mp_range_init();
+
+		crit_val.end = mp_create_pd_value(config.critical_time);
+		crit_val.end_infinity = false;
+
+		time_pd.crit = crit_val;
+	} else if (config.warning_time_set && elapsed_time > config.warning_time) {
+		xasprintf(&elapsed_time_result.output, "Connection time %fs exceeded warning threshold (%f)", elapsed_time, config.critical_time);
 
-	/* this is a bit stupid, because we don't want to print the
-	 * response time (which can look ok to the user) if we didn't get
-	 * the response we were looking for. if-else */
-	printf("%s %s - ", SERVICE, state_text(result));
-
-	if (match == NP_MATCH_FAILURE && len && !(flags & FLAG_HIDE_OUTPUT))
-		printf("Unexpected response from host/socket: %s", status);
-	else {
-		if (match == NP_MATCH_FAILURE)
-			printf("Unexpected response from host/socket on ");
-		else
-			printf("%.3f second response time on ", elapsed_time);
-		if (server_address[0] != '/') {
-			if (host_specified)
-				printf("%s port %d", server_address, server_port);
-			else
-				printf("port %d", server_port);
-		} else
-			printf("socket %s", server_address);
+		elapsed_time_result = mp_set_subcheck_state(elapsed_time_result, STATE_WARNING);
+		time_pd.warn_present = true;
+		mp_range warn_val = mp_range_init();
+		warn_val.end = mp_create_pd_value(config.critical_time);
+		warn_val.end_infinity = false;
+
+		time_pd.warn = warn_val;
+	} else {
+		elapsed_time_result = mp_set_subcheck_state(elapsed_time_result, STATE_OK);
+		xasprintf(&elapsed_time_result.output, "Connection time %fs is within thresholds", elapsed_time);
 	}
 
-	if (match != NP_MATCH_FAILURE && !(flags & FLAG_HIDE_OUTPUT) && len)
-		printf(" [%s]", status);
+	mp_add_perfdata_to_subcheck(&elapsed_time_result, time_pd);
+	mp_add_subcheck_to_check(&overall, elapsed_time_result);
 
-	/* perf-data doesn't apply when server doesn't talk properly,
-	 * so print all zeroes on warn and crit. Use fperfdata since
-	 * localisation settings can make different outputs */
-	if (match == NP_MATCH_FAILURE)
-		printf("|%s", fperfdata("time", elapsed_time, "s", (flags & FLAG_TIME_WARN ? true : false), 0,
-								(flags & FLAG_TIME_CRIT ? true : false), 0, true, 0, true, socket_timeout));
-	else
-		printf("|%s", fperfdata("time", elapsed_time, "s", (flags & FLAG_TIME_WARN ? true : false), warning_time,
-								(flags & FLAG_TIME_CRIT ? true : false), critical_time, true, 0, true, socket_timeout));
+	/* did we get the response we hoped? */
+	if (match == NP_MATCH_FAILURE) {
+		expected_data_result = mp_set_subcheck_state(expected_data_result, config.expect_mismatch_state);
+		xasprintf(&expected_data_result.output, "Answer failed to match expectation");
+		mp_add_subcheck_to_check(&overall, expected_data_result);
+	}
 
-	putchar('\n');
-	return result;
+	/* reset the alarm */
+	alarm(0);
+
+	mp_exit(overall);
 }
 
 /* process command-line arguments */
-static int process_arguments(int argc, char **argv) {
+static process_arguments_wrapper process_arguments(int argc, char **argv, check_tcp_config config) {
 	enum {
 		SNI_OPTION = CHAR_MAX + 1
 	};
 
+	int option = 0;
 	static struct option longopts[] = {{"hostname", required_argument, 0, 'H'},
 									   {"critical", required_argument, 0, 'c'},
 									   {"warning", required_argument, 0, 'w'},
@@ -406,52 +486,44 @@ static int process_arguments(int argc, char **argv) {
 									   {"certificate", required_argument, 0, 'D'},
 									   {0, 0, 0, 0}};
 
-	if (argc < 2)
+	if (argc < 2) {
 		usage4(_("No arguments found"));
-
-	/* backwards compatibility */
-	for (int i = 1; i < argc; i++) {
-		if (strcmp("-to", argv[i]) == 0)
-			strcpy(argv[i], "-t");
-		else if (strcmp("-wt", argv[i]) == 0)
-			strcpy(argv[i], "-w");
-		else if (strcmp("-ct", argv[i]) == 0)
-			strcpy(argv[i], "-c");
 	}
 
 	if (!is_option(argv[1])) {
-		server_address = argv[1];
+		config.server_address = argv[1];
 		argv[1] = argv[0];
 		argv = &argv[1];
 		argc--;
 	}
 
-	int option_char;
+	int c;
 	bool escape = false;
+
 	while (true) {
-		int option = 0;
-		option_char = getopt_long(argc, argv, "+hVv46EAH:s:e:q:m:c:w:t:p:C:W:d:Sr:jD:M:", longopts, &option);
+		c = getopt_long(argc, argv, "+hVv46EAH:s:e:q:m:c:w:t:p:C:W:d:Sr:jD:M:", longopts, &option);
 
-		if (option_char == -1 || option_char == EOF || option_char == 1)
+		if (c == -1 || c == EOF || c == 1) {
 			break;
+		}
 
-		switch (option_char) {
+		switch (c) {
 		case '?': /* print short usage statement if args not parsable */
 			usage5();
 		case 'h': /* help */
-			print_help();
+			print_help(config.service);
 			exit(STATE_UNKNOWN);
 		case 'V': /* version */
 			print_revision(progname, NP_VERSION);
 			exit(STATE_UNKNOWN);
 		case 'v': /* verbose mode */
-			flags |= FLAG_VERBOSE;
-			match_flags |= NP_MATCH_VERBOSE;
+			verbosity++;
+			config.match_flags |= NP_MATCH_VERBOSE;
 			break;
-		case '4':
+		case '4': // Apparently unused TODO
 			address_family = AF_INET;
 			break;
-		case '6':
+		case '6': // Apparently unused TODO
 #ifdef USE_IPV6
 			address_family = AF_INET6;
 #else
@@ -459,163 +531,178 @@ static int process_arguments(int argc, char **argv) {
 #endif
 			break;
 		case 'H': /* hostname */
-			host_specified = true;
-			server_address = optarg;
+			config.host_specified = true;
+			config.server_address = optarg;
 			break;
 		case 'c': /* critical */
-			critical_time = strtod(optarg, NULL);
-			flags |= FLAG_TIME_CRIT;
+			config.critical_time = strtod(optarg, NULL);
+			config.critical_time_set = true;
 			break;
 		case 'j': /* hide output */
-			flags |= FLAG_HIDE_OUTPUT;
+			config.hide_output = true;
 			break;
 		case 'w': /* warning */
-			warning_time = strtod(optarg, NULL);
-			flags |= FLAG_TIME_WARN;
-			break;
-		case 'C':
-			crit_codes = realloc(crit_codes, ++crit_codes_count);
-			crit_codes[crit_codes_count - 1] = optarg;
-			break;
-		case 'W':
-			warn_codes = realloc(warn_codes, ++warn_codes_count);
-			warn_codes[warn_codes_count - 1] = optarg;
+			config.warning_time = strtod(optarg, NULL);
+			config.warning_time_set = true;
 			break;
 		case 't': /* timeout */
-			if (!is_intpos(optarg))
+			if (!is_intpos(optarg)) {
 				usage4(_("Timeout interval must be a positive integer"));
-			else
+			} else {
 				socket_timeout = atoi(optarg);
+			}
 			break;
 		case 'p': /* port */
-			if (!is_intpos(optarg))
+			if (!is_intpos(optarg)) {
 				usage4(_("Port must be a positive integer"));
-			else
-				server_port = atoi(optarg);
+			} else {
+				config.server_port = atoi(optarg);
+			}
 			break;
 		case 'E':
 			escape = true;
 			break;
 		case 's':
-			if (escape)
-				server_send = np_escaped_string(optarg);
-			else
-				xasprintf(&server_send, "%s", optarg);
+			if (escape) {
+				config.send = np_escaped_string(optarg);
+			} else {
+				xasprintf(&config.send, "%s", optarg);
+			}
 			break;
 		case 'e': /* expect string (may be repeated) */
-			match_flags &= ~NP_MATCH_EXACT;
-			if (server_expect_count == 0)
-				server_expect = malloc(sizeof(char *) * (++server_expect_count));
-			else
-				server_expect = realloc(server_expect, sizeof(char *) * (++server_expect_count));
-			server_expect[server_expect_count - 1] = optarg;
+			config.match_flags &= ~NP_MATCH_EXACT;
+			if (config.server_expect_count == 0) {
+				config.server_expect = malloc(sizeof(char *) * (++config.server_expect_count));
+			} else {
+				config.server_expect = realloc(config.server_expect, sizeof(char *) * (++config.server_expect_count));
+			}
+
+			if (config.server_expect == NULL) {
+				die(STATE_UNKNOWN, _("Allocation failed"));
+			}
+			config.server_expect[config.server_expect_count - 1] = optarg;
 			break;
 		case 'm':
-			if (!is_intpos(optarg))
+			if (!is_intpos(optarg)) {
 				usage4(_("Maxbytes must be a positive integer"));
-			else
-				maxbytes = strtol(optarg, NULL, 0);
+			} else {
+				config.maxbytes = strtol(optarg, NULL, 0);
+			}
 			break;
 		case 'q':
-			if (escape)
-				server_quit = np_escaped_string(optarg);
-			else
-				xasprintf(&server_quit, "%s\r\n", optarg);
+			if (escape) {
+				config.quit = np_escaped_string(optarg);
+			} else {
+				xasprintf(&config.quit, "%s\r\n", optarg);
+			}
 			break;
 		case 'r':
-			if (!strncmp(optarg, "ok", 2))
-				econn_refuse_state = STATE_OK;
-			else if (!strncmp(optarg, "warn", 4))
-				econn_refuse_state = STATE_WARNING;
-			else if (!strncmp(optarg, "crit", 4))
-				econn_refuse_state = STATE_CRITICAL;
-			else
+			if (!strncmp(optarg, "ok", 2)) {
+				config.econn_refuse_state = STATE_OK;
+			} else if (!strncmp(optarg, "warn", 4)) {
+				config.econn_refuse_state = STATE_WARNING;
+			} else if (!strncmp(optarg, "crit", 4)) {
+				config.econn_refuse_state = STATE_CRITICAL;
+			} else {
 				usage4(_("Refuse must be one of ok, warn, crit"));
+			}
 			break;
 		case 'M':
-			if (!strncmp(optarg, "ok", 2))
-				expect_mismatch_state = STATE_OK;
-			else if (!strncmp(optarg, "warn", 4))
-				expect_mismatch_state = STATE_WARNING;
-			else if (!strncmp(optarg, "crit", 4))
-				expect_mismatch_state = STATE_CRITICAL;
-			else
+			if (!strncmp(optarg, "ok", 2)) {
+				config.expect_mismatch_state = STATE_OK;
+			} else if (!strncmp(optarg, "warn", 4)) {
+				config.expect_mismatch_state = STATE_WARNING;
+			} else if (!strncmp(optarg, "crit", 4)) {
+				config.expect_mismatch_state = STATE_CRITICAL;
+			} else {
 				usage4(_("Mismatch must be one of ok, warn, crit"));
+			}
 			break;
 		case 'd':
-			if (is_intpos(optarg))
-				delay = atoi(optarg);
-			else
+			if (is_intpos(optarg)) {
+				config.delay = atoi(optarg);
+			} else {
 				usage4(_("Delay must be a positive integer"));
+			}
 			break;
-		case 'D': { /* Check SSL cert validity - days 'til certificate expiration */
+		case 'D': /* Check SSL cert validity - days 'til certificate expiration */
 #ifdef HAVE_SSL
 #	ifdef USE_OPENSSL /* XXX */
+		{
 			char *temp;
 			if ((temp = strchr(optarg, ',')) != NULL) {
 				*temp = '\0';
-				if (!is_intnonneg(optarg))
+				if (!is_intnonneg(optarg)) {
 					usage2(_("Invalid certificate expiration period"), optarg);
-				days_till_exp_warn = atoi(optarg);
+				}
+				config.days_till_exp_warn = atoi(optarg);
 				*temp = ',';
 				temp++;
-				if (!is_intnonneg(temp))
+				if (!is_intnonneg(temp)) {
 					usage2(_("Invalid certificate expiration period"), temp);
-				days_till_exp_crit = atoi(temp);
+				}
+				config.days_till_exp_crit = atoi(temp);
 			} else {
-				days_till_exp_crit = 0;
-				if (!is_intnonneg(optarg))
+				config.days_till_exp_crit = 0;
+				if (!is_intnonneg(optarg)) {
 					usage2(_("Invalid certificate expiration period"), optarg);
-				days_till_exp_warn = atoi(optarg);
+				}
+				config.days_till_exp_warn = atoi(optarg);
 			}
-			check_cert = true;
-			flags |= FLAG_SSL;
+			config.check_cert = true;
+			config.use_tls = true;
 		} break;
 #	endif /* USE_OPENSSL */
 #endif
 			/* fallthrough if we don't have ssl */
 		case 'S':
 #ifdef HAVE_SSL
-			flags |= FLAG_SSL;
+			config.use_tls = true;
 #else
 			die(STATE_UNKNOWN, _("Invalid option - SSL is not available"));
 #endif
 			break;
 		case SNI_OPTION:
 #ifdef HAVE_SSL
-			flags |= FLAG_SSL;
-			sni_specified = true;
-			sni = optarg;
+			config.use_tls = true;
+			config.sni_specified = true;
+			config.sni = optarg;
 #else
 			die(STATE_UNKNOWN, _("Invalid option - SSL is not available"));
 #endif
 			break;
 		case 'A':
-			match_flags |= NP_MATCH_ALL;
+			config.match_flags |= NP_MATCH_ALL;
 			break;
 		}
 	}
 
-	option_char = optind;
-	if (!host_specified && option_char < argc)
-		server_address = strdup(argv[option_char++]);
+	c = optind;
+	if (!config.host_specified && c < argc) {
+		config.server_address = strdup(argv[c++]);
+	}
 
-	if (server_address == NULL)
+	if (config.server_address == NULL) {
 		usage4(_("You must provide a server address"));
-	else if (server_address[0] != '/' && !is_host(server_address))
-		die(STATE_CRITICAL, "%s %s - %s: %s\n", SERVICE, state_text(STATE_CRITICAL), _("Invalid hostname, address or socket"),
-			server_address);
+	} else if (config.server_address[0] != '/' && !is_host(config.server_address)) {
+		die(STATE_CRITICAL, "%s %s - %s: %s\n", config.service, state_text(STATE_CRITICAL), _("Invalid hostname, address or socket"),
+			config.server_address);
+	}
 
-	return OK;
+	process_arguments_wrapper result = {
+		.config = config,
+		.errorcode = OK,
+	};
+	return result;
 }
 
-void print_help(void) {
+void print_help(const char *service) {
 	print_revision(progname, NP_VERSION);
 
 	printf("Copyright (c) 1999 Ethan Galstad <nagios at nagios.org>\n");
 	printf(COPYRIGHT, copyright, email);
 
-	printf(_("This plugin tests %s connections with the specified host (or unix socket).\n\n"), SERVICE);
+	printf(_("This plugin tests %s connections with the specified host (or unix socket).\n\n"), service);
 
 	print_usage();
 
@@ -662,6 +749,7 @@ void print_help(void) {
 
 	printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
 
+	printf(UT_OUTPUT_FORMAT);
 	printf(UT_VERBOSE);
 
 	printf(UT_SUPPORT);
diff --git a/plugins/check_tcp.d/config.h b/plugins/check_tcp.d/config.h
new file mode 100644
index 00000000..7ecf51a6
--- /dev/null
+++ b/plugins/check_tcp.d/config.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "../common.h"
+#include "../../lib/utils_tcp.h"
+#include <netinet/in.h>
+
+typedef struct check_tcp_config {
+	char *server_address;
+	bool host_specified;
+	int server_port; // TODO can this be a uint16?
+
+	int protocol; /* most common is default */
+	char *service;
+	char *send;
+	char *quit;
+	char **server_expect;
+	size_t server_expect_count;
+#ifdef HAVE_SSL
+	bool use_tls;
+	char *sni;
+	bool sni_specified;
+	bool check_cert;
+	int days_till_exp_warn;
+	int days_till_exp_crit;
+#endif // HAVE_SSL
+	int match_flags;
+	int expect_mismatch_state;
+	unsigned int delay;
+
+	bool warning_time_set;
+	double warning_time;
+	bool critical_time_set;
+	double critical_time;
+
+	int econn_refuse_state;
+
+	ssize_t maxbytes;
+
+	bool hide_output;
+} check_tcp_config;
+
+check_tcp_config check_tcp_config_init() {
+	check_tcp_config result = {
+		.server_address = "127.0.0.1",
+		.host_specified = false,
+		.server_port = 0,
+
+		.protocol = IPPROTO_TCP,
+		.service = "TCP",
+		.send = NULL,
+		.quit = NULL,
+		.server_expect = NULL,
+		.server_expect_count = 0,
+#ifdef HAVE_SSL
+		.use_tls = false,
+		.sni = NULL,
+		.sni_specified = false,
+		.check_cert = false,
+		.days_till_exp_warn = 0,
+		.days_till_exp_crit = 0,
+#endif // HAVE_SSL
+		.match_flags = NP_MATCH_EXACT,
+		.expect_mismatch_state = STATE_WARNING,
+		.delay = 0,
+
+		.warning_time_set = false,
+		.warning_time = 0,
+		.critical_time_set = false,
+		.critical_time = 0,
+
+		.econn_refuse_state = STATE_CRITICAL,
+
+		.maxbytes = 0,
+
+		.hide_output = false,
+	};
+	return result;
+}



More information about the Commits mailing list