[monitoring-plugins] check_smtp: modern output + some tls cert helper ...

Lorenz Kästle git at monitoring-plugins.org
Sun Nov 9 12:30:12 CET 2025


 Module: monitoring-plugins
 Branch: master
 Commit: 6bc9e518b247e85a39479a0ac6685e68c3a61b40
 Author: Lorenz Kästle <12514511+RincewindsHat at users.noreply.github.com>
   Date: Sat Nov  8 00:19:25 2025 +0100
    URL: https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=6bc9e518

check_smtp: modern output + some tls cert helper functions

---

 plugins/check_smtp.c          | 676 ++++++++++++++++++++++++------------------
 plugins/check_smtp.d/config.h |  16 +-
 plugins/netutils.h            |  20 ++
 plugins/sslutils.c            | 132 +++++++++
 4 files changed, 550 insertions(+), 294 deletions(-)

diff --git a/plugins/check_smtp.c b/plugins/check_smtp.c
index 83ad575c..f2c7f05c 100644
--- a/plugins/check_smtp.c
+++ b/plugins/check_smtp.c
@@ -28,20 +28,24 @@
  *
  *****************************************************************************/
 
-const char *progname = "check_smtp";
-const char *copyright = "2000-2024";
-const char *email = "devel at monitoring-plugins.org";
-
 #include "common.h"
 #include "netutils.h"
+#include "output.h"
+#include "perfdata.h"
+#include "thresholds.h"
 #include "utils.h"
 #include "base64.h"
 #include "regex.h"
 
 #include <ctype.h>
+#include <string.h>
 #include "check_smtp.d/config.h"
 #include "../lib/states.h"
 
+const char *progname = "check_smtp";
+const char *copyright = "2000-2024";
+const char *email = "devel at monitoring-plugins.org";
+
 #define PROXY_PREFIX    "PROXY TCP4 0.0.0.0 0.0.0.0 25 25\r\n"
 #define SMTP_HELO       "HELO "
 #define SMTP_EHLO       "EHLO "
@@ -161,323 +165,414 @@ int main(int argc, char **argv) {
 	gettimeofday(&start_time, NULL);
 
 	int socket_descriptor = 0;
+
 	/* try to connect to the host at the given port number */
-	mp_state_enum result =
+	mp_state_enum tcp_result =
 		my_tcp_connect(config.server_address, config.server_port, &socket_descriptor);
 
-	char *error_msg = "";
+	mp_check overall = mp_check_init();
+	mp_subcheck sc_tcp_connect = mp_subcheck_init();
 	char buffer[MAX_INPUT_BUFFER];
 	bool ssl_established = false;
-	if (result == STATE_OK) { /* we connected */
-		/* If requested, send PROXY header */
-		if (config.use_proxy_prefix) {
-			if (verbose) {
-				printf("Sending header %s\n", PROXY_PREFIX);
-			}
-			my_send(config, PROXY_PREFIX, strlen(PROXY_PREFIX), socket_descriptor, ssl_established);
+
+	if (tcp_result != STATE_OK) {
+		// Connect failed
+		sc_tcp_connect = mp_set_subcheck_state(sc_tcp_connect, STATE_CRITICAL);
+		xasprintf(&sc_tcp_connect.output, "TCP connect to '%s' failed", config.server_address);
+		mp_add_subcheck_to_check(&overall, sc_tcp_connect);
+		mp_exit(overall);
+	}
+
+	/* we connected */
+	/* If requested, send PROXY header */
+	if (config.use_proxy_prefix) {
+		if (verbose) {
+			printf("Sending header %s\n", PROXY_PREFIX);
 		}
+		my_send(config, PROXY_PREFIX, strlen(PROXY_PREFIX), socket_descriptor, ssl_established);
+	}
 
 #ifdef HAVE_SSL
-		if (config.use_ssl) {
-			result = np_net_ssl_init_with_hostname(socket_descriptor,
-												   (config.use_sni ? config.server_address : NULL));
-			if (result != STATE_OK) {
-				printf(_("CRITICAL - Cannot create SSL context.\n"));
-				close(socket_descriptor);
-				np_net_ssl_cleanup();
-				exit(STATE_CRITICAL);
-			}
-			ssl_established = true;
+	if (config.use_ssl) {
+		int tls_result = np_net_ssl_init_with_hostname(
+			socket_descriptor, (config.use_sni ? config.server_address : NULL));
+
+		mp_subcheck sc_tls_connection = mp_subcheck_init();
+
+		if (tls_result != STATE_OK) {
+			close(socket_descriptor);
+			np_net_ssl_cleanup();
+
+			sc_tls_connection = mp_set_subcheck_state(sc_tls_connection, STATE_CRITICAL);
+			xasprintf(&sc_tls_connection.output, "cannot create TLS context");
+			mp_add_subcheck_to_check(&overall, sc_tls_connection);
+			mp_exit(overall);
 		}
+
+		sc_tls_connection = mp_set_subcheck_state(sc_tls_connection, STATE_OK);
+		xasprintf(&sc_tls_connection.output, "TLS context established");
+		mp_add_subcheck_to_check(&overall, sc_tls_connection);
+		ssl_established = true;
+	}
 #endif
 
-		/* watch for the SMTP connection string and */
-		/* return a WARNING status if we couldn't read any data */
-		if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) <= 0) {
-			printf(_("recv() failed\n"));
-			exit(STATE_WARNING);
+	/* watch for the SMTP connection string and */
+	/* return a WARNING status if we couldn't read any data */
+	if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) <= 0) {
+		mp_subcheck sc_read_data = mp_subcheck_init();
+		sc_read_data = mp_set_subcheck_state(sc_read_data, STATE_WARNING);
+		xasprintf(&sc_read_data.output, "recv() failed");
+		mp_add_subcheck_to_check(&overall, sc_read_data);
+		mp_exit(overall);
+	}
+
+	char *server_response = NULL;
+	/* save connect return (220 hostname ..) for later use */
+	xasprintf(&server_response, "%s", buffer);
+
+	/* send the HELO/EHLO command */
+	my_send(config, helocmd, (int)strlen(helocmd), socket_descriptor, ssl_established);
+
+	/* allow for response to helo command to reach us */
+	if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) <= 0) {
+		mp_subcheck sc_read_data = mp_subcheck_init();
+		sc_read_data = mp_set_subcheck_state(sc_read_data, STATE_WARNING);
+		xasprintf(&sc_read_data.output, "recv() failed");
+		mp_add_subcheck_to_check(&overall, sc_read_data);
+		mp_exit(overall);
+	}
+
+	bool supports_tls = false;
+	if (config.use_ehlo || config.use_lhlo) {
+		if (strstr(buffer, "250 STARTTLS") != NULL || strstr(buffer, "250-STARTTLS") != NULL) {
+			supports_tls = true;
 		}
+	}
 
