monitoring-plugins/plugins/check_by_ssh.c

620 lines
21 KiB
C
Raw Normal View History

/*****************************************************************************
2024-10-30 21:43:12 -04:00
*
* Monitoring check_by_ssh plugin
*
* License: GPL
2024-10-30 21:44:09 -04:00
* Copyright (c) 2000-2024 Monitoring Plugins Development Team
2024-10-30 21:43:12 -04:00
*
* Description:
*
* This file contains the check_by_ssh plugin
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*****************************************************************************/
#include "common.h"
#include "output.h"
#include "utils.h"
#include "utils_cmd.h"
2025-03-10 10:02:46 -04:00
#include "check_by_ssh.d/config.h"
#include "states.h"
const char *progname = "check_by_ssh";
const char *copyright = "2000-2024";
const char *email = "devel@monitoring-plugins.org";
#ifndef NP_MAXARGS
2024-10-30 21:43:12 -04:00
# define NP_MAXARGS 1024
#endif
2025-03-10 10:02:46 -04:00
typedef struct {
int errorcode;
check_by_ssh_config config;
} check_by_ssh_config_wrapper;
static check_by_ssh_config_wrapper process_arguments(int /*argc*/, char ** /*argv*/);
2025-09-15 06:59:37 -04:00
static check_by_ssh_config_wrapper
validate_arguments(check_by_ssh_config_wrapper /*config_wrapper*/);
2025-03-10 10:02:46 -04:00
static command_construct comm_append(command_construct /*cmd*/, const char * /*str*/);
static void print_help(void);
2024-10-30 21:43:12 -04:00
void print_usage(void);
static bool verbose = false;
2024-10-30 21:43:12 -04:00
int main(int argc, char **argv) {
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
/* Parse extra opts if any */
2024-10-30 21:43:12 -04:00
argv = np_extra_opts(&argc, argv, progname);
2025-03-10 10:02:46 -04:00
check_by_ssh_config_wrapper tmp_config = process_arguments(argc, argv);
/* process arguments */
2025-03-10 10:02:46 -04:00
if (tmp_config.errorcode == ERROR) {
usage_va(_("Could not parse arguments"));
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
const check_by_ssh_config config = tmp_config.config;
if (config.output_format_is_set) {
mp_set_format(config.output_format);
}
/* Set signal handling and alarm timeout */
2024-10-30 21:43:12 -04:00
if (signal(SIGALRM, timeout_alarm_handler) == SIG_ERR) {
usage_va(_("Cannot catch SIGALRM"));
}
2024-10-30 21:43:12 -04:00
alarm(timeout_interval);
/* run the command */
if (verbose) {
2025-03-10 10:02:46 -04:00
printf("Command: %s\n", config.cmd.commargv[0]);
for (int i = 1; i < config.cmd.commargc; i++) {
printf("Argument %i: %s\n", i, config.cmd.commargv[i]);
2025-02-24 14:32:37 -05:00
}
}
cmd_run_result child_result = cmd_run_array2(config.cmd.commargv, 0);
mp_check overall = mp_check_init();
/* SSH returns 255 if connection attempt fails; include the first line of error output */
// we can sadly not detect other SSH errors
if (child_result.cmd_error_code == 255 && config.unknown_timeout) {
mp_subcheck sc_ssh_execution = mp_subcheck_init();
xasprintf(&sc_ssh_execution.output, "SSH connection failed: %s",
child_result.err.lines > 0 ? child_result.err.line[0]
: "(no error output)");
sc_ssh_execution = mp_set_subcheck_state(sc_ssh_execution, STATE_UNKNOWN);
mp_add_subcheck_to_check(&overall, sc_ssh_execution);
mp_exit(overall);
}
if (verbose) {
for (size_t i = 0; i < child_result.out.lines; i++) {
printf("stdout: %s\n", child_result.out.line[i]);
2025-02-24 14:32:37 -05:00
}
for (size_t i = 0; i < child_result.err.lines; i++) {
printf("stderr: %s\n", child_result.err.line[i]);
2025-02-24 14:32:37 -05:00
}
}
2025-03-10 10:02:46 -04:00
size_t skip_stdout = 0;
if (config.skip_stdout) { /* --skip-stdout specified without argument */
skip_stdout = child_result.out.lines;
2025-03-10 10:02:46 -04:00
} else {
skip_stdout = config.stdout_lines_to_ignore;
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
size_t skip_stderr = 0;
if (config.skip_stderr) { /* --skip-stderr specified without argument */
skip_stderr = child_result.err.lines;
2025-03-10 10:02:46 -04:00
} else {
skip_stderr = config.sterr_lines_to_ignore;
2025-02-24 14:32:37 -05:00
}
check_by_ssh: Ignore output on stderr by default check_by_ssh no longer returns UNKNOWN if ssh(1) returns data on stderr. But it can be enforced again by the new "--unknown-on-stderr" option. --- The default logic of check_by_ssh results in an UNKNOWN state if the ssh(1) process produces output on stderr. Using the "--skip-stderr=[n]" option allows ignoring a certain amount of lines or disabling this check altogether. Furthermore, passing the "--warn-on-stderr" option reduces the exit code to WARNING. The "--help" output does not document this behavior, only states that "--warn-on-stderr" will result in the WARNING, but does not mention the UNKNOWN by default. The man page of ssh(1) mentions that debug information is logged to stderr. This conflicts with the described logic, resulting in check_by_ssh to go UNKNOWN, unless additional options are set. Starting with OpenSSH version 10.1, ssh(1) will report warnings to stderr if the opposite server does not support post-quantum cryptography, <https://www.openssh.com/pq.html>. This change, slowly being rolled out throughout the next months/years, might result in mass-breakages of check_by_ssh. By introducing a new "--unknown-on-stderr" option, enforcing the prior default logic of an UNKNOWN state for data on stderr, and ignoring output on stderr by default, check_by_ssh will continue to work. One might even argue that this change converges actual implementation and the documented behavior, as argued above. --- $ ssh example '/usr/lib/nagios/plugins/check_dummy 0 demo' ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --warn-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 1 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --unknown-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 3 --- Fixes #2147.
2025-09-15 16:20:08 -04:00
/* Allow UNKNOWN or WARNING state for (non-skipped) output found on stderr */
if (child_result.err.lines > skip_stderr &&
(config.unknown_on_stderr || config.warn_on_stderr)) {
mp_subcheck sc_stderr = mp_subcheck_init();
xasprintf(&sc_stderr.output, "remote command execution failed: %s",
child_result.err.line[skip_stderr]);
check_by_ssh: Ignore output on stderr by default check_by_ssh no longer returns UNKNOWN if ssh(1) returns data on stderr. But it can be enforced again by the new "--unknown-on-stderr" option. --- The default logic of check_by_ssh results in an UNKNOWN state if the ssh(1) process produces output on stderr. Using the "--skip-stderr=[n]" option allows ignoring a certain amount of lines or disabling this check altogether. Furthermore, passing the "--warn-on-stderr" option reduces the exit code to WARNING. The "--help" output does not document this behavior, only states that "--warn-on-stderr" will result in the WARNING, but does not mention the UNKNOWN by default. The man page of ssh(1) mentions that debug information is logged to stderr. This conflicts with the described logic, resulting in check_by_ssh to go UNKNOWN, unless additional options are set. Starting with OpenSSH version 10.1, ssh(1) will report warnings to stderr if the opposite server does not support post-quantum cryptography, <https://www.openssh.com/pq.html>. This change, slowly being rolled out throughout the next months/years, might result in mass-breakages of check_by_ssh. By introducing a new "--unknown-on-stderr" option, enforcing the prior default logic of an UNKNOWN state for data on stderr, and ignoring output on stderr by default, check_by_ssh will continue to work. One might even argue that this change converges actual implementation and the documented behavior, as argued above. --- $ ssh example '/usr/lib/nagios/plugins/check_dummy 0 demo' ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --warn-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 1 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --unknown-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 3 --- Fixes #2147.
2025-09-15 16:20:08 -04:00
if (config.unknown_on_stderr) {
sc_stderr = mp_set_subcheck_state(sc_stderr, STATE_UNKNOWN);
}
if (config.warn_on_stderr) {
sc_stderr = mp_set_subcheck_state(sc_stderr, STATE_WARNING);
2025-02-24 14:32:37 -05:00
}
mp_add_subcheck_to_check(&overall, sc_stderr);
// TODO still exit here?
}
/* this is simple if we're not supposed to be passive.
* Wrap up quickly and keep the tricks below */
2025-03-10 10:02:46 -04:00
if (!config.passive) {
mp_subcheck sc_active_check = mp_subcheck_init();
xasprintf(&sc_active_check.output, "command stdout:");
if (child_result.out.lines > skip_stdout) {
for (size_t i = skip_stdout; i < child_result.out.lines; i++) {
xasprintf(&sc_active_check.output, "%s\n%s", sc_active_check.output,
child_result.out.line[i]);
2025-02-24 14:32:37 -05:00
}
} else {
xasprintf(&sc_active_check.output, "remote command '%s' returned status %d",
config.remotecmd, child_result.cmd_error_code);
}
/* return error status from remote command */
switch (child_result.cmd_error_code) {
case 0:
sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_OK);
break;
case 1:
sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_WARNING);
break;
case 2:
sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_CRITICAL);
break;
default:
sc_active_check = mp_set_subcheck_state(sc_active_check, STATE_UNKNOWN);
break;
2025-02-24 14:32:37 -05:00
}
mp_add_subcheck_to_check(&overall, sc_active_check);
mp_exit(overall);
}
/*
* Passive mode
*/
/* process output */
mp_subcheck sc_passive_file = mp_subcheck_init();
FILE *output_file = NULL;
if (!(output_file = fopen(config.outputfile, "a"))) {
xasprintf(&sc_passive_file.output, "could not open %s", config.outputfile);
sc_passive_file = mp_set_subcheck_state(sc_passive_file, STATE_UNKNOWN);
mp_add_subcheck_to_check(&overall, sc_passive_file);
mp_exit(overall);
}
xasprintf(&sc_passive_file.output, "opened output file %s", config.outputfile);
sc_passive_file = mp_set_subcheck_state(sc_passive_file, STATE_OK);
mp_add_subcheck_to_check(&overall, sc_passive_file);
2025-03-10 10:02:46 -04:00
time_t local_time = time(NULL);
unsigned int commands = 0;
char *status_text;
int cresult;
mp_subcheck sc_parse_passive = mp_subcheck_init();
for (size_t i = skip_stdout; i < child_result.out.lines; i++) {
status_text = child_result.out.line[i++];
if (i == child_result.out.lines ||
strstr(child_result.out.line[i], "STATUS CODE: ") == NULL) {
sc_parse_passive = mp_set_subcheck_state(sc_parse_passive, STATE_UNKNOWN);
xasprintf(&sc_parse_passive.output, "failed to parse output");
mp_add_subcheck_to_check(&overall, sc_parse_passive);
mp_exit(overall);
2025-02-24 14:32:37 -05:00
}
2024-10-30 21:43:12 -04:00
2025-09-15 06:59:37 -04:00
if (config.service[commands] && status_text &&
sscanf(child_result.out.line[i], "STATUS CODE: %d", &cresult) == 1) {
fprintf(output_file, "[%d] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%d;%s\n", (int)local_time,
config.host_shortname, config.service[commands++], cresult, status_text);
}
}
2024-10-30 21:43:12 -04:00
sc_parse_passive = mp_set_subcheck_state(sc_parse_passive, STATE_OK);
xasprintf(&sc_parse_passive.output, "parsed and wrote output");
mp_add_subcheck_to_check(&overall, sc_parse_passive);
/* Multiple commands and passive checking should always return OK */
mp_exit(overall);
}
/* process command-line arguments */
2025-03-10 10:02:46 -04:00
check_by_ssh_config_wrapper process_arguments(int argc, char **argv) {
enum {
output_format_index = CHAR_MAX + 1,
};
2025-09-15 06:59:37 -04:00
static struct option longopts[] = {
{"version", no_argument, 0, 'V'},
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"fork", no_argument, 0, 'f'},
{"timeout", required_argument, 0, 't'},
{"unknown-timeout", no_argument, 0, 'U'},
{"host", required_argument, 0, 'H'}, /* backward compatibility */
{"hostname", required_argument, 0, 'H'},
{"port", required_argument, 0, 'p'},
{"output", required_argument, 0, 'O'},
{"name", required_argument, 0, 'n'},
{"services", required_argument, 0, 's'},
{"identity", required_argument, 0, 'i'},
check_by_ssh: Ignore output on stderr by default check_by_ssh no longer returns UNKNOWN if ssh(1) returns data on stderr. But it can be enforced again by the new "--unknown-on-stderr" option. --- The default logic of check_by_ssh results in an UNKNOWN state if the ssh(1) process produces output on stderr. Using the "--skip-stderr=[n]" option allows ignoring a certain amount of lines or disabling this check altogether. Furthermore, passing the "--warn-on-stderr" option reduces the exit code to WARNING. The "--help" output does not document this behavior, only states that "--warn-on-stderr" will result in the WARNING, but does not mention the UNKNOWN by default. The man page of ssh(1) mentions that debug information is logged to stderr. This conflicts with the described logic, resulting in check_by_ssh to go UNKNOWN, unless additional options are set. Starting with OpenSSH version 10.1, ssh(1) will report warnings to stderr if the opposite server does not support post-quantum cryptography, <https://www.openssh.com/pq.html>. This change, slowly being rolled out throughout the next months/years, might result in mass-breakages of check_by_ssh. By introducing a new "--unknown-on-stderr" option, enforcing the prior default logic of an UNKNOWN state for data on stderr, and ignoring output on stderr by default, check_by_ssh will continue to work. One might even argue that this change converges actual implementation and the documented behavior, as argued above. --- $ ssh example '/usr/lib/nagios/plugins/check_dummy 0 demo' ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --warn-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 1 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --unknown-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 3 --- Fixes #2147.
2025-09-15 16:20:08 -04:00
{"user", required_argument, 0, 'u'}, /* backwards compatibility */
2025-09-15 06:59:37 -04:00
{"logname", required_argument, 0, 'l'},
{"command", required_argument, 0, 'C'},
{"skip", optional_argument, 0, 'S'}, /* backwards compatibility */
{"skip-stdout", optional_argument, 0, 'S'},
{"skip-stderr", optional_argument, 0, 'E'},
check_by_ssh: Ignore output on stderr by default check_by_ssh no longer returns UNKNOWN if ssh(1) returns data on stderr. But it can be enforced again by the new "--unknown-on-stderr" option. --- The default logic of check_by_ssh results in an UNKNOWN state if the ssh(1) process produces output on stderr. Using the "--skip-stderr=[n]" option allows ignoring a certain amount of lines or disabling this check altogether. Furthermore, passing the "--warn-on-stderr" option reduces the exit code to WARNING. The "--help" output does not document this behavior, only states that "--warn-on-stderr" will result in the WARNING, but does not mention the UNKNOWN by default. The man page of ssh(1) mentions that debug information is logged to stderr. This conflicts with the described logic, resulting in check_by_ssh to go UNKNOWN, unless additional options are set. Starting with OpenSSH version 10.1, ssh(1) will report warnings to stderr if the opposite server does not support post-quantum cryptography, <https://www.openssh.com/pq.html>. This change, slowly being rolled out throughout the next months/years, might result in mass-breakages of check_by_ssh. By introducing a new "--unknown-on-stderr" option, enforcing the prior default logic of an UNKNOWN state for data on stderr, and ignoring output on stderr by default, check_by_ssh will continue to work. One might even argue that this change converges actual implementation and the documented behavior, as argued above. --- $ ssh example '/usr/lib/nagios/plugins/check_dummy 0 demo' ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --warn-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 1 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --unknown-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 3 --- Fixes #2147.
2025-09-15 16:20:08 -04:00
{"unknown-on-stderr", no_argument, 0, 'e'},
2025-09-15 06:59:37 -04:00
{"warn-on-stderr", no_argument, 0, 'W'},
{"proto1", no_argument, 0, '1'},
{"proto2", no_argument, 0, '2'},
{"use-ipv4", no_argument, 0, '4'},
{"use-ipv6", no_argument, 0, '6'},
{"ssh-option", required_argument, 0, 'o'},
{"quiet", no_argument, 0, 'q'},
{"configfile", optional_argument, 0, 'F'},
{"output-format", required_argument, 0, output_format_index},
2025-09-15 06:59:37 -04:00
{0, 0, 0, 0}};
2025-03-10 10:02:46 -04:00
check_by_ssh_config_wrapper result = {
.errorcode = OK,
.config = check_by_ssh_config_init(),
};
2025-02-24 14:32:37 -05:00
if (argc < 2) {
2025-03-10 10:02:46 -04:00
result.errorcode = ERROR;
return result;
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
for (int index = 1; index < argc; index++) {
if (strcmp("-to", argv[index]) == 0) {
strcpy(argv[index], "-t");
2025-02-24 14:32:37 -05:00
}
}
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, SSH_COMMAND);
2025-03-10 10:02:46 -04:00
int option = 0;
while (true) {
2025-09-15 06:59:37 -04:00
int opt_index =
getopt_long(argc, argv, "Vvh1246fqt:UH:O:p:i:u:l:C:S::E::n:s:o:F:", longopts, &option);
2025-03-10 10:02:46 -04:00
if (opt_index == -1 || opt_index == EOF) {
break;
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
switch (opt_index) {
2024-10-30 21:43:12 -04:00
case 'V': /* version */
print_revision(progname, NP_VERSION);
exit(STATE_UNKNOWN);
case 'h': /* help */
print_help();
exit(STATE_UNKNOWN);
case 'v': /* help */
2023-10-18 10:12:41 -04:00
verbose = true;
break;
2024-10-30 21:43:12 -04:00
case 't': /* timeout period */
2025-02-24 14:32:37 -05:00
if (!is_integer(optarg)) {
usage_va(_("Timeout interval must be a positive integer"));
2025-02-24 14:32:37 -05:00
} else {
2024-10-30 21:43:12 -04:00
timeout_interval = atoi(optarg);
2025-02-24 14:32:37 -05:00
}
break;
case 'U':
2025-03-10 10:02:46 -04:00
result.config.unknown_timeout = true;
break;
2024-10-30 21:43:12 -04:00
case 'H': /* host */
2025-03-10 10:02:46 -04:00
result.config.hostname = optarg;
break;
case 'p': /* port number */
2025-02-24 14:32:37 -05:00
if (!is_integer(optarg)) {
usage_va(_("Port must be a positive integer"));
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-p");
result.config.cmd = comm_append(result.config.cmd, optarg);
break;
2024-10-30 21:43:12 -04:00
case 'O': /* output file */
2025-03-10 10:02:46 -04:00
result.config.outputfile = optarg;
result.config.passive = true;
break;
2025-03-10 10:02:46 -04:00
case 's': /* description of service to check */ {
char *p1;
char *p2;
p1 = optarg;
2025-09-15 06:59:37 -04:00
result.config.service = realloc(result.config.service,
(++result.config.number_of_services) * sizeof(char *));
2024-10-30 21:43:12 -04:00
while ((p2 = index(p1, ':'))) {
*p2 = '\0';
2025-03-10 10:02:46 -04:00
result.config.service[result.config.number_of_services - 1] = p1;
2025-09-15 06:59:37 -04:00
result.config.service = realloc(
result.config.service, (++result.config.number_of_services) * sizeof(char *));
p1 = p2 + 1;
}
2025-03-10 10:02:46 -04:00
result.config.service[result.config.number_of_services - 1] = p1;
break;
2024-10-30 21:43:12 -04:00
case 'n': /* short name of host in the monitoring configuration */
2025-03-10 10:02:46 -04:00
result.config.host_shortname = optarg;
} break;
case 'u':
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-l");
result.config.cmd = comm_append(result.config.cmd, optarg);
break;
2024-10-30 21:43:12 -04:00
case 'l': /* login name */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-l");
result.config.cmd = comm_append(result.config.cmd, optarg);
break;
2024-10-30 21:43:12 -04:00
case 'i': /* identity */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-i");
result.config.cmd = comm_append(result.config.cmd, optarg);
break;
2024-10-30 21:43:12 -04:00
case '1': /* Pass these switches directly to ssh */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-1");
break;
2024-10-30 21:43:12 -04:00
case '2': /* 1 to force version 1, 2 to force version 2 */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-2");
break;
2024-10-30 21:43:12 -04:00
case '4': /* -4 for IPv4 */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-4");
break;
2024-10-30 21:43:12 -04:00
case '6': /* -6 for IPv6 */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-6");
break;
2024-10-30 21:43:12 -04:00
case 'f': /* fork to background */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-f");
break;
2024-10-30 21:43:12 -04:00
case 'C': /* Command for remote machine */
2025-03-10 10:02:46 -04:00
result.config.commands++;
if (result.config.commands > 1) {
2025-09-15 06:59:37 -04:00
xasprintf(&result.config.remotecmd, "%s;echo STATUS CODE: $?;",
result.config.remotecmd);
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
xasprintf(&result.config.remotecmd, "%s%s", result.config.remotecmd, optarg);
break;
2024-10-30 21:43:12 -04:00
case 'S': /* skip n (or all) lines on stdout */
2025-02-24 14:32:37 -05:00
if (optarg == NULL) {
result.config.skip_stdout = true; /* skip all output on stdout */
if (verbose) {
printf("Setting the skip_stdout flag\n");
}
2025-02-24 14:32:37 -05:00
} else if (!is_integer(optarg)) {
usage_va(_("skip-stdout argument must be an integer"));
2025-02-24 14:32:37 -05:00
} else {
result.config.stdout_lines_to_ignore = atoi(optarg);
2025-02-24 14:32:37 -05:00
}
break;
2024-10-30 21:43:12 -04:00
case 'E': /* skip n (or all) lines on stderr */
2025-02-24 14:32:37 -05:00
if (optarg == NULL) {
result.config.skip_stderr = true; /* skip all output on stderr */
if (verbose) {
printf("Setting the skip_stderr flag\n");
}
2025-02-24 14:32:37 -05:00
} else if (!is_integer(optarg)) {
usage_va(_("skip-stderr argument must be an integer"));
2025-02-24 14:32:37 -05:00
} else {
result.config.sterr_lines_to_ignore = atoi(optarg);
2025-02-24 14:32:37 -05:00
}
break;
check_by_ssh: Ignore output on stderr by default check_by_ssh no longer returns UNKNOWN if ssh(1) returns data on stderr. But it can be enforced again by the new "--unknown-on-stderr" option. --- The default logic of check_by_ssh results in an UNKNOWN state if the ssh(1) process produces output on stderr. Using the "--skip-stderr=[n]" option allows ignoring a certain amount of lines or disabling this check altogether. Furthermore, passing the "--warn-on-stderr" option reduces the exit code to WARNING. The "--help" output does not document this behavior, only states that "--warn-on-stderr" will result in the WARNING, but does not mention the UNKNOWN by default. The man page of ssh(1) mentions that debug information is logged to stderr. This conflicts with the described logic, resulting in check_by_ssh to go UNKNOWN, unless additional options are set. Starting with OpenSSH version 10.1, ssh(1) will report warnings to stderr if the opposite server does not support post-quantum cryptography, <https://www.openssh.com/pq.html>. This change, slowly being rolled out throughout the next months/years, might result in mass-breakages of check_by_ssh. By introducing a new "--unknown-on-stderr" option, enforcing the prior default logic of an UNKNOWN state for data on stderr, and ignoring output on stderr by default, check_by_ssh will continue to work. One might even argue that this change converges actual implementation and the documented behavior, as argued above. --- $ ssh example '/usr/lib/nagios/plugins/check_dummy 0 demo' ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --warn-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 1 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --unknown-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 3 --- Fixes #2147.
2025-09-15 16:20:08 -04:00
case 'e': /* exit with unknown if there is an output on stderr */
result.config.unknown_on_stderr = true;
break;
2024-10-30 21:43:12 -04:00
case 'W': /* exit with warning if there is an output on stderr */
2025-03-10 10:02:46 -04:00
result.config.warn_on_stderr = true;
break;
2024-10-30 21:43:12 -04:00
case 'o': /* Extra options for the ssh command */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-o");
result.config.cmd = comm_append(result.config.cmd, optarg);
break;
2024-10-30 21:43:12 -04:00
case 'q': /* Tell the ssh command to be quiet */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-q");
break;
2024-10-30 21:43:12 -04:00
case 'F': /* ssh configfile */
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, "-F");
result.config.cmd = comm_append(result.config.cmd, optarg);
break;
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);
}
result.config.output_format_is_set = true;
result.config.output_format = parser.output_format;
break;
}
2024-10-30 21:43:12 -04:00
default: /* help */
usage5();
}
}
2025-03-10 10:02:46 -04:00
int c = optind;
if (result.config.hostname == NULL) {
if (c <= argc) {
2024-10-30 21:43:12 -04:00
die(STATE_UNKNOWN, _("%s: You must provide a host name\n"), progname);
}
2025-03-10 10:02:46 -04:00
result.config.hostname = argv[c++];
}
2025-03-10 10:02:46 -04:00
if (strlen(result.config.remotecmd) == 0) {
2025-02-24 14:32:37 -05:00
for (; c < argc; c++) {
2025-03-10 10:02:46 -04:00
if (strlen(result.config.remotecmd) > 0) {
xasprintf(&result.config.remotecmd, "%s %s", result.config.remotecmd, argv[c]);
2025-02-24 14:32:37 -05:00
} else {
2025-03-10 10:02:46 -04:00
xasprintf(&result.config.remotecmd, "%s", argv[c]);
2025-02-24 14:32:37 -05:00
}
}
}
2025-03-10 10:02:46 -04:00
if (result.config.commands > 1 || result.config.passive) {
xasprintf(&result.config.remotecmd, "%s;echo STATUS CODE: $?;", result.config.remotecmd);
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
if (result.config.remotecmd == NULL || strlen(result.config.remotecmd) <= 1) {
usage_va(_("No remotecmd"));
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
result.config.cmd = comm_append(result.config.cmd, result.config.hostname);
result.config.cmd = comm_append(result.config.cmd, result.config.remotecmd);
2025-03-10 10:02:46 -04:00
return validate_arguments(result);
}
2025-03-10 10:02:46 -04:00
command_construct comm_append(command_construct cmd, const char *str) {
if (verbose) {
for (int i = 0; i < cmd.commargc; i++) {
printf("Current command: [%i] %s\n", i, cmd.commargv[i]);
}
2025-03-10 10:02:46 -04:00
printf("Appending: %s\n", str);
}
if (++cmd.commargc > NP_MAXARGS) {
die(STATE_UNKNOWN, _("%s: Argument limit of %d exceeded\n"), progname, NP_MAXARGS);
2025-02-24 14:32:37 -05:00
}
2025-09-15 06:59:37 -04:00
if ((cmd.commargv = (char **)realloc(cmd.commargv, (cmd.commargc + 1) * sizeof(char *))) ==
NULL) {
die(STATE_UNKNOWN, _("Can not (re)allocate 'commargv' buffer\n"));
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
cmd.commargv[cmd.commargc - 1] = strdup(str);
cmd.commargv[cmd.commargc] = NULL;
return cmd;
}
2025-03-10 10:02:46 -04:00
check_by_ssh_config_wrapper validate_arguments(check_by_ssh_config_wrapper config_wrapper) {
if (config_wrapper.config.remotecmd == NULL || config_wrapper.config.hostname == NULL) {
config_wrapper.errorcode = ERROR;
return config_wrapper;
2025-02-24 14:32:37 -05:00
}
2025-09-15 06:59:37 -04:00
if (config_wrapper.config.passive &&
config_wrapper.config.commands != config_wrapper.config.number_of_services) {
die(STATE_UNKNOWN,
_("%s: In passive mode, you must provide a service name for each command.\n"),
progname);
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
if (config_wrapper.config.passive && config_wrapper.config.host_shortname == NULL) {
2025-09-15 06:59:37 -04:00
die(STATE_UNKNOWN,
_("%s: In passive mode, you must provide the host short name from the monitoring "
"configs.\n"),
progname);
2025-02-24 14:32:37 -05:00
}
2025-03-10 10:02:46 -04:00
return config_wrapper;
}
2024-10-30 21:43:12 -04:00
void print_help(void) {
print_revision(progname, NP_VERSION);
printf("Copyright (c) 1999 Karl DeBisschop <kdebisschop@users.sourceforge.net>\n");
printf(COPYRIGHT, copyright, email);
printf(_("This plugin uses SSH to execute commands on a remote host"));
printf("\n\n");
print_usage();
printf(UT_HELP_VRSN);
printf(UT_EXTRA_OPTS);
printf(UT_HOST_PORT, 'p', "none");
printf(UT_IPv46);
printf(" %s\n", "-1, --proto1");
printf(" %s\n", _("tell ssh to use Protocol 1 [optional]"));
printf(" %s\n", "-2, --proto2");
printf(" %s\n", _("tell ssh to use Protocol 2 [optional]"));
printf(" %s\n", "-S, --skip-stdout[=n]");
printf(" %s\n", _("Ignore all or (if specified) first n lines on STDOUT [optional]"));
printf(" %s\n", "-E, --skip-stderr[=n]");
printf(" %s\n", _("Ignore all or (if specified) first n lines on STDERR [optional]"));
check_by_ssh: Ignore output on stderr by default check_by_ssh no longer returns UNKNOWN if ssh(1) returns data on stderr. But it can be enforced again by the new "--unknown-on-stderr" option. --- The default logic of check_by_ssh results in an UNKNOWN state if the ssh(1) process produces output on stderr. Using the "--skip-stderr=[n]" option allows ignoring a certain amount of lines or disabling this check altogether. Furthermore, passing the "--warn-on-stderr" option reduces the exit code to WARNING. The "--help" output does not document this behavior, only states that "--warn-on-stderr" will result in the WARNING, but does not mention the UNKNOWN by default. The man page of ssh(1) mentions that debug information is logged to stderr. This conflicts with the described logic, resulting in check_by_ssh to go UNKNOWN, unless additional options are set. Starting with OpenSSH version 10.1, ssh(1) will report warnings to stderr if the opposite server does not support post-quantum cryptography, <https://www.openssh.com/pq.html>. This change, slowly being rolled out throughout the next months/years, might result in mass-breakages of check_by_ssh. By introducing a new "--unknown-on-stderr" option, enforcing the prior default logic of an UNKNOWN state for data on stderr, and ignoring output on stderr by default, check_by_ssh will continue to work. One might even argue that this change converges actual implementation and the documented behavior, as argued above. --- $ ssh example '/usr/lib/nagios/plugins/check_dummy 0 demo' ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --warn-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 1 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --unknown-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 3 --- Fixes #2147.
2025-09-15 16:20:08 -04:00
printf(" %s\n", "-e, --unknown-on-stderr");
printf(" %s\n", _("Exit with UNKNOWN, if there is output on STDERR"));
printf(" %s\n", "-W, --warn-on-stderr");
printf(" %s\n", _("Exit with WARNING, if there is output on STDERR"));
2024-10-30 21:43:12 -04:00
printf(" %s\n", "-f");
2025-09-15 06:59:37 -04:00
printf(" %s\n", _("tells ssh to fork rather than create a tty [optional]. This will always "
"return OK if ssh is executed"));
2024-10-30 21:43:12 -04:00
printf(" %s\n", "-C, --command='COMMAND STRING'");
printf(" %s\n", _("command to execute on the remote machine"));
printf(" %s\n", "-l, --logname=USERNAME");
printf(" %s\n", _("SSH user name on remote host [optional]"));
printf(" %s\n", "-i, --identity=KEYFILE");
printf(" %s\n", _("identity of an authorized key [optional]"));
printf(" %s\n", "-O, --output=FILE");
printf(" %s\n", _("external command file for monitoring [optional]"));
printf(" %s\n", "-s, --services=LIST");
printf(" %s\n", _("list of monitoring service names, separated by ':' [optional]"));
printf(" %s\n", "-n, --name=NAME");
printf(" %s\n", _("short name of host in the monitoring configuration [optional]"));
printf(" %s\n", "-o, --ssh-option=OPTION");
printf(" %s\n", _("Call ssh with '-o OPTION' (may be used multiple times) [optional]"));
printf(" %s\n", "-F, --configfile");
printf(" %s\n", _("Tell ssh to use this configfile [optional]"));
printf(" %s\n", "-q, --quiet");
printf(" %s\n", _("Tell ssh to suppress warning and diagnostic messages [optional]"));
printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
printf(" %s\n", "-U, --unknown-timeout");
printf(" %s\n", _("Make connection problems return UNKNOWN instead of CRITICAL"));
printf(UT_VERBOSE);
printf(UT_OUTPUT_FORMAT);
2024-10-30 21:43:12 -04:00
printf("\n");
printf(" %s\n", _("The most common mode of use is to refer to a local identity file with"));
printf(" %s\n", _("the '-i' option. In this mode, the identity pair should have a null"));
printf(" %s\n", _("passphrase and the public key should be listed in the authorized_keys"));
printf(" %s\n", _("file of the remote host. Usually the key will be restricted to running"));
printf(" %s\n", _("only one command on the remote server. If the remote SSH server tracks"));
printf(" %s\n", _("invocation arguments, the one remote program may be an agent that can"));
printf(" %s\n", _("execute additional commands as proxy"));
printf("\n");
printf(" %s\n", _("To use passive mode, provide multiple '-C' options, and provide"));
printf(" %s\n", _("all of -O, -s, and -n options (servicelist order must match '-C'options)"));
printf("\n");
2024-10-30 21:43:12 -04:00
printf("%s\n", _("Examples:"));
2025-11-16 09:27:58 -05:00
printf(" %s\n", "$ check_by_ssh -H localhost -n lh -s c1:c2:c3 -C uptime -C uptime -C "
"uptime -O /tmp/foo");
2024-10-30 21:43:12 -04:00
printf(" %s\n", "$ cat /tmp/foo");
printf(" %s\n", "[1080933700] PROCESS_SERVICE_CHECK_RESULT;flint;c1;0; up 2 days");
printf(" %s\n", "[1080933700] PROCESS_SERVICE_CHECK_RESULT;flint;c2;0; up 2 days");
printf(" %s\n", "[1080933700] PROCESS_SERVICE_CHECK_RESULT;flint;c3;0; up 2 days");
printf(UT_SUPPORT);
}
2024-10-30 21:43:12 -04:00
void print_usage(void) {
printf("%s\n", _("Usage:"));
printf(" %s -H <host> -C <command> [-fqvU] [-1|-2] [-4|-6]\n"
check_by_ssh: Ignore output on stderr by default check_by_ssh no longer returns UNKNOWN if ssh(1) returns data on stderr. But it can be enforced again by the new "--unknown-on-stderr" option. --- The default logic of check_by_ssh results in an UNKNOWN state if the ssh(1) process produces output on stderr. Using the "--skip-stderr=[n]" option allows ignoring a certain amount of lines or disabling this check altogether. Furthermore, passing the "--warn-on-stderr" option reduces the exit code to WARNING. The "--help" output does not document this behavior, only states that "--warn-on-stderr" will result in the WARNING, but does not mention the UNKNOWN by default. The man page of ssh(1) mentions that debug information is logged to stderr. This conflicts with the described logic, resulting in check_by_ssh to go UNKNOWN, unless additional options are set. Starting with OpenSSH version 10.1, ssh(1) will report warnings to stderr if the opposite server does not support post-quantum cryptography, <https://www.openssh.com/pq.html>. This change, slowly being rolled out throughout the next months/years, might result in mass-breakages of check_by_ssh. By introducing a new "--unknown-on-stderr" option, enforcing the prior default logic of an UNKNOWN state for data on stderr, and ignoring output on stderr by default, check_by_ssh will continue to work. One might even argue that this change converges actual implementation and the documented behavior, as argued above. --- $ ssh example '/usr/lib/nagios/plugins/check_dummy 0 demo' ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' OK: demo $ echo $? 0 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --warn-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 1 $ ./check_by_ssh -H example -C '/usr/lib/nagios/plugins/check_dummy 0 demo' --unknown-on-stderr Remote command execution failed: ** WARNING: connection is not using a post-quantum key exchange algorithm. $ echo $? 3 --- Fixes #2147.
2025-09-15 16:20:08 -04:00
" [-S [lines]] [-E [lines]] [-e|-W] [-t timeout] [-i identity]\n"
2024-10-30 21:43:12 -04:00
" [-l user] [-n name] [-s servicelist] [-O outputfile]\n"
" [-p port] [-o ssh-option] [-F configfile]\n",
progname);
}