1 /*****************************************************************************
3 * Monitoring check_by_ssh plugin
6 * Copyright (c) 2000-2024 Monitoring Plugins Development Team
10 * This file contains the check_by_ssh plugin
13 * This program is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation, either version 3 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27 *****************************************************************************/
29 const char *progname
= "check_by_ssh";
30 const char *copyright
= "2000-2024";
31 const char *email
= "devel@monitoring-plugins.org";
36 #include "utils_cmd.h"
39 # define NP_MAXARGS 1024
42 static int process_arguments(int /*argc*/, char ** /*argv*/);
43 static int validate_arguments(void);
44 static void comm_append(const char * /*str*/);
45 static void print_help(void);
46 void print_usage(void);
48 static unsigned int commands
= 0;
49 static unsigned int services
= 0;
50 static int skip_stdout
= 0;
51 static int skip_stderr
= 0;
52 static int warn_on_stderr
= 0;
53 static bool unknown_timeout
= false;
54 static char *remotecmd
= NULL
;
55 static char **commargv
= NULL
;
56 static int commargc
= 0;
57 static char *hostname
= NULL
;
58 static char *outputfile
= NULL
;
59 static char *host_shortname
= NULL
;
60 static char **service
;
61 static bool passive
= false;
62 static bool verbose
= false;
64 int main(int argc
, char **argv
) {
68 int result
= STATE_UNKNOWN
;
70 FILE *file_pointer
= NULL
;
75 comm_append(SSH_COMMAND
);
77 setlocale(LC_ALL
, "");
78 bindtextdomain(PACKAGE
, LOCALEDIR
);
81 /* Parse extra opts if any */
82 argv
= np_extra_opts(&argc
, argv
, progname
);
84 /* process arguments */
85 if (process_arguments(argc
, argv
) == ERROR
)
86 usage_va(_("Could not parse arguments"));
88 /* Set signal handling and alarm timeout */
89 if (signal(SIGALRM
, timeout_alarm_handler
) == SIG_ERR
) {
90 usage_va(_("Cannot catch SIGALRM"));
92 alarm(timeout_interval
);
96 printf("Command: %s\n", commargv
[0]);
97 for (int i
= 1; i
< commargc
; i
++)
98 printf("Argument %i: %s\n", i
, commargv
[i
]);
101 result
= cmd_run_array(commargv
, &chld_out
, &chld_err
, 0);
103 /* SSH returns 255 if connection attempt fails; include the first line of error output */
104 if (result
== 255 && unknown_timeout
) {
105 printf(_("SSH connection failed: %s\n"), chld_err
.lines
> 0 ? chld_err
.line
[0] : "(no error output)");
106 return STATE_UNKNOWN
;
110 for (size_t i
= 0; i
< chld_out
.lines
; i
++)
111 printf("stdout: %s\n", chld_out
.line
[i
]);
112 for (size_t i
= 0; i
< chld_err
.lines
; i
++)
113 printf("stderr: %s\n", chld_err
.line
[i
]);
116 if (skip_stdout
== -1) /* --skip-stdout specified without argument */
117 skip_stdout
= chld_out
.lines
;
118 if (skip_stderr
== -1) /* --skip-stderr specified without argument */
119 skip_stderr
= chld_err
.lines
;
121 /* UNKNOWN or worse if (non-skipped) output found on stderr */
122 if (chld_err
.lines
> (size_t)skip_stderr
) {
123 printf(_("Remote command execution failed: %s\n"), chld_err
.line
[skip_stderr
]);
125 return max_state_alt(result
, STATE_WARNING
);
126 return max_state_alt(result
, STATE_UNKNOWN
);
129 /* this is simple if we're not supposed to be passive.
130 * Wrap up quickly and keep the tricks below */
132 if (chld_out
.lines
> (size_t)skip_stdout
)
133 for (size_t i
= skip_stdout
; i
< chld_out
.lines
; i
++)
134 puts(chld_out
.line
[i
]);
136 printf(_("%s - check_by_ssh: Remote command '%s' returned status %d\n"), state_text(result
), remotecmd
, result
);
137 return result
; /* return error status from remote command */
145 if (!(file_pointer
= fopen(outputfile
, "a"))) {
146 printf(_("SSH WARNING: could not open %s\n"), outputfile
);
150 local_time
= time(NULL
);
152 for (size_t i
= skip_stdout
; i
< chld_out
.lines
; i
++) {
153 status_text
= chld_out
.line
[i
++];
154 if (i
== chld_out
.lines
|| strstr(chld_out
.line
[i
], "STATUS CODE: ") == NULL
)
155 die(STATE_UNKNOWN
, _("%s: Error parsing output\n"), progname
);
157 if (service
[commands
] && status_text
&& sscanf(chld_out
.line
[i
], "STATUS CODE: %d", &cresult
) == 1) {
158 fprintf(file_pointer
, "[%d] PROCESS_SERVICE_CHECK_RESULT;%s;%s;%d;%s\n", (int)local_time
, host_shortname
, service
[commands
++],
159 cresult
, status_text
);
163 /* Multiple commands and passive checking should always return OK */
167 /* process command-line arguments */
168 int process_arguments(int argc
, char **argv
) {
174 static struct option longopts
[] = {{"version", no_argument
, 0, 'V'},
175 {"help", no_argument
, 0, 'h'},
176 {"verbose", no_argument
, 0, 'v'},
177 {"fork", no_argument
, 0, 'f'},
178 {"timeout", required_argument
, 0, 't'},
179 {"unknown-timeout", no_argument
, 0, 'U'},
180 {"host", required_argument
, 0, 'H'}, /* backward compatibility */
181 {"hostname", required_argument
, 0, 'H'},
182 {"port", required_argument
, 0, 'p'},
183 {"output", required_argument
, 0, 'O'},
184 {"name", required_argument
, 0, 'n'},
185 {"services", required_argument
, 0, 's'},
186 {"identity", required_argument
, 0, 'i'},
187 {"user", required_argument
, 0, 'u'},
188 {"logname", required_argument
, 0, 'l'},
189 {"command", required_argument
, 0, 'C'},
190 {"skip", optional_argument
, 0, 'S'}, /* backwards compatibility */
191 {"skip-stdout", optional_argument
, 0, 'S'},
192 {"skip-stderr", optional_argument
, 0, 'E'},
193 {"warn-on-stderr", no_argument
, 0, 'W'},
194 {"proto1", no_argument
, 0, '1'},
195 {"proto2", no_argument
, 0, '2'},
196 {"use-ipv4", no_argument
, 0, '4'},
197 {"use-ipv6", no_argument
, 0, '6'},
198 {"ssh-option", required_argument
, 0, 'o'},
199 {"quiet", no_argument
, 0, 'q'},
200 {"configfile", optional_argument
, 0, 'F'},
206 for (c
= 1; c
< argc
; c
++)
207 if (strcmp("-to", argv
[c
]) == 0)
208 strcpy(argv
[c
], "-t");
211 c
= getopt_long(argc
, argv
, "Vvh1246fqt:UH:O:p:i:u:l:C:S::E::n:s:o:F:", longopts
, &option
);
213 if (c
== -1 || c
== EOF
)
217 case 'V': /* version */
218 print_revision(progname
, NP_VERSION
);
226 case 't': /* timeout period */
227 if (!is_integer(optarg
))
228 usage_va(_("Timeout interval must be a positive integer"));
230 timeout_interval
= atoi(optarg
);
233 unknown_timeout
= true;
238 case 'p': /* port number */
239 if (!is_integer(optarg
))
240 usage_va(_("Port must be a positive integer"));
244 case 'O': /* output file */
248 case 's': /* description of service to check */
250 service
= realloc(service
, (++services
) * sizeof(char *));
251 while ((p2
= index(p1
, ':'))) {
253 service
[services
- 1] = p1
;
254 service
= realloc(service
, (++services
) * sizeof(char *));
257 service
[services
- 1] = p1
;
259 case 'n': /* short name of host in the monitoring configuration */
260 host_shortname
= optarg
;
267 case 'l': /* login name */
271 case 'i': /* identity */
276 case '1': /* Pass these switches directly to ssh */
279 case '2': /* 1 to force version 1, 2 to force version 2 */
282 case '4': /* -4 for IPv4 */
285 case '6': /* -6 for IPv6 */
288 case 'f': /* fork to background */
291 case 'C': /* Command for remote machine */
294 xasprintf(&remotecmd
, "%s;echo STATUS CODE: $?;", remotecmd
);
295 xasprintf(&remotecmd
, "%s%s", remotecmd
, optarg
);
297 case 'S': /* skip n (or all) lines on stdout */
299 skip_stdout
= -1; /* skip all output on stdout */
300 else if (!is_integer(optarg
))
301 usage_va(_("skip-stdout argument must be an integer"));
303 skip_stdout
= atoi(optarg
);
305 case 'E': /* skip n (or all) lines on stderr */
307 skip_stderr
= -1; /* skip all output on stderr */
308 else if (!is_integer(optarg
))
309 usage_va(_("skip-stderr argument must be an integer"));
311 skip_stderr
= atoi(optarg
);
313 case 'W': /* exit with warning if there is an output on stderr */
316 case 'o': /* Extra options for the ssh command */
320 case 'q': /* Tell the ssh command to be quiet */
323 case 'F': /* ssh configfile */
333 if (hostname
== NULL
) {
335 die(STATE_UNKNOWN
, _("%s: You must provide a host name\n"), progname
);
337 hostname
= argv
[c
++];
340 if (strlen(remotecmd
) == 0) {
341 for (; c
< argc
; c
++)
342 if (strlen(remotecmd
) > 0)
343 xasprintf(&remotecmd
, "%s %s", remotecmd
, argv
[c
]);
345 xasprintf(&remotecmd
, "%s", argv
[c
]);
348 if (commands
> 1 || passive
)
349 xasprintf(&remotecmd
, "%s;echo STATUS CODE: $?;", remotecmd
);
351 if (remotecmd
== NULL
|| strlen(remotecmd
) <= 1)
352 usage_va(_("No remotecmd"));
354 comm_append(hostname
);
355 comm_append(remotecmd
);
357 return validate_arguments();
360 void comm_append(const char *str
) {
362 if (++commargc
> NP_MAXARGS
)
363 die(STATE_UNKNOWN
, _("%s: Argument limit of %d exceeded\n"), progname
, NP_MAXARGS
);
365 if ((commargv
= (char **)realloc(commargv
, (commargc
+ 1) * sizeof(char *))) == NULL
)
366 die(STATE_UNKNOWN
, _("Can not (re)allocate 'commargv' buffer\n"));
368 commargv
[commargc
- 1] = strdup(str
);
369 commargv
[commargc
] = NULL
;
372 int validate_arguments(void) {
373 if (remotecmd
== NULL
|| hostname
== NULL
)
376 if (passive
&& commands
!= services
)
377 die(STATE_UNKNOWN
, _("%s: In passive mode, you must provide a service name for each command.\n"), progname
);
379 if (passive
&& host_shortname
== NULL
)
380 die(STATE_UNKNOWN
, _("%s: In passive mode, you must provide the host short name from the monitoring configs.\n"), progname
);
385 void print_help(void) {
386 print_revision(progname
, NP_VERSION
);
388 printf("Copyright (c) 1999 Karl DeBisschop <kdebisschop@users.sourceforge.net>\n");
389 printf(COPYRIGHT
, copyright
, email
);
391 printf(_("This plugin uses SSH to execute commands on a remote host"));
397 printf(UT_HELP_VRSN
);
399 printf(UT_EXTRA_OPTS
);
401 printf(UT_HOST_PORT
, 'p', "none");
405 printf(" %s\n", "-1, --proto1");
406 printf(" %s\n", _("tell ssh to use Protocol 1 [optional]"));
407 printf(" %s\n", "-2, --proto2");
408 printf(" %s\n", _("tell ssh to use Protocol 2 [optional]"));
409 printf(" %s\n", "-S, --skip-stdout[=n]");
410 printf(" %s\n", _("Ignore all or (if specified) first n lines on STDOUT [optional]"));
411 printf(" %s\n", "-E, --skip-stderr[=n]");
412 printf(" %s\n", _("Ignore all or (if specified) first n lines on STDERR [optional]"));
413 printf(" %s\n", "-W, --warn-on-stderr]");
414 printf(" %s\n", _("Exit with an warning, if there is an output on STDERR"));
415 printf(" %s\n", "-f");
416 printf(" %s\n", _("tells ssh to fork rather than create a tty [optional]. This will always return OK if ssh is executed"));
417 printf(" %s\n", "-C, --command='COMMAND STRING'");
418 printf(" %s\n", _("command to execute on the remote machine"));
419 printf(" %s\n", "-l, --logname=USERNAME");
420 printf(" %s\n", _("SSH user name on remote host [optional]"));
421 printf(" %s\n", "-i, --identity=KEYFILE");
422 printf(" %s\n", _("identity of an authorized key [optional]"));
423 printf(" %s\n", "-O, --output=FILE");
424 printf(" %s\n", _("external command file for monitoring [optional]"));
425 printf(" %s\n", "-s, --services=LIST");
426 printf(" %s\n", _("list of monitoring service names, separated by ':' [optional]"));
427 printf(" %s\n", "-n, --name=NAME");
428 printf(" %s\n", _("short name of host in the monitoring configuration [optional]"));
429 printf(" %s\n", "-o, --ssh-option=OPTION");
430 printf(" %s\n", _("Call ssh with '-o OPTION' (may be used multiple times) [optional]"));
431 printf(" %s\n", "-F, --configfile");
432 printf(" %s\n", _("Tell ssh to use this configfile [optional]"));
433 printf(" %s\n", "-q, --quiet");
434 printf(" %s\n", _("Tell ssh to suppress warning and diagnostic messages [optional]"));
435 printf(UT_WARN_CRIT
);
436 printf(UT_CONN_TIMEOUT
, DEFAULT_SOCKET_TIMEOUT
);
437 printf(" %s\n", "-U, --unknown-timeout");
438 printf(" %s\n", _("Make connection problems return UNKNOWN instead of CRITICAL"));
441 printf(" %s\n", _("The most common mode of use is to refer to a local identity file with"));
442 printf(" %s\n", _("the '-i' option. In this mode, the identity pair should have a null"));
443 printf(" %s\n", _("passphrase and the public key should be listed in the authorized_keys"));
444 printf(" %s\n", _("file of the remote host. Usually the key will be restricted to running"));
445 printf(" %s\n", _("only one command on the remote server. If the remote SSH server tracks"));
446 printf(" %s\n", _("invocation arguments, the one remote program may be an agent that can"));
447 printf(" %s\n", _("execute additional commands as proxy"));
449 printf(" %s\n", _("To use passive mode, provide multiple '-C' options, and provide"));
450 printf(" %s\n", _("all of -O, -s, and -n options (servicelist order must match '-C'options)"));
452 printf("%s\n", _("Examples:"));
453 printf(" %s\n", "$ check_by_ssh -H localhost -n lh -s c1:c2:c3 -C uptime -C uptime -C uptime -O /tmp/foo");
454 printf(" %s\n", "$ cat /tmp/foo");
455 printf(" %s\n", "[1080933700] PROCESS_SERVICE_CHECK_RESULT;flint;c1;0; up 2 days");
456 printf(" %s\n", "[1080933700] PROCESS_SERVICE_CHECK_RESULT;flint;c2;0; up 2 days");
457 printf(" %s\n", "[1080933700] PROCESS_SERVICE_CHECK_RESULT;flint;c3;0; up 2 days");
462 void print_usage(void) {
463 printf("%s\n", _("Usage:"));
464 printf(" %s -H <host> -C <command> [-fqvU] [-1|-2] [-4|-6]\n"
465 " [-S [lines]] [-E [lines]] [-W] [-t timeout] [-i identity]\n"
466 " [-l user] [-n name] [-s servicelist] [-O outputfile]\n"
467 " [-p port] [-o ssh-option] [-F configfile]\n",