-		char *server_response = NULL;
-		/* save connect return (220 hostname ..) for later use */
-		xasprintf(&server_response, "%s", buffer);
+	if (config.use_starttls && !supports_tls) {
+		smtp_quit(config, buffer, socket_descriptor, ssl_established);
 
-		/* send the HELO/EHLO command */
-		my_send(config, helocmd, (int)strlen(helocmd), socket_descriptor, ssl_established);
+		mp_subcheck sc_read_data = mp_subcheck_init();
+		sc_read_data = mp_set_subcheck_state(sc_read_data, STATE_WARNING);
+		xasprintf(&sc_read_data.output, "StartTLS not supported by server");
+		mp_add_subcheck_to_check(&overall, sc_read_data);
+		mp_exit(overall);
+	}
+
+#ifdef HAVE_SSL
+	if (config.use_starttls) {
+		/* send the STARTTLS command */
+		send(socket_descriptor, SMTP_STARTTLS, strlen(SMTP_STARTTLS), 0);
+
+		mp_subcheck sc_starttls_init = mp_subcheck_init();
+		recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
+				  ssl_established); /* wait for it */
+		if (!strstr(buffer, SMTP_EXPECT)) {
+			smtp_quit(config, buffer, socket_descriptor, ssl_established);
+
+			xasprintf(&sc_starttls_init.output, "StartTLS not supported by server");
+			sc_starttls_init = mp_set_subcheck_state(sc_starttls_init, STATE_UNKNOWN);
+			mp_add_subcheck_to_check(&overall, sc_starttls_init);
+			mp_exit(overall);
+		}
+
+		mp_state_enum starttls_result = np_net_ssl_init_with_hostname(
+			socket_descriptor, (config.use_sni ? config.server_address : NULL));
+		if (starttls_result != STATE_OK) {
+			close(socket_descriptor);
+			np_net_ssl_cleanup();
+
+			sc_starttls_init = mp_set_subcheck_state(sc_starttls_init, STATE_CRITICAL);
+			xasprintf(&sc_starttls_init.output, "failed to create StartTLS context");
+			mp_add_subcheck_to_check(&overall, sc_starttls_init);
+			mp_exit(overall);
+		}
+		sc_starttls_init = mp_set_subcheck_state(sc_starttls_init, STATE_OK);
+		xasprintf(&sc_starttls_init.output, "created StartTLS context");
+		mp_add_subcheck_to_check(&overall, sc_starttls_init);
+
+		ssl_established = true;
+
+		/*
+		 * Resend the EHLO command.
+		 *
+		 * RFC 3207 (4.2) says: ``The client MUST discard any knowledge
+		 * obtained from the server, such as the list of SMTP service
+		 * extensions, which was not obtained from the TLS negotiation
+		 * itself.  The client SHOULD send an EHLO command as the first
+		 * command after a successful TLS negotiation.''  For this
+		 * reason, some MTAs will not allow an AUTH LOGIN command before
+		 * we resent EHLO via TLS.
+		 */
+		if (my_send(config, helocmd, (int)strlen(helocmd), socket_descriptor, ssl_established) <=
+			0) {
+			my_close(socket_descriptor);
+
+			mp_subcheck sc_ehlo = mp_subcheck_init();
+			sc_ehlo = mp_set_subcheck_state(sc_ehlo, STATE_UNKNOWN);
+			xasprintf(&sc_ehlo.output, "cannot send EHLO command via StartTLS");
+			mp_add_subcheck_to_check(&overall, sc_ehlo);
+			mp_exit(overall);
+		}
+
+		if (verbose) {
+			printf(_("sent %s"), helocmd);
+		}
 
-		/* allow for response to helo command to reach us */
 		if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) <= 0) {
-			printf(_("recv() failed\n"));
-			exit(STATE_WARNING);
+			my_close(socket_descriptor);
+
+			mp_subcheck sc_ehlo = mp_subcheck_init();
+			sc_ehlo = mp_set_subcheck_state(sc_ehlo, STATE_UNKNOWN);
+			xasprintf(&sc_ehlo.output, "cannot read EHLO response via StartTLS");
+			mp_add_subcheck_to_check(&overall, sc_ehlo);
+			mp_exit(overall);
 		}
 
