summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLorenz Kästle <12514511+RincewindsHat@users.noreply.github.com>2024-03-26 23:35:16 (GMT)
committerGitHub <noreply@github.com>2024-03-26 23:35:16 (GMT)
commit66f62dd336832702a1e4f60cbfc4258de2c931cf (patch)
treeb78316a49a398db2fa89d70506fa158cbbf25d54
parent8698a6d976012908ea0af36ca1a520c3111928c7 (diff)
downloadmonitoring-plugins-66f62dd336832702a1e4f60cbfc4258de2c931cf.tar.gz
check_ssh: patches from op5 (#1738)
* check_ssh: properly parse a delayed version control string This resolves an issue with SSH servers which do not respond with their version control string as the first thing in the SSH protocol version exchange phase after connection establishment. This patch also makes sure that we disregard a potential comment in the version exchange string to avoid nonsense mismatches. In the future, we might want to add the capability to match against a user specified comment. In addition, the patch largely improves the communication towards the server, which adds better protocol adherence. Of course, new test cases are added to support the trigger and guard against regressions of the bugs solved by this patch. This fixes op5#7945 (https://bugs.op5.com/view.php?id=7945) Signed-off-by: Anton Lofgren <alofgren@op5.com> * check_ssh.t: Fix a few typos Signed-off-by: Anton Lofgren <alofgren@op5.com> * check_ssh: Handle non-alpha software versions This patch fixes a bug where we would reject version control strings that do not contain letters, because the assumption is made that they always do. This is not required by the RFC however, and there exist implementations that do not contain letters. I've also added a few references to the RFC to make the process of parsing the control string more apparent. This fixes op5#8716 (https://bugs.op5.com/view.php?id=8716) Signed-off-by: Anton Lofgren <alofgren@op5.com> * check_ssh: Fix a typo in "remote-protocol parameter remote-protcol -> remote-protocol Signed-off-by: Anton Lofgren <alofgren@op5.com> * Remove unused variable * Formating fixes * Update translations * Remove merge conflict artefact from previous merge * Set fixed include paths * Improve code style to be slightly more readable * Update test cases for different netcat behaviour and reduce sleep time --------- Signed-off-by: Anton Lofgren <alofgren@op5.com> Co-authored-by: Anton Lofgren <alofgren@op5.com>
-rw-r--r--plugins/check_ssh.c188
-rw-r--r--plugins/t/check_ssh.t122
2 files changed, 234 insertions, 76 deletions
diff --git a/plugins/check_ssh.c b/plugins/check_ssh.c
index 4eb746c..34ef37b 100644
--- a/plugins/check_ssh.c
+++ b/plugins/check_ssh.c
@@ -1,39 +1,39 @@
1/***************************************************************************** 1/*****************************************************************************
2* 2*
3* Monitoring check_ssh plugin 3* Monitoring check_ssh plugin
4* 4*
5* License: GPL 5* License: GPL
6* Copyright (c) 2000-2007 Monitoring Plugins Development Team 6* Copyright (c) 2000-2007 Monitoring Plugins Development Team
7* 7*
8* Description: 8* Description:
9* 9*
10* This file contains the check_ssh plugin 10* This file contains the check_ssh plugin
11* 11*
12* Try to connect to an SSH server at specified server and port 12* Try to connect to an SSH server at specified server and port
13* 13*
14* 14*
15* This program is free software: you can redistribute it and/or modify 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 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 17* the Free Software Foundation, either version 3 of the License, or
18* (at your option) any later version. 18* (at your option) any later version.
19* 19*
20* This program is distributed in the hope that it will be useful, 20* This program is distributed in the hope that it will be useful,
21* but WITHOUT ANY WARRANTY; without even the implied warranty of 21* but WITHOUT ANY WARRANTY; without even the implied warranty of
22* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23* GNU General Public License for more details. 23* GNU General Public License for more details.
24* 24*
25* You should have received a copy of the GNU General Public License 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/>. 26* along with this program. If not, see <http://www.gnu.org/licenses/>.
27* 27*
28* 28*
29*****************************************************************************/ 29*****************************************************************************/
30 30
31const char *progname = "check_ssh"; 31const char *progname = "check_ssh";
32const char *copyright = "2000-2007"; 32const char *copyright = "2000-2007";
33const char *email = "devel@monitoring-plugins.org"; 33const char *email = "devel@monitoring-plugins.org";
34 34
35#include "common.h" 35#include "./common.h"
36#include "netutils.h" 36#include "./netutils.h"
37#include "utils.h" 37#include "utils.h"
38 38
39#ifndef MSG_DONTWAIT 39#ifndef MSG_DONTWAIT
@@ -105,7 +105,7 @@ process_arguments (int argc, char **argv)
105 {"timeout", required_argument, 0, 't'}, 105 {"timeout", required_argument, 0, 't'},
106 {"verbose", no_argument, 0, 'v'}, 106 {"verbose", no_argument, 0, 'v'},
107 {"remote-version", required_argument, 0, 'r'}, 107 {"remote-version", required_argument, 0, 'r'},
108 {"remote-protcol", required_argument, 0, 'P'}, 108 {"remote-protocol", required_argument, 0, 'P'},
109 {0, 0, 0, 0} 109 {0, 0, 0, 0}
110 }; 110 };
111 111
@@ -214,7 +214,9 @@ ssh_connect (char *haddr, int hport, char *remote_version, char *remote_protocol
214{ 214{
215 int sd; 215 int sd;
216 int result; 216 int result;
217 char *output = NULL; 217 int len = 0;
218 ssize_t recv_ret = 0;
219 char *version_control_string = NULL;
218 char *buffer = NULL; 220 char *buffer = NULL;
219 char *ssh_proto = NULL; 221 char *ssh_proto = NULL;
220 char *ssh_server = NULL; 222 char *ssh_server = NULL;
@@ -229,52 +231,126 @@ ssh_connect (char *haddr, int hport, char *remote_version, char *remote_protocol
229 if (result != STATE_OK) 231 if (result != STATE_OK)
230 return result; 232 return result;
231 233
232 output = (char *) malloc (BUFF_SZ + 1); 234 char *output = (char *) calloc (BUFF_SZ + 1, sizeof(char));
233 memset (output, 0, BUFF_SZ + 1); 235
234 recv (sd, output, BUFF_SZ, 0); 236 unsigned int iteration = 0;
235 if (strncmp (output, "SSH", 3)) { 237 ssize_t byte_offset = 0;
236 printf (_("Server answer: %s"), output); 238
237 close(sd); 239 while ((version_control_string == NULL) && (recv_ret = recv(sd, output+byte_offset, BUFF_SZ - byte_offset, 0) > 0)) {
240
241 if (strchr(output, '\n')) { /* we've got at least one full line, start parsing*/
242 byte_offset = 0;
243
244 char *index = NULL;
245 while ((index = strchr(output+byte_offset, '\n')) != NULL) {
246 /*Partition the buffer so that this line is a separate string,
247 * by replacing the newline with NUL*/
248 output[(index - output)] = '\0';
249 len = strlen(output + byte_offset);
250
251 if ((len >= 4) && (strncmp (output+byte_offset, "SSH-", 4) == 0)) {
252 /*if the string starts with SSH-, this _should_ be a valid version control string*/
253 version_control_string = output+byte_offset;
254 break;
255 }
256
257 /*the start of the next line (if one exists) will be after the current one (+ NUL)*/
258 byte_offset += (len + 1);
259 }
260
261 if(version_control_string == NULL) {
262 /* move unconsumed data to beginning of buffer, null rest */
263 memmove((void *)output, (void *)output+byte_offset+1, BUFF_SZ - len+1);
264 memset(output+byte_offset+1, 0, BUFF_SZ-byte_offset+1);
265
266 /*start reading from end of current line chunk on next recv*/
267 byte_offset = strlen(output);
268 }
269 } else {
270 byte_offset += recv_ret;
271 }
272 }
273
274 if (recv_ret < 0) {
275 printf("SSH CRITICAL - %s", strerror(errno));
276 exit(STATE_CRITICAL);
277 }
278
279 if (version_control_string == NULL) {
280 printf("SSH CRITICAL - No version control string received");
281 exit(STATE_CRITICAL);
282 }
283 /*
284 * "When the connection has been established, both sides MUST send an
285 * identification string. This identification string MUST be
286 *
287 * SSH-protoversion-softwareversion SP comments CR LF"
288 * - RFC 4253:4.2
289 */
290 strip (version_control_string);
291 if (verbose)
292 printf ("%s\n", version_control_string);
293 ssh_proto = version_control_string + 4;
294
295 /*
296 * We assume the protoversion is of the form Major.Minor, although
297 * this is not _strictly_ required. See
298 *
299 * "Both the 'protoversion' and 'softwareversion' strings MUST consist of
300 * printable US-ASCII characters, with the exception of whitespace
301 * characters and the minus sign (-)"
302 * - RFC 4253:4.2
303 * and,
304 *
305 * "As stated earlier, the 'protoversion' specified for this protocol is
306 * "2.0". Earlier versions of this protocol have not been formally
307 * documented, but it is widely known that they use 'protoversion' of
308 * "1.x" (e.g., "1.5" or "1.3")."
309 * - RFC 4253:5
310 */
311 ssh_server = ssh_proto + strspn (ssh_proto, "0123456789.") + 1; /* (+1 for the '-' separating protoversion from softwareversion) */
312
313 /* If there's a space in the version string, whatever's after the space is a comment
314 * (which is NOT part of the server name/version)*/
315 char *tmp = strchr(ssh_server, ' ');
316 if (tmp) {
317 ssh_server[tmp - ssh_server] = '\0';
318 }
319 if (strlen(ssh_proto) == 0 || strlen(ssh_server) == 0) {
320 printf(_("SSH CRITICAL - Invalid protocol version control string %s\n"), version_control_string);
238 exit (STATE_CRITICAL); 321 exit (STATE_CRITICAL);
239 } 322 }
240 else { 323 ssh_proto[strspn (ssh_proto, "0123456789. ")] = 0;
241 strip (output);
242 if (verbose)
243 printf ("%s\n", output);
244 ssh_proto = output + 4;
245 ssh_server = ssh_proto + strspn (ssh_proto, "-0123456789. ");
246 ssh_proto[strspn (ssh_proto, "0123456789. ")] = 0;
247
248 xasprintf (&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
249 send (sd, buffer, strlen (buffer), MSG_DONTWAIT);
250 if (verbose)
251 printf ("%s\n", buffer);
252
253 if (remote_version && strcmp(remote_version, ssh_server)) {
254 printf
255 (_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"),
256 ssh_server, ssh_proto, remote_version);
257 close(sd);
258 exit (STATE_CRITICAL);
259 }
260 324
261 if (remote_protocol && strcmp(remote_protocol, ssh_proto)) { 325 xasprintf (&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
262 printf 326 send (sd, buffer, strlen (buffer), MSG_DONTWAIT);
263 (_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s'\n"), 327 if (verbose)
264 ssh_server, ssh_proto, remote_protocol); 328 printf ("%s\n", buffer);
265 close(sd);
266 exit (STATE_CRITICAL);
267 }
268 329
269 elapsed_time = (double)deltime(tv) / 1.0e6; 330 if (remote_version && strcmp(remote_version, ssh_server)) {
331 printf
332 (_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"),
333 ssh_server, ssh_proto, remote_version);
334 close(sd);
335 exit (STATE_CRITICAL);
336 }
270 337
338 if (remote_protocol && strcmp(remote_protocol, ssh_proto)) {
271 printf 339 printf
272 (_("SSH OK - %s (protocol %s) | %s\n"), 340 (_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s' | %s\n"),
273 ssh_server, ssh_proto, fperfdata("time", elapsed_time, "s", 341 ssh_server, ssh_proto, remote_protocol, fperfdata("time", elapsed_time, "s",
274 false, 0, false, 0, true, 0, true, (int)socket_timeout)); 342 false, 0, false, 0, true, 0, true, (int)socket_timeout));
275 close(sd); 343 close(sd);
276 exit (STATE_OK); 344 exit (STATE_CRITICAL);
277 } 345 }
346 elapsed_time = (double)deltime(tv) / 1.0e6;
347
348 printf
349 (_("SSH OK - %s (protocol %s) | %s\n"),
350 ssh_server, ssh_proto, fperfdata("time", elapsed_time, "s",
351 false, 0, false, 0, true, 0, true, (int)socket_timeout));
352 close(sd);
353 exit (STATE_OK);
278} 354}
279 355
280 356
@@ -292,7 +368,7 @@ print_help (void)
292 368
293 printf ("%s\n", _("Try to connect to an SSH server at specified server and port")); 369 printf ("%s\n", _("Try to connect to an SSH server at specified server and port"));
294 370
295 printf ("\n\n"); 371 printf ("\n\n");
296 372
297 print_usage (); 373 print_usage ();
298 374
@@ -306,10 +382,10 @@ print_help (void)
306 printf (UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT); 382 printf (UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
307 383
308 printf (" %s\n", "-r, --remote-version=STRING"); 384 printf (" %s\n", "-r, --remote-version=STRING");
309 printf (" %s\n", _("Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)")); 385 printf (" %s\n", _("Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)"));
310 386
311 printf (" %s\n", "-P, --remote-protocol=STRING"); 387 printf (" %s\n", "-P, --remote-protocol=STRING");
312 printf (" %s\n", _("Alert if protocol doesn't match expected protocol version (ex: 2.0)")); 388 printf (" %s\n", _("Alert if protocol doesn't match expected protocol version (ex: 2.0)"));
313 389
314 printf (UT_VERBOSE); 390 printf (UT_VERBOSE);
315 391
@@ -321,7 +397,7 @@ print_help (void)
321void 397void
322print_usage (void) 398print_usage (void)
323{ 399{
324 printf ("%s\n", _("Usage:")); 400 printf ("%s\n", _("Usage:"));
325 printf ("%s [-4|-6] [-t <timeout>] [-r <remote version>] [-p <port>] <host>\n", progname); 401 printf ("%s [-4|-6] [-t <timeout>] [-r <remote version>] [-p <port>] <host>\n", progname);
326} 402}
327 403
diff --git a/plugins/t/check_ssh.t b/plugins/t/check_ssh.t
index a5cd23c..907d33a 100644
--- a/plugins/t/check_ssh.t
+++ b/plugins/t/check_ssh.t
@@ -8,34 +8,116 @@ use strict;
8use Test::More; 8use Test::More;
9use NPTest; 9use NPTest;
10 10
11my $res;
12
11# Required parameters 13# Required parameters
12my $ssh_host = getTestParameter("NP_SSH_HOST", "A host providing SSH service", "localhost"); 14my $ssh_host = getTestParameter("NP_SSH_HOST",
13my $host_nonresponsive = getTestParameter("NP_HOST_NONRESPONSIVE", "The hostname of system not responsive to network requests", "10.0.0.1" ); 15 "A host providing SSH service",
14my $hostname_invalid = getTestParameter("NP_HOSTNAME_INVALID", "An invalid (not known to DNS) hostname", "nosuchhost" ); 16 "localhost");
17
18my $host_nonresponsive = getTestParameter("NP_HOST_NONRESPONSIVE",
19 "The hostname of system not responsive to network requests",
20 "10.0.0.1" );
21
22my $hostname_invalid = getTestParameter("NP_HOSTNAME_INVALID",
23 "An invalid (not known to DNS) hostname",
24 "nosuchhost" );
25
26
27plan tests => 14 + 6;
28
29SKIP: {
30 skip "SSH_HOST must be defined", 6 unless $ssh_host;
31 my $result = NPTest->testCmd(
32 "./check_ssh -H $ssh_host"
33 );
34 cmp_ok($result->return_code, '==', 0, "Exit with return code 0 (OK)");
35 like($result->output, '/^SSH OK - /', "Status text if command returned none (OK)");
36
37
38 $result = NPTest->testCmd(
39 "./check_ssh -H $host_nonresponsive -t 2"
40 );
41 cmp_ok($result->return_code, '==', 2, "Exit with return code 0 (OK)");
42 like($result->output, '/^CRITICAL - Socket timeout after 2 seconds/', "Status text if command returned none (OK)");
43
44
45
46 $result = NPTest->testCmd(
47 "./check_ssh -H $hostname_invalid -t 2"
48 );
49 cmp_ok($result->return_code, '==', 3, "Exit with return code 0 (OK)");
50 like($result->output, '/^check_ssh: Invalid hostname/', "Status text if command returned none (OK)");
51
15 52
53}
54SKIP: {
16 55
17plan skip_all => "SSH_HOST must be defined" unless $ssh_host; 56 skip "No netcat available", 14 unless (system("which nc > /dev/null") == 0);
18plan tests => 6;
19 57
58 # netcat on linux (on debian) will just keep the socket open if not advised otherwise
59 # therefore we add -q to close it after two seconds after receiving the EOF from input
60 my $nc_flags = "-l 5003 -N";
61 #A valid protocol version control string has the form
62 # SSH-protoversion-softwareversion SP comments CR LF
63 #
64 # where `comments` is optional, protoversion is the SSH protocol version and
65 # softwareversion is an arbitrary string representing the server software version
66 open(NC, "echo 'SSH-2.0-nagiosplug.ssh.0.1' | nc ${nc_flags}|");
67 sleep 0.1;
68 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
69 cmp_ok( $res->return_code, '==', 0, "Got SSH protocol version control string");
70 like( $res->output, '/^SSH OK - nagiosplug.ssh.0.1 \(protocol 2.0\)/', "Output OK");
71 close NC;
20 72
21my $result = NPTest->testCmd( 73 open(NC, "echo 'SSH-2.0-3.2.9.1' | nc ${nc_flags}|");
22 "./check_ssh -H $ssh_host" 74 sleep 0.1;
23 ); 75 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
24cmp_ok($result->return_code, '==', 0, "Exit with return code 0 (OK)"); 76 cmp_ok( $res->return_code, "==", 0, "Got SSH protocol version control string with non-alpha softwareversion string");
25like($result->output, '/^SSH OK - /', "Status text if command returned none (OK)"); 77 like( $res->output, '/^SSH OK - 3.2.9.1 \(protocol 2.0\)/', "Output OK for non-alpha softwareversion string");
78 close NC;
26 79
80 open(NC, "echo 'SSH-2.0-nagiosplug.ssh.0.1 this is a comment' | nc ${nc_flags} |");
81 sleep 0.1;
82 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003 -r nagiosplug.ssh.0.1" );
83 cmp_ok( $res->return_code, '==', 0, "Got SSH protocol version control string, and parsed comment appropriately");
84 like( $res->output, '/^SSH OK - nagiosplug.ssh.0.1 \(protocol 2.0\)/', "Output OK");
85 close NC;
27 86
28$result = NPTest->testCmd( 87 open(NC, "echo 'SSH-' | nc ${nc_flags}|");
29 "./check_ssh -H $host_nonresponsive -t 2" 88 sleep 0.1;
30 ); 89 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
31cmp_ok($result->return_code, '==', 2, "Exit with return code 0 (OK)"); 90 cmp_ok( $res->return_code, '==', 2, "Got invalid SSH protocol version control string");
32like($result->output, '/^CRITICAL - Socket timeout after 2 seconds/', "Status text if command returned none (OK)"); 91 like( $res->output, '/^SSH CRITICAL/', "Output OK");
92 close NC;
33 93
94 open(NC, "echo '' | nc ${nc_flags}|");
95 sleep 0.1;
96 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
97 cmp_ok( $res->return_code, '==', 2, "No version control string received");
98 like( $res->output, '/^SSH CRITICAL - No version control string received/', "Output OK");
99 close NC;
34 100
101 open(NC, "echo 'Not a version control string' | nc ${nc_flags}|");
102 sleep 0.1;
103 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
104 cmp_ok( $res->return_code, '==', 2, "No version control string received");
105 like( $res->output, '/^SSH CRITICAL - No version control string received/', "Output OK");
106 close NC;
35 107
36$result = NPTest->testCmd(
37 "./check_ssh -H $hostname_invalid -t 2"
38 );
39cmp_ok($result->return_code, '==', 3, "Exit with return code 0 (OK)");
40like($result->output, '/^check_ssh: Invalid hostname/', "Status text if command returned none (OK)");
41 108
109 #RFC 4253 permits servers to send any number of data lines prior to sending the protocol version control string
110 open(NC, "{ echo 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; sleep 0.5;
111 echo 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'; sleep 0.5;
112 echo 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC'; sleep 0.2;
113 echo 'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD'; sleep 0.3;
114 printf 'EEEEEEEEEEEEEEEEEE'; sleep 0.2;
115 printf 'EEEEEEEEEEEEEEEEEE\n'; sleep 0.2;
116 echo 'Some\nPrepended\nData\nLines\n'; sleep 0.2;
117 echo 'SSH-2.0-nagiosplug.ssh.0.2';} | nc ${nc_flags}|");
118 sleep 0.1;
119 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
120 cmp_ok( $res->return_code, '==', 0, "Got delayed SSH protocol version control string");
121 like( $res->output, '/^SSH OK - nagiosplug.ssh.0.2 \(protocol 2.0\)/', "Output OK");
122 close NC;
123}