Merge pull request #2045 from RincewindsHat/fix/calloc_argument_order
[monitoring-plugins.git] / plugins / check_by_ssh.c
blob905b23939415096e2c9e60efc0ffc1e82df98ff6
1 /*****************************************************************************
3 * Monitoring check_by_ssh plugin
5 * License: GPL
6 * Copyright (c) 2000-2024 Monitoring Plugins Development Team
8 * Description:
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";
33 #include "common.h"
34 #include "utils.h"
35 #include "netutils.h"
36 #include "utils_cmd.h"
38 #ifndef NP_MAXARGS
39 # define NP_MAXARGS 1024
40 #endif
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) {
66 char *status_text;
67 int cresult;
68 int result = STATE_UNKNOWN;
69 time_t local_time;
70 FILE *file_pointer = NULL;
71 output chld_out;
72 output chld_err;
74 remotecmd = "";
75 comm_append(SSH_COMMAND);
77 setlocale(LC_ALL, "");
78 bindtextdomain(PACKAGE, LOCALEDIR);
79 textdomain(PACKAGE);
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);
94 /* run the command */
95 if (verbose) {
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;
109 if (verbose) {
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]);
124 if (warn_on_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 */
131 if (!passive) {
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]);
135 else
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 */
141 * Passive mode
144 /* process output */
145 if (!(file_pointer = fopen(outputfile, "a"))) {
146 printf(_("SSH WARNING: could not open %s\n"), outputfile);
147 exit(STATE_UNKNOWN);
150 local_time = time(NULL);
151 commands = 0;
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 */
164 return result;
167 /* process command-line arguments */
168 int process_arguments(int argc, char **argv) {
169 int c;
170 char *p1;
171 char *p2;
173 int option = 0;
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'},
201 {0, 0, 0, 0}};
203 if (argc < 2)
204 return ERROR;
206 for (c = 1; c < argc; c++)
207 if (strcmp("-to", argv[c]) == 0)
208 strcpy(argv[c], "-t");
210 while (1) {
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)
214 break;
216 switch (c) {
217 case 'V': /* version */
218 print_revision(progname, NP_VERSION);
219 exit(STATE_UNKNOWN);
220 case 'h': /* help */
221 print_help();
222 exit(STATE_UNKNOWN);
223 case 'v': /* help */
224 verbose = true;
225 break;
226 case 't': /* timeout period */
227 if (!is_integer(optarg))
228 usage_va(_("Timeout interval must be a positive integer"));
229 else
230 timeout_interval = atoi(optarg);
231 break;
232 case 'U':
233 unknown_timeout = true;
234 break;
235 case 'H': /* host */
236 hostname = optarg;
237 break;
238 case 'p': /* port number */
239 if (!is_integer(optarg))
240 usage_va(_("Port must be a positive integer"));
241 comm_append("-p");
242 comm_append(optarg);
243 break;
244 case 'O': /* output file */
245 outputfile = optarg;
246 passive = true;
247 break;
248 case 's': /* description of service to check */
249 p1 = optarg;
250 service = realloc(service, (++services) * sizeof(char *));
251 while ((p2 = index(p1, ':'))) {
252 *p2 = '\0';
253 service[services - 1] = p1;
254 service = realloc(service, (++services) * sizeof(char *));
255 p1 = p2 + 1;
257 service[services - 1] = p1;
258 break;
259 case 'n': /* short name of host in the monitoring configuration */
260 host_shortname = optarg;
261 break;
263 case 'u':
264 comm_append("-l");
265 comm_append(optarg);
266 break;
267 case 'l': /* login name */
268 comm_append("-l");
269 comm_append(optarg);
270 break;
271 case 'i': /* identity */
272 comm_append("-i");
273 comm_append(optarg);
274 break;
276 case '1': /* Pass these switches directly to ssh */
277 comm_append("-1");
278 break;
279 case '2': /* 1 to force version 1, 2 to force version 2 */
280 comm_append("-2");
281 break;
282 case '4': /* -4 for IPv4 */
283 comm_append("-4");
284 break;
285 case '6': /* -6 for IPv6 */
286 comm_append("-6");
287 break;
288 case 'f': /* fork to background */
289 comm_append("-f");
290 break;
291 case 'C': /* Command for remote machine */
292 commands++;
293 if (commands > 1)
294 xasprintf(&remotecmd, "%s;echo STATUS CODE: $?;", remotecmd);
295 xasprintf(&remotecmd, "%s%s", remotecmd, optarg);
296 break;
297 case 'S': /* skip n (or all) lines on stdout */
298 if (optarg == NULL)
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"));
302 else
303 skip_stdout = atoi(optarg);
304 break;
305 case 'E': /* skip n (or all) lines on stderr */
306 if (optarg == NULL)
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"));
310 else
311 skip_stderr = atoi(optarg);
312 break;
313 case 'W': /* exit with warning if there is an output on stderr */
314 warn_on_stderr = 1;
315 break;
316 case 'o': /* Extra options for the ssh command */
317 comm_append("-o");
318 comm_append(optarg);
319 break;
320 case 'q': /* Tell the ssh command to be quiet */
321 comm_append("-q");
322 break;
323 case 'F': /* ssh configfile */
324 comm_append("-F");
325 comm_append(optarg);
326 break;
327 default: /* help */
328 usage5();
332 c = optind;
333 if (hostname == NULL) {
334 if (c <= argc) {
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]);
344 else
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)
374 return ERROR;
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);
382 return OK;
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"));
393 printf("\n\n");
395 print_usage();
397 printf(UT_HELP_VRSN);
399 printf(UT_EXTRA_OPTS);
401 printf(UT_HOST_PORT, 'p', "none");
403 printf(UT_IPv46);
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"));
439 printf(UT_VERBOSE);
440 printf("\n");
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"));
448 printf("\n");
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)"));
451 printf("\n");
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");
459 printf(UT_SUPPORT);
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",
468 progname);