-		bool supports_tls = false;
-		if (config.use_ehlo || config.use_lhlo) {
-			if (strstr(buffer, "250 STARTTLS") != NULL || strstr(buffer, "250-STARTTLS") != NULL) {
-				supports_tls = true;
-			}
+		if (verbose) {
+			printf("%s", buffer);
 		}
+	}
 
-		if (config.use_starttls && !supports_tls) {
-			printf(_("WARNING - TLS not supported by server\n"));
+#	ifdef USE_OPENSSL
+	if (ssl_established) {
+		net_ssl_check_cert_result cert_check_result =
+			np_net_ssl_check_cert2(config.days_till_exp_warn, config.days_till_exp_crit);
+
+		mp_subcheck sc_cert_check = mp_subcheck_init();
+
+		switch (cert_check_result.errors) {
+		case ALL_OK: {
+			xasprintf(&sc_cert_check.output, "Certificate expiration. Remaining time %g days",
+					  cert_check_result.remaining_seconds / 86400);
+			sc_cert_check = mp_set_subcheck_state(sc_cert_check, cert_check_result.result_state);
+		} break;
+		case NO_SERVER_CERTIFICATE_PRESENT: {
+			xasprintf(&sc_cert_check.output, "no server certificate present");
+			sc_cert_check = mp_set_subcheck_state(sc_cert_check, cert_check_result.result_state);
+		} break;
+		case UNABLE_TO_RETRIEVE_CERTIFICATE_SUBJECT: {
+			xasprintf(&sc_cert_check.output, "can not retrieve certificate subject");
+			sc_cert_check = mp_set_subcheck_state(sc_cert_check, cert_check_result.result_state);
+		} break;
+		case WRONG_TIME_FORMAT_IN_CERTIFICATE: {
+			xasprintf(&sc_cert_check.output, "wrong time format in certificate");
+			sc_cert_check = mp_set_subcheck_state(sc_cert_check, cert_check_result.result_state);
+		} break;
+		};
+
+		mp_add_subcheck_to_check(&overall, sc_cert_check);
+
+		if (config.check_cert) {
 			smtp_quit(config, buffer, socket_descriptor, ssl_established);
-			exit(STATE_WARNING);
+			my_close(socket_descriptor);
+			mp_exit(overall);
 		}
+	}
+#	endif /* USE_OPENSSL */
 
-#ifdef HAVE_SSL
-		if (config.use_starttls) {
-			/* send the STARTTLS command */
-			send(socket_descriptor, SMTP_STARTTLS, strlen(SMTP_STARTTLS), 0);
-
-			recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
-					  ssl_established); /* wait for it */
-			if (!strstr(buffer, SMTP_EXPECT)) {
-				printf(_("Server does not support STARTTLS\n"));
-				smtp_quit(config, buffer, socket_descriptor, ssl_established);
-				exit(STATE_UNKNOWN);
-			}
+#endif
 
-			result = np_net_ssl_init_with_hostname(socket_descriptor,
-												   (config.use_sni ? config.server_address : NULL));
-			if (result != STATE_OK) {
-				printf(_("CRITICAL - Cannot create SSL context.\n"));
-				close(socket_descriptor);
-				np_net_ssl_cleanup();
-				exit(STATE_CRITICAL);
-			}
+	if (verbose) {
+		printf("%s", buffer);
+	}
 
-			ssl_established = true;
-
-			/*
-			 * Resend the EHLO command.
-			 *
-			 * RFC 3207 (4.2) says: ``The client MUST discard any knowledge
-			 * obtained from the server, such as the list of SMTP service
-			 * extensions, which was not obtained from the TLS negotiation
-			 * itself.  The client SHOULD send an EHLO command as the first
-			 * command after a successful TLS negotiation.''  For this
-			 * reason, some MTAs will not allow an AUTH LOGIN command before
-			 * we resent EHLO via TLS.
-			 */
-			if (my_send(config, helocmd, strlen(helocmd), socket_descriptor, ssl_established) <=
-				0) {
-				printf("%s\n", _("SMTP UNKNOWN - Cannot send EHLO command via TLS."));
-				my_close(socket_descriptor);
-				exit(STATE_UNKNOWN);
-			}
+	/* save buffer for later use */
+	xasprintf(&server_response, "%s%s", server_response, buffer);
+	/* strip the buffer of carriage returns */
+	strip(server_response);
 
-			if (verbose) {
-				printf(_("sent %s"), helocmd);
-			}
+	/* make sure we find the droids we are looking for */
+	mp_subcheck sc_expect_response = mp_subcheck_init();
 
-			if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) <=
-				0) {
-				printf("%s\n", _("SMTP UNKNOWN - Cannot read EHLO response via TLS."));
-				my_close(socket_descriptor);
-				exit(STATE_UNKNOWN);
-			}
+	if (!strstr(server_response, config.server_expect)) {
+		sc_expect_response = mp_set_subcheck_state(sc_expect_response, STATE_WARNING);
+		if (config.server_port == SMTP_PORT) {
+			xasprintf(&sc_expect_response.output, _("invalid SMTP response received from host: %s"),
+					  server_response);
+		} else {
+			xasprintf(&sc_expect_response.output,
+					  _("invalid SMTP response received from host on port %d: %s"),
+					  config.server_port, server_response);
+		}
+		exit(STATE_WARNING);
+	} else {
+		xasprintf(&sc_expect_response.output, "received valid SMTP response '%s' from host: '%s'",
+				  config.server_expect, server_response);
+		sc_expect_response = mp_set_subcheck_state(sc_expect_response, STATE_OK);
+	}
 
