Module: monitoring-plugins
 Branch: master
 Commit: 6ac236c1ef06ef17541d3919aed2a008aabaa7f4
 Author: Lorenz Kästle <12514511+rincewinds...@users.noreply.github.com>
   Date: Wed Mar 12 22:01:46 2025 +0100
    URL: 
https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=6ac236c1

Refactor check_users

---

 plugins/Makefile.am            |   2 +
 plugins/check_users.c          | 250 +++++++++++++++++++----------------------
 plugins/check_users.d/config.h |  20 ++++
 plugins/check_users.d/users.c  | 166 +++++++++++++++++++++++++++
 plugins/check_users.d/users.h  |  18 +++
 5 files changed, 319 insertions(+), 137 deletions(-)

diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 5cd20319..0a2a91aa 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -55,6 +55,7 @@ EXTRA_DIST = t \
                         check_game.d \
                         check_radius.d \
                         check_time.d \
+                        check_users.d \
                         check_nagios.d \
                         check_dbi.d \
                         check_real.d \
@@ -152,6 +153,7 @@ check_tcp_LDADD = $(SSLOBJS)
 check_time_LDADD = $(NETLIBS)
 check_ntp_time_LDADD = $(NETLIBS) $(MATHLIBS)
 check_ups_LDADD = $(NETLIBS)
+check_users_SOURCES = check_users.c check_users.d/users.c
 check_users_LDADD = $(BASEOBJS) $(WTSAPI32LIBS) $(SYSTEMDLIBS)
 check_by_ssh_LDADD = $(NETLIBS)
 check_ide_smart_LDADD = $(BASEOBJS)
diff --git a/plugins/check_users.c b/plugins/check_users.c
index e91ed4a0..61427f97 100644
--- a/plugins/check_users.c
+++ b/plugins/check_users.c
@@ -34,8 +34,15 @@ const char *progname = "check_users";
 const char *copyright = "2000-2024";
 const char *email = "devel@monitoring-plugins.org";
 
-#include "common.h"
-#include "utils.h"
+#include "check_users.d/users.h"
+#include "output.h"
+#include "perfdata.h"
+#include "states.h"
+#include "utils_base.h"
+#include "./common.h"
+#include "./utils.h"
+#include "check_users.d/config.h"
+#include "thresholds.h"
 
 #if HAVE_WTSAPI32_H
 #      include <windows.h>
@@ -53,29 +60,16 @@ const char *email = "devel@monitoring-plugins.org";
 #      include <systemd/sd-login.h>
 #endif
 
-#define possibly_set(a, b) ((a) == 0 ? (b) : 0)
+typedef struct process_argument_wrapper {
+       int errorcode;
+       check_users_config config;
+} check_users_config_wrapper;
+check_users_config_wrapper process_arguments(int /*argc*/, char ** /*argv*/);
 
-static int process_arguments(int, char **);
-static void print_help(void);
+void print_help(void);
 void print_usage(void);
 
