summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-11-16 14:32:03 +0100
committerLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2025-11-16 14:36:10 +0100
commite77ce530c44178521b7e0c4012feed1e8006e41e (patch)
tree1e004cf1c4004fbf3b862e0819d835ecf8060ffd
parent7bfb16e0da721dcf50558f9104d3ed84efe03516 (diff)
downloadmonitoring-plugins-e77ce530c44178521b7e0c4012feed1e8006e41e.tar.gz
check_by_ssh: Implement modern output functionality
-rw-r--r--plugins/check_by_ssh.c184
-rw-r--r--plugins/check_by_ssh.d/config.h21
2 files changed, 152 insertions, 53 deletions
diff --git a/plugins/check_by_ssh.c b/plugins/check_by_ssh.c
index a43c0d34..ad385fbd 100644
--- a/plugins/check_by_ssh.c
+++ b/plugins/check_by_ssh.c
@@ -26,16 +26,17 @@
26 * 26 *
27 *****************************************************************************/ 27 *****************************************************************************/
28 28
29const char *progname = "check_by_ssh";
30const char *copyright = "2000-2024";
31const char *email = "devel@monitoring-plugins.org";
32
33#include "common.h" 29#include "common.h"
30#include "output.h"
34#include "utils.h" 31#include "utils.h"
35#include "utils_cmd.h" 32#include "utils_cmd.h"
36#include "check_by_ssh.d/config.h" 33#include "check_by_ssh.d/config.h"
37#include "states.h" 34#include "states.h"
38 35
36const char *progname = "check_by_ssh";
37const char *copyright = "2000-2024";
38const char *email = "devel@monitoring-plugins.org";
39
39#ifndef NP_MAXARGS 40#ifndef NP_MAXARGS
40# define NP_MAXARGS 1024 41# define NP_MAXARGS 1024
41#endif 42#endif
@@ -71,6 +72,10 @@ int main(int argc, char **argv) {
71 72
72 const check_by_ssh_config config = tmp_config.config; 73 const check_by_ssh_config config = tmp_config.config;
73 74
75 if (config.output_format_is_set) {
76 mp_set_format(config.output_format);
77 }
78
74 /* Set signal handling and alarm timeout */ 79 /* Set signal handling and alarm timeout */
75 if (signal(SIGALRM, timeout_alarm_handler) == SIG_ERR) { 80 if (signal(SIGALRM, timeout_alarm_handler) == SIG_ERR) {
76 usage_va(_("Cannot catch SIGALRM")); 81 usage_va(_("Cannot catch SIGALRM"));
@@ -85,62 +90,101 @@ int main(int argc, char **argv) {
85 } 90 }
86 } 91 }
87 92
88 output chld_out; 93 cmd_run_result child_result = cmd_run_array2(config.cmd.commargv, 0);
89 output chld_err; 94 mp_check overall = mp_check_init();
90 mp_state_enum result = cmd_run_array(config.cmd.commargv, &chld_out, &chld_err, 0);
91 95
92 /* SSH returns 255 if connection attempt fails; include the first line of error output */ 96 /* SSH returns 255 if connection attempt fails; include the first line of error output */
93 if (result == 255 && config.unknown_timeout) { 97 mp_subcheck sc_ssh_execution = mp_subcheck_init();
94 printf(_("SSH connection failed: %s\n"), 98 if (child_result.cmd_error_code == 255 && config.unknown_timeout) {
95 chld_err.lines > 0 ? chld_err.line[0] : "(no error output)"); 99 xasprintf(&sc_ssh_execution.output, "SSH connection failed: %s",
96 return STATE_UNKNOWN; 100 child_result.stderr.lines > 0 ? child_result.stderr.line[0]
101 : "(no error output)");
102
103 sc_ssh_execution = mp_set_subcheck_state(sc_ssh_execution, STATE_UNKNOWN);
104 mp_add_subcheck_to_check(&overall, sc_ssh_execution);
105 mp_exit(overall);
97 } 106 }
107 xasprintf(&sc_ssh_execution.output, "SSH connection succeeded");
108 sc_ssh_execution = mp_set_subcheck_state(sc_ssh_execution, STATE_OK);
109 mp_add_subcheck_to_check(&overall, sc_ssh_execution);
98 110
99 if (verbose) { 111 if (verbose) {
100 for (size_t i = 0; i < chld_out.lines; i++) { 112 for (size_t i = 0; i < child_result.stdout.lines; i++) {
101 printf("stdout: %s\n", chld_out.line[i]); 113 printf("stdout: %s\n", child_result.stdout.line[i]);
102 } 114 }
103 for (size_t i = 0; i < chld_err.lines; i++) { 115 for (size_t i = 0; i < child_result.stderr.lines; i++) {
104 printf("stderr: %s\n", chld_err.line[i]); 116 printf("stderr: %s\n", child_result.stderr.line[i]);
105 } 117 }
106 } 118 }
107 119
108 size_t skip_stdout = 0; 120 size_t skip_stdout = 0;
109 if (config.skip_stdout == -1) { /* --skip-stdout specified without argument */ 121 if (config.skip_stdout) { /* --skip-stdout specified without argument */
110 skip_stdout = chld_out.lines; 122 skip_stdout = child_result.stdout.lines;
111 } else { 123 } else {
112 skip_stdout = config.skip_stdout; 124 skip_stdout = config.stdout_lines_to_ignore;
113 } 125 }
114 126
115 size_t skip_stderr = 0; 127 size_t skip_stderr = 0;
116 if (config.skip_stderr == -1) { /* --skip-stderr specified without argument */ 128 if (config.skip_stderr) { /* --skip-stderr specified without argument */
117 skip_stderr = chld_err.lines; 129 skip_stderr = child_result.stderr.lines;
118 } else { 130 } else {
119 skip_stderr = config.skip_stderr; 131 skip_stderr = config.sterr_lines_to_ignore;
120 } 132 }
121 133
122 /* Allow UNKNOWN or WARNING state for (non-skipped) output found on stderr */ 134 /* Allow UNKNOWN or WARNING state for (non-skipped) output found on stderr */
123 if (chld_err.lines > (size_t)skip_stderr && (config.unknown_on_stderr || config.warn_on_stderr)) { 135 if (child_result.stderr.lines > skip_stderr &&
124 printf(_("Remote command execution failed: %s\n"), chld_err.line[skip_stderr]); 136 (config.unknown_on_stderr || config.warn_on_stderr)) {
137 mp_subcheck sc_stderr = mp_subcheck_init();
138 xasprintf(&sc_stderr.output, "remote command execution failed: %s",
139 child_result.stderr.line[skip_stderr]);
140
125 if (config.unknown_on_stderr) { 141 if (config.unknown_on_stderr) {
126 return max_state_alt(result, STATE_UNKNOWN); 142 sc_stderr = mp_set_subcheck_state(sc_stderr, STATE_UNKNOWN);
127 } else if (config.warn_on_stderr) { 143 }
128 return max_state_alt(result, STATE_WARNING); 144
145 if (config.warn_on_stderr) {
146 sc_stderr = mp_set_subcheck_state(sc_stderr, STATE_WARNING);
129 } 147 }
148
149 mp_add_subcheck_to_check(&overall, sc_stderr);
150 // TODO still exit here?
130 } 151 }
131 152
132 /* this is simple if we're not supposed to be passive. 153 /* this is simple if we're not supposed to be passive.
133 * Wrap up quickly and keep the tricks below */ 154 * Wrap up quickly and keep the tricks below */
134 if (!config.passive) { 155 if (!config.passive) {
135 if (chld_out.lines > (size_t)skip_stdout) { 156 mp_subcheck sc_active_check = mp_subcheck_init();
136 for (size_t i = skip_stdout; i < chld_out.lines; i++) { 157 xasprintf(&sc_active_check.output, "command stdout:");
137 puts(chld_out.line[i]); 158
159 if (child_result.stdout.lines > skip_stdout) {
160 for (size_t i = skip_stdout; i < child_result.stdout.lines; i++) {
161 xasprintf(&sc_active_check.output, "%s\n%s", sc_active_check.output,
162 child_result.stdout.line[i]);
138 } 163 }
139 } else { 164 } else {
140 printf(_("%s - check_by_ssh: Remote command '%s' returned status %d\n"), 165 xasprintf(&sc_active_check.output, "remote command '%s' returned status %d",
141 state_text(result), config.remotecmd, result); 166 config.remotecmd, child_result.cmd_error_code);
167 }
168
169 /* return error status from remote command */
170
171 switch (child_result.cmd_error_code) {
172 case 0:
173 sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_OK);
174 break;
175 case 1:
176 sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_WARNING);
177 break;
178 case 2:
179 sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_CRITICAL);
180 break;
181 default:
182 sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_UNKNOWN);
183 break;
142 } 184 }
143 return result; /* return error status from remote command */ 185
186 mp_add_subcheck_to_check(&overall, sc_active_check);
187 mp_exit(overall);
144 } 188 }
145 189
146 /* 190 /*
@@ -148,36 +192,57 @@ int main(int argc, char **argv) {
148 */ 192 */
149 193
150 /* process output */ 194 /* process output */
151 FILE *file_pointer = NULL; 195 mp_subcheck sc_passive_file = mp_subcheck_init();
152 if (!(file_pointer = fopen(config.outputfile, "a"))) { 196 FILE *output_file = NULL;
153 printf(_("SSH WARNING: could not open %s\n"), config.outputfile); 197 if (!(output_file = fopen(config.outputfile, "a"))) {
154 exit(STATE_UNKNOWN); 198 xasprintf(&sc_passive_file.output, "could not open %s", config.outputfile);
199 sc_passive_file = mp_set_subcheck_state(sc_passive_file, STATE_UNKNOWN);
200
201 mp_add_subcheck_to_check(&overall, sc_passive_file);
202 mp_exit(overall);
155 } 203 }
156 204
205 xasprintf(&sc_passive_file.output, "opened output file %s", config.outputfile);
206 sc_passive_file = mp_set_subcheck_state(sc_passive_file, STATE_OK);
207 mp_add_subcheck_to_check(&overall, sc_passive_file);
208
157 time_t local_time = time(NULL); 209 time_t local_time = time(NULL);
158 unsigned int commands = 0; 210 unsigned int commands = 0;
159 char *status_text; 211 char *status_text;
160 int cresult; 212 int cresult;
161 for (size_t i = skip_stdout; i < chld_out.lines; i++) { 213 mp_subcheck sc_parse_passive = mp_subcheck_init();
162 status_text = chld_out.line[i++]; 214 for (size_t i = skip_stdout; i < child_result.stdout.lines; i++) {
163 if (i == chld_out.lines || strstr(chld_out.line[i], "STATUS CODE: ") == NULL) { 215 status_text = child_result.stdout.line[i++];
164 die(STATE_UNKNOWN, _("%s: Error parsing output\n"), progname); 216 if (i == child_result.stdout.lines ||
217 strstr(child_result.stdout.line[i], "STATUS CODE: ") == NULL) {
218
219 sc_parse_passive = mp_set_subcheck_state(sc_parse_passive, STATE_UNKNOWN);
220 xasprintf(&sc_parse_passive.output, "failed to parse output");
221 mp_add_subcheck_to_check(&overall, sc_parse_passive);
222 mp_exit(overall);
165 } 223 }
166 224
167 if (config.service[commands] && status_text && 225 if (config.service[commands] && status_text &&
168 sscanf(chld_out.line[i], "STATUS CODE: %d", &cresult) == 1) { 226 sscanf(child_result.stdout.line[i], "STATUS CODE: %d", &cresult) == 1) {
169 fprintf(file_pointer, "[%d] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%d;%s\n", 227 fprintf(output_file, "[%d] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%d;%s\n", (int)local_time,
170 (int)local_time, config.host_shortname, config.service[commands++], cresult, 228 config.host_shortname, config.service[commands++], cresult, status_text);
171 status_text);
172 } 229 }
173 } 230 }
174 231
232 sc_parse_passive = mp_set_subcheck_state(sc_parse_passive, STATE_OK);
233 xasprintf(&sc_parse_passive.output, "parsed and wrote output");
234 mp_add_subcheck_to_check(&overall, sc_parse_passive);
235
175 /* Multiple commands and passive checking should always return OK */ 236 /* Multiple commands and passive checking should always return OK */
176 exit(result); 237 mp_exit(overall);
177} 238}
178 239
179/* process command-line arguments */ 240/* process command-line arguments */
180check_by_ssh_config_wrapper process_arguments(int argc, char **argv) { 241check_by_ssh_config_wrapper process_arguments(int argc, char **argv) {
242 enum {
243 output_format_index = CHAR_MAX + 1,
244 };
245
181 static struct option longopts[] = { 246 static struct option longopts[] = {
182 {"version", no_argument, 0, 'V'}, 247 {"version", no_argument, 0, 'V'},
183 {"help", no_argument, 0, 'h'}, 248 {"help", no_argument, 0, 'h'},
@@ -207,6 +272,7 @@ check_by_ssh_config_wrapper process_arguments(int argc, char **argv) {
207 {"ssh-option", required_argument, 0, 'o'}, 272 {"ssh-option", required_argument, 0, 'o'},
208 {"quiet", no_argument, 0, 'q'}, 273 {"quiet", no_argument, 0, 'q'},
209 {"configfile", optional_argument, 0, 'F'}, 274 {"configfile", optional_argument, 0, 'F'},
275 {"output-format", required_argument, 0, output_format_index},
210 {0, 0, 0, 0}}; 276 {0, 0, 0, 0}};
211 277
212 check_by_ssh_config_wrapper result = { 278 check_by_ssh_config_wrapper result = {
@@ -327,20 +393,27 @@ check_by_ssh_config_wrapper process_arguments(int argc, char **argv) {
327 break; 393 break;
328 case 'S': /* skip n (or all) lines on stdout */ 394 case 'S': /* skip n (or all) lines on stdout */
329 if (optarg == NULL) { 395 if (optarg == NULL) {
330 result.config.skip_stdout = -1; /* skip all output on stdout */ 396 result.config.skip_stdout = true; /* skip all output on stdout */
397
398 if (verbose) {
399 printf("Setting the skip_stdout flag\n");
400 }
331 } else if (!is_integer(optarg)) { 401 } else if (!is_integer(optarg)) {
332 usage_va(_("skip-stdout argument must be an integer")); 402 usage_va(_("skip-stdout argument must be an integer"));
333 } else { 403 } else {
334 result.config.skip_stdout = atoi(optarg); 404 result.config.stdout_lines_to_ignore = atoi(optarg);
335 } 405 }
336 break; 406 break;
337 case 'E': /* skip n (or all) lines on stderr */ 407 case 'E': /* skip n (or all) lines on stderr */
338 if (optarg == NULL) { 408 if (optarg == NULL) {
339 result.config.skip_stderr = -1; /* skip all output on stderr */ 409 result.config.skip_stderr = true; /* skip all output on stderr */
410 if (verbose) {
411 printf("Setting the skip_stderr flag\n");
412 }
340 } else if (!is_integer(optarg)) { 413 } else if (!is_integer(optarg)) {
341 usage_va(_("skip-stderr argument must be an integer")); 414 usage_va(_("skip-stderr argument must be an integer"));
342 } else { 415 } else {
343 result.config.skip_stderr = atoi(optarg); 416 result.config.sterr_lines_to_ignore = atoi(optarg);
344 } 417 }
345 break; 418 break;
346 case 'e': /* exit with unknown if there is an output on stderr */ 419 case 'e': /* exit with unknown if there is an output on stderr */
@@ -360,6 +433,18 @@ check_by_ssh_config_wrapper process_arguments(int argc, char **argv) {
360 result.config.cmd = comm_append(result.config.cmd, "-F"); 433 result.config.cmd = comm_append(result.config.cmd, "-F");
361 result.config.cmd = comm_append(result.config.cmd, optarg); 434 result.config.cmd = comm_append(result.config.cmd, optarg);
362 break; 435 break;
436 case output_format_index: {
437 parsed_output_format parser = mp_parse_output_format(optarg);
438 if (!parser.parsing_success) {
439 // TODO List all available formats here, maybe add anothoer usage function
440 printf("Invalid output format: %s\n", optarg);
441 exit(STATE_UNKNOWN);
442 }
443
444 result.config.output_format_is_set = true;
445 result.config.output_format = parser.output_format;
446 break;
447 }
363 default: /* help */ 448 default: /* help */
364 usage5(); 449 usage5();
365 } 450 }
@@ -502,6 +587,7 @@ void print_help(void) {
502 printf(" %s\n", "-U, --unknown-timeout"); 587 printf(" %s\n", "-U, --unknown-timeout");
503 printf(" %s\n", _("Make connection problems return UNKNOWN instead of CRITICAL")); 588 printf(" %s\n", _("Make connection problems return UNKNOWN instead of CRITICAL"));
504 printf(UT_VERBOSE); 589 printf(UT_VERBOSE);
590 printf(UT_OUTPUT_FORMAT);
505 printf("\n"); 591 printf("\n");
506 printf(" %s\n", _("The most common mode of use is to refer to a local identity file with")); 592 printf(" %s\n", _("The most common mode of use is to refer to a local identity file with"));
507 printf(" %s\n", _("the '-i' option. In this mode, the identity pair should have a null")); 593 printf(" %s\n", _("the '-i' option. In this mode, the identity pair should have a null"));
diff --git a/plugins/check_by_ssh.d/config.h b/plugins/check_by_ssh.d/config.h
index 0e4b56d4..b6a57964 100644
--- a/plugins/check_by_ssh.d/config.h
+++ b/plugins/check_by_ssh.d/config.h
@@ -1,6 +1,7 @@
1#pragma once 1#pragma once
2 2
3#include "../../config.h" 3#include "../../config.h"
4#include "output.h"
4#include <stddef.h> 5#include <stddef.h>
5 6
6typedef struct { 7typedef struct {
@@ -23,10 +24,16 @@ typedef struct {
23 bool unknown_timeout; 24 bool unknown_timeout;
24 bool unknown_on_stderr; 25 bool unknown_on_stderr;
25 bool warn_on_stderr; 26 bool warn_on_stderr;
26 int skip_stdout; 27 bool skip_stdout;
27 int skip_stderr; 28 size_t stdout_lines_to_ignore;
29 bool skip_stderr;
30 size_t sterr_lines_to_ignore;
31
28 bool passive; 32 bool passive;
29 char *outputfile; 33 char *outputfile;
34
35 bool output_format_is_set;
36 mp_output_format output_format;
30} check_by_ssh_config; 37} check_by_ssh_config;
31 38
32check_by_ssh_config check_by_ssh_config_init() { 39check_by_ssh_config check_by_ssh_config_init() {
@@ -49,10 +56,16 @@ check_by_ssh_config check_by_ssh_config_init() {
49 .unknown_timeout = false, 56 .unknown_timeout = false,
50 .unknown_on_stderr = false, 57 .unknown_on_stderr = false,
51 .warn_on_stderr = false, 58 .warn_on_stderr = false,
52 .skip_stderr = 0, 59
53 .skip_stdout = 0, 60 .skip_stderr = false,
61 .stdout_lines_to_ignore = 0,
62 .skip_stdout = false,
63 .sterr_lines_to_ignore = 0,
64
54 .passive = false, 65 .passive = false,
55 .outputfile = NULL, 66 .outputfile = NULL,
67
68 .output_format_is_set = false,
56 }; 69 };
57 return tmp; 70 return tmp;
58} 71}