-			if (verbose) {
-				printf("%s", buffer);
-			}
+	mp_add_subcheck_to_check(&overall, sc_expect_response);
 
-#	ifdef USE_OPENSSL
-			if (config.check_cert) {
-				result =
-					np_net_ssl_check_cert(config.days_till_exp_warn, config.days_till_exp_crit);
-				smtp_quit(config, buffer, socket_descriptor, ssl_established);
-				my_close(socket_descriptor);
-				exit(result);
-			}
-#	endif /* USE_OPENSSL */
+	if (config.send_mail_from) {
+		my_send(config, cmd_str, (int)strlen(cmd_str), socket_descriptor, ssl_established);
+		if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) >= 1 &&
+			verbose) {
+			printf("%s", buffer);
 		}
-#endif
+	}
 
-		if (verbose) {
+	size_t counter = 0;
+	while (counter < config.ncommands) {
+		xasprintf(&cmd_str, "%s%s", config.commands[counter], "\r\n");
+		my_send(config, cmd_str, (int)strlen(cmd_str), socket_descriptor, ssl_established);
+		if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) >= 1 &&
+			verbose) {
 			printf("%s", buffer);
 		}
 
-		/* save buffer for later use */
-		xasprintf(&server_response, "%s%s", server_response, buffer);
-		/* strip the buffer of carriage returns */
-		strip(server_response);
+		strip(buffer);
 
-		/* make sure we find the droids we are looking for */
-		if (!strstr(server_response, config.server_expect)) {
-			if (config.server_port == SMTP_PORT) {
-				printf(_("Invalid SMTP response received from host: %s\n"), server_response);
-			} else {
-				printf(_("Invalid SMTP response received from host on port %d: %s\n"),
-					   config.server_port, server_response);
+		if (counter < config.nresponses) {
+			int cflags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
+			regex_t preg;
+			int errcode = regcomp(&preg, config.responses[counter], cflags);
+			char errbuf[MAX_INPUT_BUFFER];
+			if (errcode != 0) {
+				regerror(errcode, &preg, errbuf, MAX_INPUT_BUFFER);
+				printf(_("Could Not Compile Regular Expression"));
+				exit(STATE_UNKNOWN);
 			}
-			exit(STATE_WARNING);
-		}
 
-		if (config.send_mail_from) {
-			my_send(config, cmd_str, (int)strlen(cmd_str), socket_descriptor, ssl_established);
-			if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) >=
-					1 &&
-				verbose) {
-				printf("%s", buffer);
+			regmatch_t pmatch[10];
+			int eflags = 0;
+			int excode = regexec(&preg, buffer, 10, pmatch, eflags);
+			mp_subcheck sc_expected_responses = mp_subcheck_init();
+			if (excode == 0) {
+				xasprintf(&sc_expected_responses.output, "valid response '%s' to command '%s'",
+						  buffer, config.commands[counter]);
+				sc_expected_responses = mp_set_subcheck_state(sc_expected_responses, STATE_OK);
+			} else if (excode == REG_NOMATCH) {
+				sc_expected_responses = mp_set_subcheck_state(sc_expected_responses, STATE_WARNING);
+				xasprintf(&sc_expected_responses.output, "invalid response '%s' to command '%s'",
+						  buffer, config.commands[counter]);
+			} else {
+				regerror(excode, &preg, errbuf, MAX_INPUT_BUFFER);
+				xasprintf(&sc_expected_responses.output, "regexec execute error: %s", errbuf);
+				sc_expected_responses = mp_set_subcheck_state(sc_expected_responses, STATE_UNKNOWN);
 			}
 		}
+		counter++;
+	}
 
