Merge pull request #2045 from RincewindsHat/fix/calloc_argument_order
[monitoring-plugins.git] / plugins / check_ssh.c
blob42a88cf979bea39d3e1cafeae8d67d0758e4800e
1 /*****************************************************************************
3 * Monitoring check_ssh plugin
5 * License: GPL
6 * Copyright (c) 2000-2024 Monitoring Plugins Development Team
8 * Description:
10 * This file contains the check_ssh plugin
12 * Try to connect to an SSH server at specified server and port
15 * This program is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation, either version 3 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *****************************************************************************/
31 const char *progname = "check_ssh";
32 const char *copyright = "2000-2024";
33 const char *email = "devel@monitoring-plugins.org";
35 #include "./common.h"
36 #include "./netutils.h"
37 #include "utils.h"
39 #ifndef MSG_DONTWAIT
40 # define MSG_DONTWAIT 0
41 #endif
43 #define SSH_DFL_PORT 22
44 #define BUFF_SZ 256
46 static int port = -1;
47 static char *server_name = NULL;
48 static char *remote_version = NULL;
49 static char *remote_protocol = NULL;
50 static bool verbose = false;
52 static int process_arguments(int /*argc*/, char ** /*argv*/);
53 static int validate_arguments(void);
54 static void print_help(void);
55 void print_usage(void);
57 static int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_protocol);
59 int main(int argc, char **argv) {
60 setlocale(LC_ALL, "");
61 bindtextdomain(PACKAGE, LOCALEDIR);
62 textdomain(PACKAGE);
64 /* Parse extra opts if any */
65 argv = np_extra_opts(&argc, argv, progname);
67 if (process_arguments(argc, argv) == ERROR)
68 usage4(_("Could not parse arguments"));
70 /* initialize alarm signal handling */
71 signal(SIGALRM, socket_timeout_alarm_handler);
73 alarm(socket_timeout);
75 /* ssh_connect exits if error is found */
76 int result = ssh_connect(server_name, port, remote_version, remote_protocol);
78 alarm(0);
80 return (result);
83 /* process command-line arguments */
84 int process_arguments(int argc, char **argv) {
85 static struct option longopts[] = {{"help", no_argument, 0, 'h'},
86 {"version", no_argument, 0, 'V'},
87 {"host", required_argument, 0, 'H'}, /* backward compatibility */
88 {"hostname", required_argument, 0, 'H'},
89 {"port", required_argument, 0, 'p'},
90 {"use-ipv4", no_argument, 0, '4'},
91 {"use-ipv6", no_argument, 0, '6'},
92 {"timeout", required_argument, 0, 't'},
93 {"verbose", no_argument, 0, 'v'},
94 {"remote-version", required_argument, 0, 'r'},
95 {"remote-protocol", required_argument, 0, 'P'},
96 {0, 0, 0, 0}};
98 if (argc < 2)
99 return ERROR;
101 for (int i = 1; i < argc; i++)
102 if (strcmp("-to", argv[i]) == 0)
103 strcpy(argv[i], "-t");
105 int option_char;
106 while (true) {
107 int option = 0;
108 option_char = getopt_long(argc, argv, "+Vhv46t:r:H:p:P:", longopts, &option);
110 if (option_char == -1 || option_char == EOF)
111 break;
113 switch (option_char) {
114 case '?': /* help */
115 usage5();
116 case 'V': /* version */
117 print_revision(progname, NP_VERSION);
118 exit(STATE_UNKNOWN);
119 case 'h': /* help */
120 print_help();
121 exit(STATE_UNKNOWN);
122 case 'v': /* verbose */
123 verbose = true;
124 break;
125 case 't': /* timeout period */
126 if (!is_integer(optarg))
127 usage2(_("Timeout interval must be a positive integer"), optarg);
128 else
129 socket_timeout = atoi(optarg);
130 break;
131 case '4':
132 address_family = AF_INET;
133 break;
134 case '6':
135 #ifdef USE_IPV6
136 address_family = AF_INET6;
137 #else
138 usage4(_("IPv6 support not available"));
139 #endif
140 break;
141 case 'r': /* remote version */
142 remote_version = optarg;
143 break;
144 case 'P': /* remote version */
145 remote_protocol = optarg;
146 break;
147 case 'H': /* host */
148 if (!is_host(optarg))
149 usage2(_("Invalid hostname/address"), optarg);
150 server_name = optarg;
151 break;
152 case 'p': /* port */
153 if (is_intpos(optarg)) {
154 port = atoi(optarg);
155 } else {
156 usage2(_("Port number must be a positive integer"), optarg);
161 option_char = optind;
162 if (server_name == NULL && option_char < argc) {
163 if (is_host(argv[option_char])) {
164 server_name = argv[option_char++];
168 if (port == -1 && option_char < argc) {
169 if (is_intpos(argv[option_char])) {
170 port = atoi(argv[option_char++]);
171 } else {
172 print_usage();
173 exit(STATE_UNKNOWN);
177 return validate_arguments();
180 int validate_arguments(void) {
181 if (server_name == NULL)
182 return ERROR;
183 if (port == -1) /* funky, but allows -p to override stray integer in args */
184 port = SSH_DFL_PORT;
185 return OK;
188 /************************************************************************
190 * Try to connect to SSH server at specified server and port
192 *-----------------------------------------------------------------------*/
194 int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_protocol) {
195 struct timeval tv;
196 gettimeofday(&tv, NULL);
198 int socket;
199 int result = my_tcp_connect(haddr, hport, &socket);
201 if (result != STATE_OK)
202 return result;
204 char *output = (char *)calloc(BUFF_SZ + 1, sizeof(char));
205 char *buffer = NULL;
206 ssize_t recv_ret = 0;
207 char *version_control_string = NULL;
208 ssize_t byte_offset = 0;
209 while ((version_control_string == NULL) && (recv_ret = recv(socket, output + byte_offset, BUFF_SZ - byte_offset, 0) > 0)) {
211 if (strchr(output, '\n')) { /* we've got at least one full line, start parsing*/
212 byte_offset = 0;
214 char *index = NULL;
215 int len = 0;
216 while ((index = strchr(output + byte_offset, '\n')) != NULL) {
217 /*Partition the buffer so that this line is a separate string,
218 * by replacing the newline with NUL*/
219 output[(index - output)] = '\0';
220 len = strlen(output + byte_offset);
222 if ((len >= 4) && (strncmp(output + byte_offset, "SSH-", 4) == 0)) {
223 /*if the string starts with SSH-, this _should_ be a valid version control string*/
224 version_control_string = output + byte_offset;
225 break;
228 /*the start of the next line (if one exists) will be after the current one (+ NUL)*/
229 byte_offset += (len + 1);
232 if (version_control_string == NULL) {
233 /* move unconsumed data to beginning of buffer, null rest */
234 memmove((void *)output, (void *)(output + byte_offset + 1), BUFF_SZ - len + 1);
235 memset(output + byte_offset + 1, 0, BUFF_SZ - byte_offset + 1);
237 /*start reading from end of current line chunk on next recv*/
238 byte_offset = strlen(output);
240 } else {
241 byte_offset += recv_ret;
245 if (recv_ret < 0) {
246 printf("SSH CRITICAL - %s", strerror(errno));
247 exit(STATE_CRITICAL);
250 if (version_control_string == NULL) {
251 printf("SSH CRITICAL - No version control string received");
252 exit(STATE_CRITICAL);
255 * "When the connection has been established, both sides MUST send an
256 * identification string. This identification string MUST be
258 * SSH-protoversion-softwareversion SP comments CR LF"
259 * - RFC 4253:4.2
261 strip(version_control_string);
262 if (verbose)
263 printf("%s\n", version_control_string);
265 char *ssh_proto = version_control_string + 4;
268 * We assume the protoversion is of the form Major.Minor, although
269 * this is not _strictly_ required. See
271 * "Both the 'protoversion' and 'softwareversion' strings MUST consist of
272 * printable US-ASCII characters, with the exception of whitespace
273 * characters and the minus sign (-)"
274 * - RFC 4253:4.2
275 * and,
277 * "As stated earlier, the 'protoversion' specified for this protocol is
278 * "2.0". Earlier versions of this protocol have not been formally
279 * documented, but it is widely known that they use 'protoversion' of
280 * "1.x" (e.g., "1.5" or "1.3")."
281 * - RFC 4253:5
283 char *ssh_server = ssh_proto + strspn(ssh_proto, "0123456789.") + 1; /* (+1 for the '-' separating protoversion from softwareversion) */
285 /* If there's a space in the version string, whatever's after the space is a comment
286 * (which is NOT part of the server name/version)*/
287 char *tmp = strchr(ssh_server, ' ');
288 if (tmp) {
289 ssh_server[tmp - ssh_server] = '\0';
291 if (strlen(ssh_proto) == 0 || strlen(ssh_server) == 0) {
292 printf(_("SSH CRITICAL - Invalid protocol version control string %s\n"), version_control_string);
293 exit(STATE_CRITICAL);
295 ssh_proto[strspn(ssh_proto, "0123456789. ")] = 0;
297 static char *rev_no = VERSION;
298 xasprintf(&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
299 send(socket, buffer, strlen(buffer), MSG_DONTWAIT);
300 if (verbose)
301 printf("%s\n", buffer);
303 if (remote_version && strcmp(remote_version, ssh_server)) {
304 printf(_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"), ssh_server, ssh_proto, remote_version);
305 close(socket);
306 exit(STATE_CRITICAL);
309 double elapsed_time = (double)deltime(tv) / 1.0e6;
310 if (remote_protocol && strcmp(remote_protocol, ssh_proto)) {
311 printf(_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s' | %s\n"), ssh_server, ssh_proto, remote_protocol,
312 fperfdata("time", elapsed_time, "s", false, 0, false, 0, true, 0, true, (int)socket_timeout));
313 close(socket);
314 exit(STATE_CRITICAL);
317 printf(_("SSH OK - %s (protocol %s) | %s\n"), ssh_server, ssh_proto,
318 fperfdata("time", elapsed_time, "s", false, 0, false, 0, true, 0, true, (int)socket_timeout));
319 close(socket);
320 exit(STATE_OK);
323 void print_help(void) {
324 char *myport;
325 xasprintf(&myport, "%d", SSH_DFL_PORT);
327 print_revision(progname, NP_VERSION);
329 printf("Copyright (c) 1999 Remi Paulmier <remi@sinfomic.fr>\n");
330 printf(COPYRIGHT, copyright, email);
332 printf("%s\n", _("Try to connect to an SSH server at specified server and port"));
334 printf("\n\n");
336 print_usage();
338 printf(UT_HELP_VRSN);
339 printf(UT_EXTRA_OPTS);
341 printf(UT_HOST_PORT, 'p', myport);
343 printf(UT_IPv46);
345 printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
347 printf(" %s\n", "-r, --remote-version=STRING");
348 printf(" %s\n", _("Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)"));
350 printf(" %s\n", "-P, --remote-protocol=STRING");
351 printf(" %s\n", _("Alert if protocol doesn't match expected protocol version (ex: 2.0)"));
353 printf(UT_VERBOSE);
355 printf(UT_SUPPORT);
358 void print_usage(void) {
359 printf("%s\n", _("Usage:"));
360 printf("%s [-4|-6] [-t <timeout>] [-r <remote version>] [-p <port>] <host>\n", progname);