summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Lofgren <alofgren@op5.com>2013-10-21 06:18:30 (GMT)
committerLorenz Kästle <lorenz.kaestle@netways.de>2022-01-14 14:34:08 (GMT)
commitcb0b3e245afcdc29e606299c93fc232ddd6d7cef (patch)
tree69f35f5d31087e4efcd830dd7b1d0b73b4467c2e
parent9899bc736f45400fa70bdee281f5f5b46490b805 (diff)
downloadmonitoring-plugins-cb0b3e2.tar.gz
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>
-rw-r--r--plugins/check_ssh.c122
-rw-r--r--plugins/t/check_ssh.t109
2 files changed, 174 insertions, 57 deletions
diff --git a/plugins/check_ssh.c b/plugins/check_ssh.c
index 8ccbd5a..8a3abb0 100644
--- a/plugins/check_ssh.c
+++ b/plugins/check_ssh.c
@@ -215,8 +215,13 @@ ssh_connect (char *haddr, int hport, char *remote_version, char *remote_protocol
215{ 215{
216 int sd; 216 int sd;
217 int result; 217 int result;
218 int len = 0;
219 ssize_t byte_offset = 0;
220 ssize_t recv_ret = 0;
221 char *version_control_string = NULL;
218 char *output = NULL; 222 char *output = NULL;
219 char *buffer = NULL; 223 char *buffer = NULL;
224 char *tmp= NULL, *saveptr = NULL;
220 char *ssh_proto = NULL; 225 char *ssh_proto = NULL;
221 char *ssh_server = NULL; 226 char *ssh_server = NULL;
222 static char *rev_no = VERSION; 227 static char *rev_no = VERSION;
@@ -231,51 +236,94 @@ ssh_connect (char *haddr, int hport, char *remote_version, char *remote_protocol
231 return result; 236 return result;
232 237
233 output = (char *) malloc (BUFF_SZ + 1); 238 output = (char *) malloc (BUFF_SZ + 1);
234 memset (output, 0, BUFF_SZ + 1); 239 memset(output, 0, BUFF_SZ+1);
235 recv (sd, output, BUFF_SZ, 0); 240 while (!version_control_string && (recv_ret = recv(sd, output+byte_offset, BUFF_SZ - byte_offset, 0)) > 0) {
236 if (strncmp (output, "SSH", 3)) { 241 if (strchr(output, '\n')) { /* we've got at least one full line, start parsing*/
237 printf (_("Server answer: %s"), output); 242 byte_offset = 0;
238 close(sd); 243 while (strchr(output+byte_offset, '\n') != NULL) {
244 /*Partition the buffer so that this line is a separate string,
245 * by replacing the newline with NUL*/
246 output[(strchr(output+byte_offset, '\n')-output)]= '\0';
247 len = strlen(output+byte_offset);
248 if (len >= 4) {
249 /*if the string starts with SSH-, this _should_ be a valid version control string*/
250 if (strncmp (output+byte_offset, "SSH-", 4) == 0) {
251 version_control_string = output+byte_offset;
252 break;
253 }
254 }
255
256 /*the start of the next line (if one exists) will be after the current one (+ NUL)*/
257 byte_offset+=len+1;
258 }
259 if(!version_control_string) {
260 /* move unconsumed data to beginning of buffer, null rest */
261 memmove((void *)output, (void *)output+byte_offset+1, BUFF_SZ - len+1);
262 memset(output+byte_offset+1, 0, BUFF_SZ-byte_offset+1);
263
264 /*start reading from end of current line chunk on next recv*/
265 byte_offset = strlen(output);
266 }
267 }
268 else {
269 byte_offset += recv_ret;
270 }
271 }
272 tmp = NULL;
273 if (recv_ret < 0) {
274 printf("SSH CRITICAL - %s", strerror(errno));
275 exit(STATE_CRITICAL);
276 }
277 if (!version_control_string) {
278 printf("SSH CRITICAL - No version control string received");
279 exit(STATE_CRITICAL);
280 }
281 strip (version_control_string);
282 if (verbose)
283 printf ("%s\n", version_control_string);
284 ssh_proto = version_control_string + 4;
285 ssh_server = ssh_proto + strspn (ssh_proto, "-0123456789.");
286
287 /* If there's a space in the version string, whatever's after the space is a comment
288 * (which is NOT part of the server name/version)*/
289 tmp = strchr(ssh_server, ' ');
290 if (tmp) {
291 ssh_server[tmp - ssh_server] = '\0';
292 }
293 if (strlen(ssh_proto) == 0 || strlen(ssh_server) == 0) {
294 printf(_("SSH CRITICAL - Invalid protocol version control string %s\n"), version_control_string);
239 exit (STATE_CRITICAL); 295 exit (STATE_CRITICAL);
240 } 296 }
241 else { 297 ssh_proto[strspn (ssh_proto, "0123456789. ")] = 0;
242 strip (output);
243 if (verbose)
244 printf ("%s\n", output);
245 ssh_proto = output + 4;
246 ssh_server = ssh_proto + strspn (ssh_proto, "-0123456789. ");
247 ssh_proto[strspn (ssh_proto, "0123456789. ")] = 0;
248
249 xasprintf (&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
250 send (sd, buffer, strlen (buffer), MSG_DONTWAIT);
251 if (verbose)
252 printf ("%s\n", buffer);
253
254 if (remote_version && strcmp(remote_version, ssh_server)) {
255 printf
256 (_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"),
257 ssh_server, ssh_proto, remote_version);
258 close(sd);
259 exit (STATE_CRITICAL);
260 }
261 298
262 if (remote_protocol && strcmp(remote_protocol, ssh_proto)) { 299 xasprintf (&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
263 printf 300 send (sd, buffer, strlen (buffer), MSG_DONTWAIT);
264 (_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s'\n"), 301 if (verbose)
265 ssh_server, ssh_proto, remote_protocol); 302 printf ("%s\n", buffer);
266 close(sd);
267 exit (STATE_CRITICAL);
268 }
269 303
270 elapsed_time = (double)deltime(tv) / 1.0e6; 304 if (remote_version && strcmp(remote_version, ssh_server)) {
305 printf
306 (_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"),
307 ssh_server, ssh_proto, remote_version);
308 close(sd);
309 exit (STATE_CRITICAL);
310 }
271 311
312 if (remote_protocol && strcmp(remote_protocol, ssh_proto)) {
272 printf 313 printf
273 (_("SSH OK - %s (protocol %s) | %s\n"), 314 (_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s'\n"),
274 ssh_server, ssh_proto, fperfdata("time", elapsed_time, "s", 315 ssh_server, ssh_proto, remote_protocol);
275 FALSE, 0, FALSE, 0, TRUE, 0, TRUE, (int)socket_timeout));
276 close(sd); 316 close(sd);
277 exit (STATE_OK); 317 exit (STATE_CRITICAL);
278 } 318 }
319 elapsed_time = (double)deltime(tv) / 1.0e6;
320
321 printf
322 (_("SSH OK - %s (protocol %s) | %s\n"),
323 ssh_server, ssh_proto, fperfdata("time", elapsed_time, "s",
324 FALSE, 0, FALSE, 0, TRUE, 0, TRUE, (int)socket_timeout));
325 close(sd);
326 exit (STATE_OK);
279} 327}
280 328
281 329
diff --git a/plugins/t/check_ssh.t b/plugins/t/check_ssh.t
index a5cd23c..6b5e93b 100644
--- a/plugins/t/check_ssh.t
+++ b/plugins/t/check_ssh.t
@@ -9,33 +9,102 @@ use Test::More;
9use NPTest; 9use NPTest;
10 10
11# Required parameters 11# Required parameters
12my $ssh_host = getTestParameter("NP_SSH_HOST", "A host providing SSH service", "localhost"); 12my $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" ); 13 "A host providing SSH service",
14my $hostname_invalid = getTestParameter("NP_HOSTNAME_INVALID", "An invalid (not known to DNS) hostname", "nosuchhost" ); 14 "localhost");
15my $host_nonresponsive = getTestParameter("NP_HOST_NONRESPONSIVE",
16 "The hostname of system not responsive to network requests",
17 "10.0.0.1" );
15 18
19my $hostname_invalid = getTestParameter("NP_HOSTNAME_INVALID",
20 "An invalid (not known to DNS) hostname",
21 "nosuchhost" );
16 22
17plan skip_all => "SSH_HOST must be defined" unless $ssh_host; 23my $res;
18plan tests => 6;
19 24
20 25
21my $result = NPTest->testCmd( 26plan tests => 18;
22 "./check_ssh -H $ssh_host" 27SKIP: {
23 );
24cmp_ok($result->return_code, '==', 0, "Exit with return code 0 (OK)");
25like($result->output, '/^SSH OK - /', "Status text if command returned none (OK)");
26 28
29 skip "No netcat available", 12 unless (system("which nc > /dev/null") == 0);
27 30
28$result = NPTest->testCmd( 31 my $nc_flags = "-l 5003 -i 1";
29 "./check_ssh -H $host_nonresponsive -t 2" 32 #A valid protocol version control string has the form
30 ); 33 # SSH-protoversion-softwareversion SP comments CR LF
31cmp_ok($result->return_code, '==', 2, "Exit with return code 0 (OK)"); 34 #
32like($result->output, '/^CRITICAL - Socket timeout after 2 seconds/', "Status text if command returned none (OK)"); 35 # where `comments` is optional, protoversion is the SSH protocol version and
36 # softwareversion is an arbitrary string representing the server software version
37 open(NC, "echo 'SSH-2.0-nagiosplug.ssh.0.1' | nc ${nc_flags}|");
38 sleep 1;
39 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
40 cmp_ok( $res->return_code, '==', 0, "Got SSH protocol version control string");
41 like( $res->output, '/^SSH OK - nagiosplug.ssh.0.1 \(protocol 2.0\)/', "Output OK");
42 close NC;
33 43
44 open(NC, "echo 'SSH-2.0-nagiosplug.ssh.0.1 this is a comment' | nc ${nc_flags} |");
45 sleep 1;
46 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003 -r nagiosplug.ssh.0.1" );
47 cmp_ok( $res->return_code, '==', 0, "Got SSH protocol version control string, and parsed comment appropriately");
48 like( $res->output, '/^SSH OK - nagiosplug.ssh.0.1 \(protocol 2.0\)/', "Output OK");
49 close NC;
34 50
35 51
36$result = NPTest->testCmd( 52 open(NC, "echo 'SSH-' | nc ${nc_flags}|");
37 "./check_ssh -H $hostname_invalid -t 2" 53 sleep 1;
38 ); 54 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
39cmp_ok($result->return_code, '==', 3, "Exit with return code 0 (OK)"); 55 cmp_ok( $res->return_code, '==', 2, "Got invalid SSH protocol version control string");
40like($result->output, '/^check_ssh: Invalid hostname/', "Status text if command returned none (OK)"); 56 like( $res->output, '/^SSH CRITICAL/', "Output OK");
57 close NC;
41 58
59 open(NC, "echo '' | nc ${nc_flags}|");
60 sleep 1;
61 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
62 cmp_ok( $res->return_code, '==', 2, "No version control string received");
63 like( $res->output, '/^SSH CRITICAL - No version control string received/', "Output OK");
64 close NC;
65
66 open(NC, "echo 'Not a version control string' | nc ${nc_flags}|");
67 sleep 1;
68 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
69 cmp_ok( $res->return_code, '==', 2, "No version control string received");
70 like( $res->output, '/^SSH CRITICAL - No version control string received/', "Output OK");
71 close NC;
72
73
74 #RFC 4253 permits servers to send any number of data lines prior to sending the protocol version control string
75 open(NC, "echo 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n
76 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n
77 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n
78 DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\n
79 Some\nPrepended\nData\nLines\nSSH-2.0-nagiosplug.ssh.0.2' | nc ${nc_flags}|");
80 sleep 1;
81 $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
82 cmp_ok( $res->return_code, '==', 0, "Got delayed SSH protocol version control string");
83 like( $res->output, '/^SSH OK - nagiosplug.ssh.0.2 \(protocol 2.0\)/', "Output OK");
84 close NC;
85}
86
87SKIP {
88 skip "SSH_HOST must be defined", 6 unless $ssh_host;
89 $res = NPTest->testCmd(
90 "./check_ssh -H $ssh_host"
91 );
92 cmp_ok($result->return_code, '==', 0, "Exit with return code 0 (OK)");
93 like($result->output, '/^SSH OK - /', "Status text if command returned none (OK)");
94
95
96 $res = NPTest->testCmd(
97 "./check_ssh -H $host_nonresponsive -t 2"
98 );
99 cmp_ok($result->return_code, '==', 2, "Exit with return code 0 (OK)");
100 like($result->output, '/^CRITICAL - Socket timeout after 2 seconds/', "Status text if command returned none (OK)");
101
102
103
104 $res = NPTest->testCmd(
105 "./check_ssh -H $hostname_invalid -t 2"
106 );
107 cmp_ok($result->return_code, '==', 3, "Exit with return code 0 (OK)");
108 like($result->output, '/^check_ssh: Invalid hostname/', "Status text if command returned none (OK)");
109
110}