-		int counter = 0;
-		while (counter < config.ncommands) {
-			xasprintf(&cmd_str, "%s%s", config.commands[counter], "\r\n");
-			my_send(config, cmd_str, (int)strlen(cmd_str), socket_descriptor, ssl_established);
-			if (recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor, ssl_established) >=
-					1 &&
-				verbose) {
-				printf("%s", buffer);
-			}
-			strip(buffer);
-			if (counter < config.nresponses) {
-				int cflags = REG_EXTENDED | REG_NOSUB | REG_NEWLINE;
-				regex_t preg;
-				int errcode = regcomp(&preg, config.responses[counter], cflags);
-				char errbuf[MAX_INPUT_BUFFER];
-				if (errcode != 0) {
-					regerror(errcode, &preg, errbuf, MAX_INPUT_BUFFER);
-					printf(_("Could Not Compile Regular Expression"));
-					exit(STATE_UNKNOWN);
+	if (config.authtype != NULL) {
+		mp_subcheck sc_auth = mp_subcheck_init();
+
+		if (strcmp(config.authtype, "LOGIN") == 0) {
+			char *abuf;
+			int ret;
+			do {
+				/* send AUTH LOGIN */
+				my_send(config, SMTP_AUTH_LOGIN, strlen(SMTP_AUTH_LOGIN), socket_descriptor,
+						ssl_established);
+
+				if (verbose) {
+					printf(_("sent %s\n"), "AUTH LOGIN");
 				}
 
-				regmatch_t pmatch[10];
-				int eflags = 0;
-				int excode = regexec(&preg, buffer, 10, pmatch, eflags);
-				if (excode == 0) {
-					result = STATE_OK;
-				} else if (excode == REG_NOMATCH) {
-					result = STATE_WARNING;
-					printf(_("SMTP %s - Invalid response '%s' to command '%s'\n"),
-						   state_text(result), buffer, config.commands[counter]);
-				} else {
-					regerror(excode, &preg, errbuf, MAX_INPUT_BUFFER);
-					printf(_("Execute Error: %s\n"), errbuf);
-					result = STATE_UNKNOWN;
+				if ((ret = recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
+									 ssl_established)) <= 0) {
+					xasprintf(&sc_auth.output, _("recv() failed after AUTH LOGIN"));
+					sc_auth = mp_set_subcheck_state(sc_auth, STATE_WARNING);
+					break;
 				}
-			}
-			counter++;
-		}
 
-		if (config.authtype != NULL) {
-			if (strcmp(config.authtype, "LOGIN") == 0) {
-				char *abuf;
-				int ret;
-				do {
-					if (config.authuser == NULL) {
-						result = STATE_CRITICAL;
-						xasprintf(&error_msg, _("no authuser specified, "));
-						break;
-					}
-					if (config.authpass == NULL) {
-						result = STATE_CRITICAL;
-						xasprintf(&error_msg, _("no authpass specified, "));
-						break;
-					}
-
-					/* send AUTH LOGIN */
-					my_send(config, SMTP_AUTH_LOGIN, strlen(SMTP_AUTH_LOGIN), socket_descriptor,
-							ssl_established);
-					if (verbose) {
-						printf(_("sent %s\n"), "AUTH LOGIN");
-					}
-
-					if ((ret = recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
-										 ssl_established)) <= 0) {
-						xasprintf(&error_msg, _("recv() failed after AUTH LOGIN, "));
-						result = STATE_WARNING;
-						break;
-					}
-					if (verbose) {
-						printf(_("received %s\n"), buffer);
-					}
-
-					if (strncmp(buffer, "334", 3) != 0) {
-						result = STATE_CRITICAL;
-						xasprintf(&error_msg, _("invalid response received after AUTH LOGIN, "));
-						break;
-					}
-
-					/* encode authuser with base64 */
-					base64_encode_alloc(config.authuser, strlen(config.authuser), &abuf);
-					xasprintf(&abuf, "%s\r\n", abuf);
-					my_send(config, abuf, (int)strlen(abuf), socket_descriptor, ssl_established);
-					if (verbose) {
-						printf(_("sent %s\n"), abuf);
-					}
-
-					if ((ret = recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
-										 ssl_established)) <= 0) {
-						result = STATE_CRITICAL;
-						xasprintf(&error_msg, _("recv() failed after sending authuser, "));
-						break;
-					}
-					if (verbose) {
-						printf(_("received %s\n"), buffer);
-					}
-					if (strncmp(buffer, "334", 3) != 0) {
-						result = STATE_CRITICAL;
-						xasprintf(&error_msg, _("invalid response received after authuser, "));
-						break;
-					}
-					/* encode authpass with base64 */
-					base64_encode_alloc(config.authpass, strlen(config.authpass), &abuf);
-					xasprintf(&abuf, "%s\r\n", abuf);
-					my_send(config, abuf, (int)strlen(abuf), socket_descriptor, ssl_established);
-					if (verbose) {
-						printf(_("sent %s\n"), abuf);
-					}
-					if ((ret = recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
-										 ssl_established)) <= 0) {
-						result = STATE_CRITICAL;
-						xasprintf(&error_msg, _("recv() failed after sending authpass, "));
-						break;
-					}
-					if (verbose) {
-						printf(_("received %s\n"), buffer);
-					}
-					if (strncmp(buffer, "235", 3) != 0) {
-						result = STATE_CRITICAL;
-						xasprintf(&error_msg, _("invalid response received after authpass, "));
-						break;
-					}
+				if (verbose) {
+					printf(_("received %s\n"), buffer);
+				}
+
+				if (strncmp(buffer, "334", 3) != 0) {
+					xasprintf(&sc_auth.output, "invalid response received after AUTH LOGIN");
+					sc_auth = mp_set_subcheck_state(sc_auth, STATE_CRITICAL);
 					break;
-				} while (false);
-			} else {
-				result = STATE_CRITICAL;
-				xasprintf(&error_msg, _("only authtype LOGIN is supported, "));
-			}
-		}
+				}
 
-		/* tell the server we're done */
-		smtp_quit(config, buffer, socket_descriptor, ssl_established);
+				/* encode authuser with base64 */
+				base64_encode_alloc(config.authuser, strlen(config.authuser), &abuf);
+				xasprintf(&abuf, "%s\r\n", abuf);
+				my_send(config, abuf, (int)strlen(abuf), socket_descriptor, ssl_established);
+				if (verbose) {
+					printf(_("sent %s\n"), abuf);
+				}
 