-static char *warning_range = NULL;
-static char *critical_range = NULL;
-static thresholds *thlds = NULL;
-
 int main(int argc, char **argv) {
-       int users = -1;
-       int result = STATE_UNKNOWN;
-#if HAVE_WTSAPI32_H
-       WTS_SESSION_INFO *wtsinfo;
-       DWORD wtscount;
-       DWORD index;
-#elif HAVE_UTMPX_H
-       struct utmpx *putmpx;
-#else
-       char input_buffer[MAX_INPUT_BUFFER];
-#endif
-
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
@@ -83,133 +77,100 @@ int main(int argc, char **argv) {
        /* Parse extra opts if any */
        argv = np_extra_opts(&argc, argv, progname);
 
-       if (process_arguments(argc, argv) == ERROR) {
+       check_users_config_wrapper tmp_config = process_arguments(argc, argv);
+
+       if (tmp_config.errorcode == ERROR) {
                usage4(_("Could not parse arguments"));
        }
 
-       users = 0;
-
-#ifdef HAVE_LIBSYSTEMD
-       if (sd_booted() > 0) {
-               users = sd_get_sessions(NULL);
-       } else {
-#endif
-#if HAVE_WTSAPI32_H
-               if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, 
&wtsinfo, &wtscount)) {
-                       printf(_("Could not enumerate RD sessions: %d\n"), 
GetLastError());
-                       return STATE_UNKNOWN;
-               }
-
-               for (index = 0; index < wtscount; index++) {
-                       LPTSTR username;
-                       DWORD size;
-                       int len;
-
-                       if 
(!WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, 
wtsinfo[index].SessionId, WTSUserName, &username, &size)) {
-                               continue;
-                       }
-
-                       len = lstrlen(username);
-
-                       WTSFreeMemory(username);
-
-                       if (len == 0) {
-                               continue;
-                       }
-
-                       if (wtsinfo[index].State == WTSActive || 
wtsinfo[index].State == WTSDisconnected) {
-                               users++;
-                       }
-               }
+       check_users_config config = tmp_config.config;
 
-               WTSFreeMemory(wtsinfo);
-#elif HAVE_UTMPX_H
-       /* get currently logged users from utmpx */
-       setutxent();
-
-       while ((putmpx = getutxent()) != NULL) {
-               if (putmpx->ut_type == USER_PROCESS) {
-                       users++;
-               }
-       }
-
-       endutxent();
+#ifdef _WIN32
+#      if HAVE_WTSAPI32_H
+       get_num_of_users_wrapper user_wrapper = get_num_of_users_windows();
+#      else
+#              error Did not find WTSAPI32
+#      endif // HAVE_WTSAPI32_H
 #else
-       /* run the command */
-       child_process = spopen(WHO_COMMAND);
-       if (child_process == NULL) {
-               printf(_("Could not open pipe: %s\n"), WHO_COMMAND);
-               return STATE_UNKNOWN;
-       }
-
-       child_stderr = fdopen(child_stderr_array[fileno(child_process)], "r");
-       if (child_stderr == NULL) {
-               printf(_("Could not open stderr for %s\n"), WHO_COMMAND);
-       }
-
-       while (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_process)) {
-               /* increment 'users' on all lines except total user count */
-               if (input_buffer[0] != '#') {
-                       users++;
-                       continue;
-               }
-
-               /* get total logged in users */
-               if (sscanf(input_buffer, _("# users=%d"), &users) == 1) {
-                       break;
-               }
-       }
-
-       /* check STDERR */
-       if (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_stderr)) {
-               result = possibly_set(result, STATE_UNKNOWN);
+#      ifdef HAVE_LIBSYSTEMD
+       get_num_of_users_wrapper user_wrapper = get_num_of_users_systemd();
+#      elif HAVE_UTMPX_H
+       get_num_of_users_wrapper user_wrapper = get_num_of_users_utmp();
+#      else  // !HAVE_LIBSYSTEMD && !HAVE_UTMPX_H
+       get_num_of_users_wrapper user_wrapper = get_num_of_users_who_command();
+#      endif // HAVE_LIBSYSTEMD
+#endif     // _WIN32
+
+       mp_check overall = mp_check_init();
+       if (config.output_format_is_set) {
+               mp_set_format(config.output_format);
        }
-       (void)fclose(child_stderr);
+       mp_subcheck sc_users = mp_subcheck_init();
 
-       /* close the pipe */
-       if (spclose(child_process)) {
-               result = possibly_set(result, STATE_UNKNOWN);
+       if (user_wrapper.errorcode != 0) {
+               sc_users = mp_set_subcheck_state(sc_users, STATE_UNKNOWN);
+               sc_users.output = "Failed to retrieve number of users";
+               mp_add_subcheck_to_check(&overall, sc_users);
+               mp_exit(overall);
        }
-#endif
-#ifdef HAVE_LIBSYSTEMD
-       }
-#endif
-
        /* check the user count against warning and critical thresholds */
-       result = get_status((double)users, thlds);
 
-       if (result == STATE_UNKNOWN) {
-               printf("%s\n", _("Unable to read output"));
-       } else {
-               printf(_("USERS %s - %d users currently logged in |%s\n"), 
state_text(result), users,
-                          sperfdata_int("users", users, "", warning_range, 
critical_range, true, 0, false, 0));
+       mp_perfdata users_pd = {
+               .label = "users",
+               .value = mp_create_pd_value(user_wrapper.users),
+       };
+
+       users_pd = mp_pd_set_thresholds(users_pd, config.thresholds);
+       mp_add_perfdata_to_subcheck(&sc_users, users_pd);
+
+       int tmp_status = mp_get_pd_status(users_pd);
+       sc_users = mp_set_subcheck_state(sc_users, tmp_status);
+
+       switch (tmp_status) {
+       case STATE_WARNING:
+               xasprintf(&sc_users.output, "%d users currently logged in. This 
violates the warning threshold", user_wrapper.users);
+               break;
+       case STATE_CRITICAL:
+               xasprintf(&sc_users.output, "%d users currently logged in. This 
violates the critical threshold", user_wrapper.users);
+               break;
+       default:
+               xasprintf(&sc_users.output, "%d users currently logged in", 
user_wrapper.users);
        }
 
-       return result;
+       mp_add_subcheck_to_check(&overall, sc_users);
+       mp_exit(overall);
 }
 
+#define output_format_index CHAR_MAX + 1
+
 /* process command-line arguments */
-int process_arguments(int argc, char **argv) {
+check_users_config_wrapper process_arguments(int argc, char **argv) {
        static struct option longopts[] = {{"critical", required_argument, 0, 
'c'},
                                                                           
{"warning", required_argument, 0, 'w'},
                                                                           
{"version", no_argument, 0, 'V'},
                                                                           
{"help", no_argument, 0, 'h'},
+                                                                          
{"output-format", required_argument, 0, output_format_index},
                                                                           {0, 
0, 0, 0}};
 
        if (argc < 2) {
-               usage("\n");
+               usage(progname);
        }
 
-       int option_char;
+       char *warning_range = NULL;
+       char *critical_range = NULL;
+       check_users_config_wrapper result = {
+               .config = check_users_config_init(),
+               .errorcode = OK,
+       };
+
        while (true) {
-               int option = 0;
-               option_char = getopt_long(argc, argv, "+hVvc:w:", longopts, 
&option);
+               int counter = getopt_long(argc, argv, "+hVvc:w:", longopts, 
NULL);
 
-               if (option_char == -1 || option_char == EOF || option_char == 
1) {
+               if (counter == -1 || counter == EOF || counter == 1) {
                        break;
                }
 
-               switch (option_char) {
+               switch (counter) {
                case '?': /* print short usage statement if args not parsable */
                        usage5();
                case 'h': /* help */
@@ -224,31 +185,45 @@ int process_arguments(int argc, char **argv) {
                case 'w': /* warning */
                        warning_range = optarg;
                        break;
-               }
-       }
-
-       option_char = optind;
-
-       if (warning_range == NULL && argc > option_char) {
-               warning_range = argv[option_char++];
-       }
+               case output_format_index: {
+                       parsed_output_format parser = 
mp_parse_output_format(optarg);
+                       if (!parser.parsing_success) {
+                               // TODO List all available formats here, maybe 
add anothoer usage function
+                               printf("Invalid output format: %s\n", optarg);
+                               exit(STATE_UNKNOWN);
+                       }
 
-       if (critical_range == NULL && argc > option_char) {
-               critical_range = argv[option_char++];
+                       result.config.output_format_is_set = true;
+                       result.config.output_format = parser.output_format;
+                       break;
+               }
+               }
        }
 
-       /* this will abort in case of invalid ranges */
-       set_thresholds(&thlds, warning_range, critical_range);
-
-       if (!thlds->warning) {
-               usage4(_("Warning threshold must be a valid range expression"));
+       // TODO add proper verification for ranges here!
+       if (warning_range) {
+               mp_range_parsed tmp = mp_parse_range_string(warning_range);
+               if (tmp.error == MP_PARSING_SUCCES) {
+                       result.config.thresholds.warning = tmp.range;
+                       result.config.thresholds.warning_is_set = true;
+               } else {
+                       printf("Failed to parse warning range: %s", 
warning_range);
+                       exit(STATE_UNKNOWN);
+               }
        }
 
-       if (!thlds->critical) {
-               usage4(_("Critical threshold must be a valid range 
expression"));
+       if (critical_range) {
+               mp_range_parsed tmp = mp_parse_range_string(critical_range);
+               if (tmp.error == MP_PARSING_SUCCES) {
+                       result.config.thresholds.critical = tmp.range;
+                       result.config.thresholds.critical_is_set = true;
+               } else {
+                       printf("Failed to parse critical range: %s", 
critical_range);
+                       exit(STATE_UNKNOWN);
+               }
        }
 
-       return OK;
+       return result;
 }
 
 void print_help(void) {
@@ -271,6 +246,7 @@ void print_help(void) {
        printf("    %s\n", _("Set WARNING status if number of logged in users 
violates RANGE_EXPRESSION"));
        printf(" %s\n", "-c, --critical=RANGE_EXPRESSION");
        printf("    %s\n", _("Set CRITICAL status if number of logged in users 
violates RANGE_EXPRESSION"));
+       printf(UT_OUTPUT_FORMAT);
 
        printf(UT_SUPPORT);
 }
diff --git a/plugins/check_users.d/config.h b/plugins/check_users.d/config.h
new file mode 100644
index 00000000..26d3ee70
--- /dev/null
+++ b/plugins/check_users.d/config.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "output.h"
+#include "thresholds.h"
+
+typedef struct check_users_config {
+       mp_thresholds thresholds;
+
+       bool output_format_is_set;
+       mp_output_format output_format;
+} check_users_config;
+
+check_users_config check_users_config_init() {
+       check_users_config tmp = {
+               .thresholds = mp_thresholds_init(),
+
+               .output_format_is_set = false,
+       };
+       return tmp;
+}
diff --git a/plugins/check_users.d/users.c b/plugins/check_users.d/users.c
new file mode 100644
index 00000000..7969ae79
--- /dev/null
+++ b/plugins/check_users.d/users.c
@@ -0,0 +1,166 @@
+#include "./users.h"
+
+#ifdef _WIN32
+#      ifdef HAVE_WTSAPI32_H
+#              include <windows.h>
+#              include <wtsapi32.h>
+#              undef ERROR
+#              define ERROR -1
+
+get_num_of_users_wrapper get_num_of_users_windows() {
+       WTS_SESSION_INFO *wtsinfo;
+       DWORD wtscount;
+
+       get_num_of_users_wrapper result = {};
+
+       if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &wtsinfo, 
&wtscount)) {
+               // printf(_("Could not enumerate RD sessions: %d\n"), 
GetLastError());
+               result.error = WINDOWS_COULD_NOT_ENUMERATE_SESSIONS;
+               return result;
+       }
+
+       for (DWORD index = 0; index < wtscount; index++) {
+               LPTSTR username;
+               DWORD size;
+
+               if (!WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, 
wtsinfo[index].SessionId, WTSUserName, &username, &size)) {
+                       continue;
+               }
+
+               int len = lstrlen(username);
+
+               WTSFreeMemory(username);
+
+               if (len == 0) {
+                       continue;
+               }
+
+               if (wtsinfo[index].State == WTSActive || wtsinfo[index].State 
== WTSDisconnected) {
+                       result.users++;
+               }
+       }
+
+       WTSFreeMemory(wtsinfo);
+       return result;
+}
+#      else // HAVE_WTSAPI32_H
+#              error On windows but without the WTSAPI32 lib
+#      endif // HAVE_WTSAPI32_H
+
+#else // _WIN32
+
+#      include "../../config.h"
+
+#      ifdef HAVE_LIBSYSTEMD
+#              include <systemd/sd-daemon.h>
+#              include <systemd/sd-login.h>
+
+get_num_of_users_wrapper get_num_of_users_systemd() {
+       get_num_of_users_wrapper result = {};
+
+       // Test whether we booted with systemd
+       if (sd_booted() > 0) {
+               int users = sd_get_sessions(NULL);
+               if (users >= 0) {
+                       // Success
+                       result.users = users;
+                       return result;
+               }
+
+               // Failure! return the error code
+               result.errorcode = users;
+               return result;
+       }
+
+       // Looks like we are not running systemd,
+       // return with error here
+       result.errorcode = NO_SYSTEMD_ERROR;
+       return result;
+}
+#      endif
+
+#      ifdef HAVE_UTMPX_H
+#              include <utmpx.h>
+
+get_num_of_users_wrapper get_num_of_users_utmp() {
+       int users = 0;
+
+       /* get currently logged users from utmpx */
+       setutxent();
+
+       struct utmpx *putmpx;
+       while ((putmpx = getutxent()) != NULL) {
+               if (putmpx->ut_type == USER_PROCESS) {
+                       users++;
+               }
+       }
+
+       endutxent();
+
+       get_num_of_users_wrapper result = {
+               .errorcode = 0,
+               .users = users,
+       };
+
+       return result;
+}
+#      endif
+
+#      ifndef HAVE_WTSAPI32_H
+#              ifndef HAVE_LIBSYSTEMD
+#                      ifndef HAVE_UTMPX_H
+//  Fall back option here for the others (probably still not on windows)
+
+#                              include "../popen.h"
+#                              include "../common.h"
+#                              include "../utils.h"
+
+get_num_of_users_wrapper get_num_of_users_who_command() {
+       /* run the command */
+       child_process = spopen(WHO_COMMAND);
+       if (child_process == NULL) {
+               // printf(_("Could not open pipe: %s\n"), WHO_COMMAND);
+               get_num_of_users_wrapper result = {
+                       .errorcode = COULD_NOT_OPEN_PIPE,
+               };
+               return result;
+       }
+
+       child_stderr = fdopen(child_stderr_array[fileno(child_process)], "r");
+       if (child_stderr == NULL) {
+               // printf(_("Could not open stderr for %s\n"), WHO_COMMAND);
+               //  TODO this error should probably be reported
+       }
+
+       get_num_of_users_wrapper result = {};
+       char input_buffer[MAX_INPUT_BUFFER];
+       while (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_process)) {
+               /* increment 'users' on all lines except total user count */
+               if (input_buffer[0] != '#') {
+                       result.users++;
+                       continue;
+               }
+
+               /* get total logged in users */
+               if (sscanf(input_buffer, _("# users=%d"), &result.users) == 1) {
+                       break;
+               }
+       }
+
+       /* check STDERR */
+       if (fgets(input_buffer, MAX_INPUT_BUFFER - 1, child_stderr)) {
+               // if this fails, something broke and the result can not be 
relied upon or so is the theorie here
+               result.errorcode = STDERR_COULD_NOT_BE_READ;
+       }
+       (void)fclose(child_stderr);
+
+       /* close the pipe */
+       spclose(child_process);
+
+       return result;
+}
+
+#                      endif
+#              endif
+#      endif
+#endif
diff --git a/plugins/check_users.d/users.h b/plugins/check_users.d/users.h
new file mode 100644
index 00000000..aacba775
--- /dev/null
+++ b/plugins/check_users.d/users.h
@@ -0,0 +1,18 @@
+#pragma once
+
+typedef struct get_num_of_users_wrapper {
+       int errorcode;
+       int users;
+} get_num_of_users_wrapper;
+
+enum {
+       NO_SYSTEMD_ERROR = 64,
+       WINDOWS_COULD_NOT_ENUMERATE_SESSIONS,
+       COULD_NOT_OPEN_PIPE,
+       STDERR_COULD_NOT_BE_READ,
+};
+
+get_num_of_users_wrapper get_num_of_users_systemd();
+get_num_of_users_wrapper get_num_of_users_utmp();
+get_num_of_users_wrapper get_num_of_users_windows();
+get_num_of_users_wrapper get_num_of_users_who_command();

Reply via email to