summaryrefslogtreecommitdiffstats
path: root/plugins/check_ssh.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/check_ssh.c')
-rw-r--r--plugins/check_ssh.c267
1 files changed, 180 insertions, 87 deletions
diff --git a/plugins/check_ssh.c b/plugins/check_ssh.c
index 42a88cf9..f6c8d551 100644
--- a/plugins/check_ssh.c
+++ b/plugins/check_ssh.c
@@ -28,6 +28,9 @@
28 * 28 *
29 *****************************************************************************/ 29 *****************************************************************************/
30 30
31#include "output.h"
32#include "perfdata.h"
33#include "states.h"
31const char *progname = "check_ssh"; 34const char *progname = "check_ssh";
32const char *copyright = "2000-2024"; 35const char *copyright = "2000-2024";
33const char *email = "devel@monitoring-plugins.org"; 36const char *email = "devel@monitoring-plugins.org";
@@ -35,26 +38,27 @@ const char *email = "devel@monitoring-plugins.org";
35#include "./common.h" 38#include "./common.h"
36#include "./netutils.h" 39#include "./netutils.h"
37#include "utils.h" 40#include "utils.h"
41#include "./check_ssh.d/config.h"
38 42
39#ifndef MSG_DONTWAIT 43#ifndef MSG_DONTWAIT
40# define MSG_DONTWAIT 0 44# define MSG_DONTWAIT 0
41#endif 45#endif
42 46
43#define SSH_DFL_PORT 22 47#define BUFF_SZ 256
44#define BUFF_SZ 256
45 48
46static int port = -1;
47static char *server_name = NULL;
48static char *remote_version = NULL;
49static char *remote_protocol = NULL;
50static bool verbose = false; 49static bool verbose = false;
51 50
52static int process_arguments(int /*argc*/, char ** /*argv*/); 51typedef struct process_arguments_wrapper {
53static int validate_arguments(void); 52 int errorcode;
53 check_ssh_config config;
54} process_arguments_wrapper;
55
56static process_arguments_wrapper process_arguments(int /*argc*/, char ** /*argv*/);
54static void print_help(void); 57static void print_help(void);
55void print_usage(void); 58void print_usage(void);
56 59
57static int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_protocol); 60static int ssh_connect(mp_check *overall, char *haddr, int hport, char *remote_version,
61 char *remote_protocol);
58 62
59int main(int argc, char **argv) { 63int main(int argc, char **argv) {
60 setlocale(LC_ALL, ""); 64 setlocale(LC_ALL, "");
@@ -64,51 +68,75 @@ int main(int argc, char **argv) {
64 /* Parse extra opts if any */ 68 /* Parse extra opts if any */
65 argv = np_extra_opts(&argc, argv, progname); 69 argv = np_extra_opts(&argc, argv, progname);
66 70
67 if (process_arguments(argc, argv) == ERROR) 71 process_arguments_wrapper tmp_config = process_arguments(argc, argv);
72
73 if (tmp_config.errorcode == ERROR) {
68 usage4(_("Could not parse arguments")); 74 usage4(_("Could not parse arguments"));
75 }
76
77 check_ssh_config config = tmp_config.config;
78
79 mp_check overall = mp_check_init();
80 if (config.output_format_is_set) {
81 mp_set_format(config.output_format);
82 }
69 83
70 /* initialize alarm signal handling */ 84 /* initialize alarm signal handling */
71 signal(SIGALRM, socket_timeout_alarm_handler); 85 signal(SIGALRM, socket_timeout_alarm_handler);
72
73 alarm(socket_timeout); 86 alarm(socket_timeout);
74 87
75 /* ssh_connect exits if error is found */ 88 /* ssh_connect exits if error is found */
76 int result = ssh_connect(server_name, port, remote_version, remote_protocol); 89 ssh_connect(&overall, config.server_name, config.port, config.remote_version,
90 config.remote_protocol);
77 91
78 alarm(0); 92 alarm(0);
79 93
80 return (result); 94 mp_exit(overall);
81} 95}
82 96
97#define output_format_index CHAR_MAX + 1
98
83/* process command-line arguments */ 99/* process command-line arguments */
84int process_arguments(int argc, char **argv) { 100process_arguments_wrapper process_arguments(int argc, char **argv) {
85 static struct option longopts[] = {{"help", no_argument, 0, 'h'}, 101 static struct option longopts[] = {
86 {"version", no_argument, 0, 'V'}, 102 {"help", no_argument, 0, 'h'},
87 {"host", required_argument, 0, 'H'}, /* backward compatibility */ 103 {"version", no_argument, 0, 'V'},
88 {"hostname", required_argument, 0, 'H'}, 104 {"host", required_argument, 0, 'H'}, /* backward compatibility */
89 {"port", required_argument, 0, 'p'}, 105 {"hostname", required_argument, 0, 'H'},
90 {"use-ipv4", no_argument, 0, '4'}, 106 {"port", required_argument, 0, 'p'},
91 {"use-ipv6", no_argument, 0, '6'}, 107 {"use-ipv4", no_argument, 0, '4'},
92 {"timeout", required_argument, 0, 't'}, 108 {"use-ipv6", no_argument, 0, '6'},
93 {"verbose", no_argument, 0, 'v'}, 109 {"timeout", required_argument, 0, 't'},
94 {"remote-version", required_argument, 0, 'r'}, 110 {"verbose", no_argument, 0, 'v'},
95 {"remote-protocol", required_argument, 0, 'P'}, 111 {"remote-version", required_argument, 0, 'r'},
96 {0, 0, 0, 0}}; 112 {"remote-protocol", required_argument, 0, 'P'},
97 113 {"output-format", required_argument, 0, output_format_index},
98 if (argc < 2) 114 {0, 0, 0, 0}};
99 return ERROR; 115
100 116 process_arguments_wrapper result = {
101 for (int i = 1; i < argc; i++) 117 .config = check_ssh_config_init(),
102 if (strcmp("-to", argv[i]) == 0) 118 .errorcode = OK,
119 };
120
121 if (argc < 2) {
122 result.errorcode = ERROR;
123 return result;
124 }
125
126 for (int i = 1; i < argc; i++) {
127 if (strcmp("-to", argv[i]) == 0) {
103 strcpy(argv[i], "-t"); 128 strcpy(argv[i], "-t");
129 }
130 }
104 131
105 int option_char; 132 int option_char;
106 while (true) { 133 while (true) {
107 int option = 0; 134 int option = 0;
108 option_char = getopt_long(argc, argv, "+Vhv46t:r:H:p:P:", longopts, &option); 135 option_char = getopt_long(argc, argv, "+Vhv46t:r:H:p:P:", longopts, &option);
109 136
110 if (option_char == -1 || option_char == EOF) 137 if (option_char == -1 || option_char == EOF) {
111 break; 138 break;
139 }
112 140
113 switch (option_char) { 141 switch (option_char) {
114 case '?': /* help */ 142 case '?': /* help */
@@ -123,10 +151,11 @@ int process_arguments(int argc, char **argv) {
123 verbose = true; 151 verbose = true;
124 break; 152 break;
125 case 't': /* timeout period */ 153 case 't': /* timeout period */
126 if (!is_integer(optarg)) 154 if (!is_intpos(optarg)) {
127 usage2(_("Timeout interval must be a positive integer"), optarg); 155 usage2(_("Timeout interval must be a positive integer"), optarg);
128 else 156 } else {
129 socket_timeout = atoi(optarg); 157 socket_timeout = (unsigned int)atoi(optarg);
158 }
130 break; 159 break;
131 case '4': 160 case '4':
132 address_family = AF_INET; 161 address_family = AF_INET;
@@ -139,50 +168,61 @@ int process_arguments(int argc, char **argv) {
139#endif 168#endif
140 break; 169 break;
141 case 'r': /* remote version */ 170 case 'r': /* remote version */
142 remote_version = optarg; 171 result.config.remote_version = optarg;
143 break; 172 break;
144 case 'P': /* remote version */ 173 case 'P': /* remote version */
145 remote_protocol = optarg; 174 result.config.remote_protocol = optarg;
146 break; 175 break;
147 case 'H': /* host */ 176 case 'H': /* host */
148 if (!is_host(optarg)) 177 if (!is_host(optarg)) {
149 usage2(_("Invalid hostname/address"), optarg); 178 usage2(_("Invalid hostname/address"), optarg);
150 server_name = optarg; 179 }
180 result.config.server_name = optarg;
151 break; 181 break;
152 case 'p': /* port */ 182 case 'p': /* port */
153 if (is_intpos(optarg)) { 183 if (is_intpos(optarg)) {
154 port = atoi(optarg); 184 result.config.port = atoi(optarg);
155 } else { 185 } else {
156 usage2(_("Port number must be a positive integer"), optarg); 186 usage2(_("Port number must be a positive integer"), optarg);
157 } 187 }
188 break;
189 case output_format_index: {
190 parsed_output_format parser = mp_parse_output_format(optarg);
191 if (!parser.parsing_success) {
192 // TODO List all available formats here, maybe add anothoer usage function
193 printf("Invalid output format: %s\n", optarg);
194 exit(STATE_UNKNOWN);
195 }
196
197 result.config.output_format_is_set = true;
198 result.config.output_format = parser.output_format;
199 break;
200 }
158 } 201 }
159 } 202 }
160 203
161 option_char = optind; 204 option_char = optind;
162 if (server_name == NULL && option_char < argc) { 205 if (result.config.server_name == NULL && option_char < argc) {
163 if (is_host(argv[option_char])) { 206 if (is_host(argv[option_char])) {
164 server_name = argv[option_char++]; 207 result.config.server_name = argv[option_char++];
165 } 208 }
166 } 209 }
167 210
168 if (port == -1 && option_char < argc) { 211 if (result.config.port == -1 && option_char < argc) {
169 if (is_intpos(argv[option_char])) { 212 if (is_intpos(argv[option_char])) {
170 port = atoi(argv[option_char++]); 213 result.config.port = atoi(argv[option_char++]);
171 } else { 214 } else {
172 print_usage(); 215 print_usage();
173 exit(STATE_UNKNOWN); 216 exit(STATE_UNKNOWN);
174 } 217 }
175 } 218 }
176 219
177 return validate_arguments(); 220 if (result.config.server_name == NULL) {
178} 221 result.errorcode = ERROR;
222 return result;
223 }
179 224
180int validate_arguments(void) { 225 return result;
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;
186} 226}
187 227
188/************************************************************************ 228/************************************************************************
@@ -191,36 +231,45 @@ int validate_arguments(void) {
191 * 231 *
192 *-----------------------------------------------------------------------*/ 232 *-----------------------------------------------------------------------*/
193 233
194int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_protocol) { 234int ssh_connect(mp_check *overall, char *haddr, int hport, char *desired_remote_version,
235 char *desired_remote_protocol) {
195 struct timeval tv; 236 struct timeval tv;
196 gettimeofday(&tv, NULL); 237 gettimeofday(&tv, NULL);
197 238
198 int socket; 239 int socket;
199 int result = my_tcp_connect(haddr, hport, &socket); 240 int result = my_tcp_connect(haddr, hport, &socket);
200 241
201 if (result != STATE_OK) 242 mp_subcheck connection_sc = mp_subcheck_init();
243 if (result != STATE_OK) {
244 connection_sc = mp_set_subcheck_state(connection_sc, STATE_CRITICAL);
245 xasprintf(&connection_sc.output,
246 "Failed to establish TCP connection to Host %s and Port %d", haddr, hport);
247 mp_add_subcheck_to_check(overall, connection_sc);
202 return result; 248 return result;
249 }
203 250
204 char *output = (char *)calloc(BUFF_SZ + 1, sizeof(char)); 251 char *output = (char *)calloc(BUFF_SZ + 1, sizeof(char));
205 char *buffer = NULL; 252 char *buffer = NULL;
206 ssize_t recv_ret = 0; 253 ssize_t recv_ret = 0;
207 char *version_control_string = NULL; 254 char *version_control_string = NULL;
208 ssize_t byte_offset = 0; 255 size_t byte_offset = 0;
209 while ((version_control_string == NULL) && (recv_ret = recv(socket, output + byte_offset, BUFF_SZ - byte_offset, 0) > 0)) { 256 while ((version_control_string == NULL) &&
257 (recv_ret = recv(socket, output + byte_offset, (unsigned long)(BUFF_SZ - byte_offset),
258 0) > 0)) {
210 259
211 if (strchr(output, '\n')) { /* we've got at least one full line, start parsing*/ 260 if (strchr(output, '\n')) { /* we've got at least one full line, start parsing*/
212 byte_offset = 0; 261 byte_offset = 0;
213 262
214 char *index = NULL; 263 char *index = NULL;
215 int len = 0;
216 while ((index = strchr(output + byte_offset, '\n')) != NULL) { 264 while ((index = strchr(output + byte_offset, '\n')) != NULL) {
217 /*Partition the buffer so that this line is a separate string, 265 /*Partition the buffer so that this line is a separate string,
218 * by replacing the newline with NUL*/ 266 * by replacing the newline with NUL*/
219 output[(index - output)] = '\0'; 267 output[(index - output)] = '\0';
220 len = strlen(output + byte_offset); 268 size_t len = strlen(output + byte_offset);
221 269
222 if ((len >= 4) && (strncmp(output + byte_offset, "SSH-", 4) == 0)) { 270 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*/ 271 /*if the string starts with SSH-, this _should_ be a valid version control
272 * string*/
224 version_control_string = output + byte_offset; 273 version_control_string = output + byte_offset;
225 break; 274 break;
226 } 275 }
@@ -230,27 +279,38 @@ int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_proto
230 } 279 }
231 280
232 if (version_control_string == NULL) { 281 if (version_control_string == NULL) {
233 /* move unconsumed data to beginning of buffer, null rest */ 282 /* move unconsumed data to beginning of buffer */
234 memmove((void *)output, (void *)(output + byte_offset + 1), BUFF_SZ - len + 1); 283 memmove((void *)output, (void *)(output + byte_offset), BUFF_SZ - byte_offset);
235 memset(output + byte_offset + 1, 0, BUFF_SZ - byte_offset + 1);
236 284
237 /*start reading from end of current line chunk on next recv*/ 285 /*start reading from end of current line chunk on next recv*/
238 byte_offset = strlen(output); 286 byte_offset = strlen(output);
287
288 /* NUL the rest of the buffer */
289 memset(output + byte_offset, 0, BUFF_SZ - byte_offset);
239 } 290 }
240 } else { 291 } else {
241 byte_offset += recv_ret; 292 byte_offset += (size_t)recv_ret;
242 } 293 }
243 } 294 }
244 295
245 if (recv_ret < 0) { 296 if (recv_ret < 0) {
246 printf("SSH CRITICAL - %s", strerror(errno)); 297 connection_sc = mp_set_subcheck_state(connection_sc, STATE_CRITICAL);
247 exit(STATE_CRITICAL); 298 xasprintf(&connection_sc.output, "%s - %s", "SSH CRITICAL - ", strerror(errno));
299 mp_add_subcheck_to_check(overall, connection_sc);
300 return OK;
248 } 301 }
249 302
250 if (version_control_string == NULL) { 303 if (version_control_string == NULL) {
251 printf("SSH CRITICAL - No version control string received"); 304 connection_sc = mp_set_subcheck_state(connection_sc, STATE_CRITICAL);
252 exit(STATE_CRITICAL); 305 xasprintf(&connection_sc.output, "%s", "SSH CRITICAL - No version control string received");
306 mp_add_subcheck_to_check(overall, connection_sc);
307 return OK;
253 } 308 }
309
310 connection_sc = mp_set_subcheck_state(connection_sc, STATE_OK);
311 xasprintf(&connection_sc.output, "%s", "Initial connection succeeded");
312 mp_add_subcheck_to_check(overall, connection_sc);
313
254 /* 314 /*
255 * "When the connection has been established, both sides MUST send an 315 * "When the connection has been established, both sides MUST send an
256 * identification string. This identification string MUST be 316 * identification string. This identification string MUST be
@@ -259,8 +319,9 @@ int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_proto
259 * - RFC 4253:4.2 319 * - RFC 4253:4.2
260 */ 320 */
261 strip(version_control_string); 321 strip(version_control_string);
262 if (verbose) 322 if (verbose) {
263 printf("%s\n", version_control_string); 323 printf("%s\n", version_control_string);
324 }
264 325
265 char *ssh_proto = version_control_string + 4; 326 char *ssh_proto = version_control_string + 4;
266 327
@@ -280,7 +341,8 @@ int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_proto
280 * "1.x" (e.g., "1.5" or "1.3")." 341 * "1.x" (e.g., "1.5" or "1.3")."
281 * - RFC 4253:5 342 * - RFC 4253:5
282 */ 343 */
283 char *ssh_server = ssh_proto + strspn(ssh_proto, "0123456789.") + 1; /* (+1 for the '-' separating protoversion from softwareversion) */ 344 char *ssh_server = ssh_proto + strspn(ssh_proto, "0123456789.") +
345 1; /* (+1 for the '-' separating protoversion from softwareversion) */
284 346
285 /* If there's a space in the version string, whatever's after the space is a comment 347 /* 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)*/ 348 * (which is NOT part of the server name/version)*/
@@ -288,41 +350,69 @@ int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_proto
288 if (tmp) { 350 if (tmp) {
289 ssh_server[tmp - ssh_server] = '\0'; 351 ssh_server[tmp - ssh_server] = '\0';
290 } 352 }
353
354 mp_subcheck protocol_validity_sc = mp_subcheck_init();
291 if (strlen(ssh_proto) == 0 || strlen(ssh_server) == 0) { 355 if (strlen(ssh_proto) == 0 || strlen(ssh_server) == 0) {
292 printf(_("SSH CRITICAL - Invalid protocol version control string %s\n"), version_control_string); 356 protocol_validity_sc = mp_set_subcheck_state(protocol_validity_sc, STATE_CRITICAL);
293 exit(STATE_CRITICAL); 357 xasprintf(&protocol_validity_sc.output, "Invalid protocol version control string %s",
358 version_control_string);
359 mp_add_subcheck_to_check(overall, protocol_validity_sc);
360 return OK;
294 } 361 }
362
363 protocol_validity_sc = mp_set_subcheck_state(protocol_validity_sc, STATE_OK);
364 xasprintf(&protocol_validity_sc.output, "Valid protocol version control string %s",
365 version_control_string);
366 mp_add_subcheck_to_check(overall, protocol_validity_sc);
367
295 ssh_proto[strspn(ssh_proto, "0123456789. ")] = 0; 368 ssh_proto[strspn(ssh_proto, "0123456789. ")] = 0;
296 369
297 static char *rev_no = VERSION; 370 static char *rev_no = VERSION;
298 xasprintf(&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no); 371 xasprintf(&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
299 send(socket, buffer, strlen(buffer), MSG_DONTWAIT); 372 send(socket, buffer, strlen(buffer), MSG_DONTWAIT);
300 if (verbose) 373 if (verbose) {
301 printf("%s\n", buffer); 374 printf("%s\n", buffer);
375 }
302 376
303 if (remote_version && strcmp(remote_version, ssh_server)) { 377 if (desired_remote_version && strcmp(desired_remote_version, ssh_server)) {
304 printf(_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"), ssh_server, ssh_proto, remote_version); 378 mp_subcheck remote_version_sc = mp_subcheck_init();
379 remote_version_sc = mp_set_subcheck_state(remote_version_sc, STATE_CRITICAL);
380 xasprintf(&remote_version_sc.output, _("%s (protocol %s) version mismatch, expected '%s'"),
381 ssh_server, ssh_proto, desired_remote_version);
305 close(socket); 382 close(socket);
306 exit(STATE_CRITICAL); 383 mp_add_subcheck_to_check(overall, remote_version_sc);
384 return OK;
307 } 385 }
308 386
309 double elapsed_time = (double)deltime(tv) / 1.0e6; 387 double elapsed_time = (double)deltime(tv) / 1.0e6;
310 if (remote_protocol && strcmp(remote_protocol, ssh_proto)) { 388 mp_perfdata time_pd = perfdata_init();
311 printf(_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s' | %s\n"), ssh_server, ssh_proto, remote_protocol, 389 time_pd.value = mp_create_pd_value(elapsed_time);
312 fperfdata("time", elapsed_time, "s", false, 0, false, 0, true, 0, true, (int)socket_timeout)); 390 time_pd.label = "time";
313 close(socket); 391 time_pd.max_present = true;
314 exit(STATE_CRITICAL); 392 time_pd.max = mp_create_pd_value(socket_timeout);
393
394 mp_subcheck protocol_version_sc = mp_subcheck_init();
395 mp_add_perfdata_to_subcheck(&protocol_version_sc, time_pd);
396
397 if (desired_remote_protocol && strcmp(desired_remote_protocol, ssh_proto)) {
398 protocol_version_sc = mp_set_subcheck_state(protocol_version_sc, STATE_CRITICAL);
399 xasprintf(&protocol_version_sc.output,
400 _("%s (protocol %s) protocol version mismatch, expected '%s'"), ssh_server,
401 ssh_proto, desired_remote_protocol);
402 } else {
403 protocol_version_sc = mp_set_subcheck_state(protocol_version_sc, STATE_OK);
404 xasprintf(&protocol_version_sc.output, "SSH server version: %s (protocol version: %s)",
405 ssh_server, ssh_proto);
315 } 406 }
316 407
317 printf(_("SSH OK - %s (protocol %s) | %s\n"), ssh_server, ssh_proto, 408 mp_add_subcheck_to_check(overall, protocol_version_sc);
318 fperfdata("time", elapsed_time, "s", false, 0, false, 0, true, 0, true, (int)socket_timeout));
319 close(socket); 409 close(socket);
320 exit(STATE_OK); 410 return OK;
321} 411}
322 412
323void print_help(void) { 413void print_help(void) {
324 char *myport; 414 char *myport;
325 xasprintf(&myport, "%d", SSH_DFL_PORT); 415 xasprintf(&myport, "%d", default_ssh_port);
326 416
327 print_revision(progname, NP_VERSION); 417 print_revision(progname, NP_VERSION);
328 418
@@ -345,10 +435,12 @@ void print_help(void) {
345 printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT); 435 printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
346 436
347 printf(" %s\n", "-r, --remote-version=STRING"); 437 printf(" %s\n", "-r, --remote-version=STRING");
348 printf(" %s\n", _("Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)")); 438 printf(" %s\n",
439 _("Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)"));
349 440
350 printf(" %s\n", "-P, --remote-protocol=STRING"); 441 printf(" %s\n", "-P, --remote-protocol=STRING");
351 printf(" %s\n", _("Alert if protocol doesn't match expected protocol version (ex: 2.0)")); 442 printf(" %s\n", _("Alert if protocol doesn't match expected protocol version (ex: 2.0)"));
443 printf(UT_OUTPUT_FORMAT);
352 444
353 printf(UT_VERBOSE); 445 printf(UT_VERBOSE);
354 446
@@ -357,5 +449,6 @@ void print_help(void) {
357 449
358void print_usage(void) { 450void print_usage(void) {
359 printf("%s\n", _("Usage:")); 451 printf("%s\n", _("Usage:"));
360 printf("%s [-4|-6] [-t <timeout>] [-r <remote version>] [-p <port>] <host>\n", progname); 452 printf("%s [-4|-6] [-t <timeout>] [-r <remote version>] [-p <port>] --hostname <host>\n",
453 progname);
361} 454}