-		/* finally close the connection */
-		close(socket_descriptor);
+				if ((ret = recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
+									 ssl_established)) <= 0) {
+					xasprintf(&sc_auth.output, "recv() failed after sending authuser");
+					sc_auth = mp_set_subcheck_state(sc_auth, STATE_CRITICAL);
+					break;
+				}
+
+				if (verbose) {
+					printf(_("received %s\n"), buffer);
+				}
+
+				if (strncmp(buffer, "334", 3) != 0) {
+					xasprintf(&sc_auth.output, "invalid response received after authuser");
+					sc_auth = mp_set_subcheck_state(sc_auth, STATE_CRITICAL);
+					break;
+				}
+
+				/* encode authpass with base64 */
+				base64_encode_alloc(config.authpass, strlen(config.authpass), &abuf);
+				xasprintf(&abuf, "%s\r\n", abuf);
+				my_send(config, abuf, (int)strlen(abuf), socket_descriptor, ssl_established);
+
+				if (verbose) {
+					printf(_("sent %s\n"), abuf);
+				}
+
+				if ((ret = recvlines(config, buffer, MAX_INPUT_BUFFER, socket_descriptor,
+									 ssl_established)) <= 0) {
+					xasprintf(&sc_auth.output, "recv() failed after sending authpass");
+					sc_auth = mp_set_subcheck_state(sc_auth, STATE_CRITICAL);
+					break;
+				}
+
+				if (verbose) {
+					printf(_("received %s\n"), buffer);
+				}
+
+				if (strncmp(buffer, "235", 3) != 0) {
+					xasprintf(&sc_auth.output, "invalid response received after authpass");
+					sc_auth = mp_set_subcheck_state(sc_auth, STATE_CRITICAL);
+					break;
+				}
+				break;
+			} while (false);
+		} else {
+			sc_auth = mp_set_subcheck_state(sc_auth, STATE_CRITICAL);
+			xasprintf(&sc_auth.output, "only authtype LOGIN is supported");
+		}
+
+		mp_add_subcheck_to_check(&overall, sc_auth);
 	}
 
+	/* tell the server we're done */
+	smtp_quit(config, buffer, socket_descriptor, ssl_established);
+
+	/* finally close the connection */
+	close(socket_descriptor);
+
 	/* reset the alarm */
 	alarm(0);
 
 	long microsec = deltime(start_time);
 	double elapsed_time = (double)microsec / 1.0e6;
 
-	if (result == STATE_OK) {
-		if (config.check_critical_time && elapsed_time > config.critical_time) {
-			result = STATE_CRITICAL;
-		} else if (config.check_warning_time && elapsed_time > config.warning_time) {
-			result = STATE_WARNING;
-		}
-	}
+	mp_perfdata pd_elapsed_time = perfdata_init();
+	pd_elapsed_time = mp_set_pd_value(pd_elapsed_time, elapsed_time);
+	pd_elapsed_time.label = "time";
+	pd_elapsed_time.uom = "s";
 
-	printf(_("SMTP %s - %s%.3f sec. response time%s%s|%s\n"), state_text(result), error_msg,
-		   elapsed_time, verbose ? ", " : "", verbose ? buffer : "",
-		   fperfdata("time", elapsed_time, "s", config.check_warning_time, config.warning_time,
-					 config.check_critical_time, config.critical_time, true, 0, false, 0));
+	pd_elapsed_time = mp_pd_set_thresholds(pd_elapsed_time, config.connection_time);
 
-	exit(result);
+	mp_subcheck sc_connection_time = mp_subcheck_init();
+	xasprintf(&sc_connection_time.output, "connection time: %.3gs", elapsed_time);
+	sc_connection_time =
+		mp_set_subcheck_state(sc_connection_time, mp_get_pd_status(pd_elapsed_time));
+	mp_add_subcheck_to_check(&overall, sc_connection_time);
+
+	mp_exit(overall);
 }
 
 /* process command-line arguments */
@@ -535,8 +630,8 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) {
 		}
 	}
 
-	int command_size = 0;
-	int response_size = 0;
+	unsigned long command_size = 0;
+	unsigned long response_size = 0;
 	bool implicit_tls = false;
 	int server_port_option = 0;
 	while (true) {
@@ -591,7 +686,7 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) {
 				result.config.commands =
 					realloc(result.config.commands, sizeof(char *) * command_size);
 				if (result.config.commands == NULL) {
-					die(STATE_UNKNOWN, _("Could not realloc() units [%d]\n"),
+					die(STATE_UNKNOWN, _("Could not realloc() units [%lu]\n"),
 						result.config.ncommands);
 				}
 			}
@@ -605,7 +700,7 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) {
 				result.config.responses =
 					realloc(result.config.responses, sizeof(char *) * response_size);
 				if (result.config.responses == NULL) {
-					die(STATE_UNKNOWN, _("Could not realloc() units [%d]\n"),
+					die(STATE_UNKNOWN, _("Could not realloc() units [%lu]\n"),
 						result.config.nresponses);
 				}
 			}
@@ -613,22 +708,22 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) {
 			strncpy(result.config.responses[result.config.nresponses], optarg, 255);
 			result.config.nresponses++;
 			break;
-		case 'c': /* critical time threshold */
-			if (!is_nonnegative(optarg)) {
-				usage4(_("Critical time must be a positive"));
-			} else {
-				result.config.critical_time = strtod(optarg, NULL);
-				result.config.check_critical_time = true;
+		case 'c': /* critical time threshold */ {
+			mp_range_parsed tmp = mp_parse_range_string(optarg);
+			if (tmp.error != MP_PARSING_SUCCES) {
+				die(STATE_UNKNOWN, "failed to parse critical time threshold");
 			}
-			break;
-		case 'w': /* warning time threshold */
-			if (!is_nonnegative(optarg)) {
-				usage4(_("Warning time must be a positive"));
-			} else {
-				result.config.warning_time = strtod(optarg, NULL);
-				result.config.check_warning_time = true;
+			result.config.connection_time =
+				mp_thresholds_set_warn(result.config.connection_time, tmp.range);
+		} break;
+		case 'w': /* warning time threshold */ {
+			mp_range_parsed tmp = mp_parse_range_string(optarg);
+			if (tmp.error != MP_PARSING_SUCCES) {
+				die(STATE_UNKNOWN, "failed to parse warning time threshold");
 			}
-			break;
+			result.config.connection_time =
+				mp_thresholds_set_crit(result.config.connection_time, tmp.range);
+		} break;
 		case 'v': /* verbose */
 			verbose++;
 			break;
@@ -742,6 +837,19 @@ check_smtp_config_wrapper process_arguments(int argc, char **argv) {
 		result.config.server_port = server_port_option;
 	}
 
+	if (result.config.authtype) {
+		if (strcmp(result.config.authtype, "LOGIN") == 0) {
+			if (result.config.authuser == NULL) {
+				usage4("no authuser specified");
+			}
+			if (result.config.authpass == NULL) {
+				usage4("no authpass specified");
+			}
+		} else {
+			usage4("only authtype LOGIN is supported");
+		}
+	}
+
 	return result;
 }
 
@@ -791,7 +899,7 @@ char *smtp_quit(check_smtp_config config, char buffer[MAX_INPUT_BUFFER], int soc
 int recvline(char *buf, size_t bufsize, check_smtp_config config, int socket_descriptor,
 			 bool ssl_established) {
 	int result;
-	int counter;
+	size_t counter;
 
 	for (counter = result = 0; counter < bufsize - 1; counter++) {
 		if ((result = my_recv(config, &buf[counter], 1, socket_descriptor, ssl_established)) != 1) {
@@ -799,7 +907,7 @@ int recvline(char *buf, size_t bufsize, check_smtp_config config, int socket_des
 		}
 		if (buf[counter] == '\n') {
 			buf[++counter] = '\0';
-			return counter;
+			return (int)counter;
 		}
 	}
 	return (result == 1 || counter == 0) ? -2 : result; /* -2 if out of space */
diff --git a/plugins/check_smtp.d/config.h b/plugins/check_smtp.d/config.h
index 0a6511ef..bc433093 100644
--- a/plugins/check_smtp.d/config.h
+++ b/plugins/check_smtp.d/config.h
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "../../config.h"
+#include "thresholds.h"
 #include <stddef.h>
 #include <string.h>
 
@@ -18,20 +19,18 @@ typedef struct {
 	char *server_expect;
 	bool ignore_send_quit_failure;
 
-	double warning_time;
-	bool check_warning_time;
-	double critical_time;
-	bool check_critical_time;
+	mp_thresholds connection_time;
+
 	bool use_ehlo;
 	bool use_lhlo;
 
 	char *from_arg;
 	bool send_mail_from;
 
-	int ncommands;
+	unsigned long ncommands;
 	char **commands;
 
-	int nresponses;
+	unsigned long nresponses;
 	char **responses;
 
 	char *authtype;
@@ -58,10 +57,7 @@ check_smtp_config check_smtp_config_init() {
 		.server_expect = SMTP_EXPECT,
 		.ignore_send_quit_failure = false,
 
-		.warning_time = 0,
-		.check_warning_time = false,
-		.critical_time = 0,
-		.check_critical_time = false,
+		.connection_time = mp_thresholds_init(),
 		.use_ehlo = false,
 		.use_lhlo = false,
 
diff --git a/plugins/netutils.h b/plugins/netutils.h
index c4461113..dbd22398 100644
--- a/plugins/netutils.h
+++ b/plugins/netutils.h
@@ -114,6 +114,26 @@ int np_net_ssl_init_with_hostname_version_and_cert(int socket, char *host_name,
 void np_net_ssl_cleanup(void);
 int np_net_ssl_write(const void *buf, int num);
 int np_net_ssl_read(void *buf, int num);
+
+typedef enum {
+	ALL_OK,
+	NO_SERVER_CERTIFICATE_PRESENT,
+	UNABLE_TO_RETRIEVE_CERTIFICATE_SUBJECT,
+	WRONG_TIME_FORMAT_IN_CERTIFICATE,
+} retrieve_expiration_date_errors;
+
+typedef struct {
+	double remaining_seconds;
+	retrieve_expiration_date_errors errors;
+} retrieve_expiration_time_result;
+
+typedef struct {
+	mp_state_enum result_state;
+	double remaining_seconds;
+	retrieve_expiration_date_errors errors;
+} net_ssl_check_cert_result;
+net_ssl_check_cert_result np_net_ssl_check_cert2(int days_till_exp_warn, int days_till_exp_crit);
+
 mp_state_enum np_net_ssl_check_cert(int days_till_exp_warn, int days_till_exp_crit);
 mp_subcheck mp_net_ssl_check_cert(int days_till_exp_warn, int days_till_exp_crit);
 #endif /* HAVE_SSL */
diff --git a/plugins/sslutils.c b/plugins/sslutils.c
index 0e6d7525..c1d15534 100644
--- a/plugins/sslutils.c
+++ b/plugins/sslutils.c
@@ -312,6 +312,138 @@ mp_state_enum np_net_ssl_check_certificate(X509 *certificate, int days_till_exp_
 #	endif /* USE_OPENSSL */
 }
 
+retrieve_expiration_time_result np_net_ssl_get_cert_expiration(X509 *certificate) {
+#	ifdef USE_OPENSSL
+	retrieve_expiration_time_result result = {
+		.errors = ALL_OK,
+		.remaining_seconds = {},
+	};
+
+	if (!certificate) {
+		// printf("%s\n", _("CRITICAL - No server certificate present to inspect."));
+		result.errors = NO_SERVER_CERTIFICATE_PRESENT;
+		return result;
+	}
+
+	/* Extract CN from certificate subject */
+	X509_NAME *subj = X509_get_subject_name(certificate);
+
+	if (!subj) {
+		// printf("%s\n", _("CRITICAL - Cannot retrieve certificate subject."));
+		result.errors = UNABLE_TO_RETRIEVE_CERTIFICATE_SUBJECT;
+		return result;
+	}
+
+	char cn[MAX_CN_LENGTH] = "";
+	int cnlen = X509_NAME_get_text_by_NID(subj, NID_commonName, cn, sizeof(cn));
+	if (cnlen == -1) {
+		strcpy(cn, _("Unknown CN"));
+	}
+
+	/* Retrieve timestamp of certificate */
+	ASN1_STRING *expiration_timestamp = X509_get_notAfter(certificate);
+
+	int offset = 0;
+	struct tm stamp = {};
+	/* Generate tm structure to process timestamp */
+	if (expiration_timestamp->type == V_ASN1_UTCTIME) {
+		if (expiration_timestamp->length < 10) {
+			result.errors = WRONG_TIME_FORMAT_IN_CERTIFICATE;
+			return result;
+		}
+
+		stamp.tm_year =
+			(expiration_timestamp->data[0] - '0') * 10 + (expiration_timestamp->data[1] - '0');
+		if (stamp.tm_year < 50) {
+			stamp.tm_year += 100;
+		}
+		offset = 0;
+	} else {
+		if (expiration_timestamp->length < 12) {
+			result.errors = WRONG_TIME_FORMAT_IN_CERTIFICATE;
+			return result;
+		}
+
+		stamp.tm_year = (expiration_timestamp->data[0] - '0') * 1000 +
+						(expiration_timestamp->data[1] - '0') * 100 +
+						(expiration_timestamp->data[2] - '0') * 10 +
+						(expiration_timestamp->data[3] - '0');
+		stamp.tm_year -= 1900;
+		offset = 2;
+	}
+	stamp.tm_mon = (expiration_timestamp->data[2 + offset] - '0') * 10 +
+				   (expiration_timestamp->data[3 + offset] - '0') - 1;
+	stamp.tm_mday = (expiration_timestamp->data[4 + offset] - '0') * 10 +
+					(expiration_timestamp->data[5 + offset] - '0');
+	stamp.tm_hour = (expiration_timestamp->data[6 + offset] - '0') * 10 +
+					(expiration_timestamp->data[7 + offset] - '0');
+	stamp.tm_min = (expiration_timestamp->data[8 + offset] - '0') * 10 +
+				   (expiration_timestamp->data[9 + offset] - '0');
+	stamp.tm_sec = (expiration_timestamp->data[10 + offset] - '0') * 10 +
+				   (expiration_timestamp->data[11 + offset] - '0');
+	stamp.tm_isdst = -1;
+
+	time_t tm_t = timegm(&stamp);
+	double time_left = difftime(tm_t, time(NULL));
+	result.remaining_seconds = time_left;
+
+	char *timezone = getenv("TZ");
+	setenv("TZ", "GMT", 1);
+	tzset();
+
+	char timestamp[50] = "";
+	strftime(timestamp, 50, "%c %z", localtime(&tm_t));
+	if (timezone) {
+		setenv("TZ", timezone, 1);
+	} else {
+		unsetenv("TZ");
+	}
+
+	tzset();
+
+	X509_free(certificate);
+
+	return result;
+#	else  /* ifndef USE_OPENSSL */
+	printf("%s\n", _("WARNING - Plugin does not support checking certificates."));
+	return STATE_WARNING;
+#	endif /* USE_OPENSSL */
+}
+
+net_ssl_check_cert_result np_net_ssl_check_cert2(int days_till_exp_warn, int days_till_exp_crit) {
+#	ifdef USE_OPENSSL
+	X509 *certificate = NULL;
+	certificate = SSL_get_peer_certificate(s);
+
+	retrieve_expiration_time_result expiration_date = np_net_ssl_get_cert_expiration(certificate);
+
+	net_ssl_check_cert_result result = {
+		.result_state = STATE_UNKNOWN,
+		.remaining_seconds = expiration_date.remaining_seconds,
+		.errors = expiration_date.errors,
+	};
+
+	if (expiration_date.errors == ALL_OK) {
+		// got a valid expiration date
+		unsigned int remaining_days = result.remaining_seconds / 86400;
+
+		if (remaining_days < days_till_exp_crit) {
+			result.result_state = STATE_CRITICAL;
+		} else if (remaining_days < days_till_exp_warn) {
+			result.result_state = STATE_WARNING;
+		} else {
+			result.result_state = STATE_OK;
+		}
+	}
+
+	return result;
+
+#	else  /* ifndef USE_OPENSSL */
+	printf("%s\n", _("WARNING - Plugin does not support checking certificates."));
+	return STATE_WARNING;
+#	endif /* USE_OPENSSL */
+}
+
 mp_state_enum np_net_ssl_check_cert(int days_till_exp_warn, int days_till_exp_crit) {
 #	ifdef USE_OPENSSL
 	X509 *certificate = NULL;



More information about the Commits mailing list