Module: monitoring-plugins
    Branch: dev/check_ssh-patches
    Commit: cb0b3e245afcdc29e606299c93fc232ddd6d7cef
    Author: Anton Lofgren <alofg...@op5.com>
 Committer: Lorenz Kästle <lorenz.kaes...@netways.de>
      Date: Mon Oct 21 08:18:30 2013 +0200
       URL: 
https://www.monitoring-plugins.org/repositories/monitoring-plugins/commit/?id=cb0b3e2

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 <alofg...@op5.com>

---

 plugins/check_ssh.c   | 122 +++++++++++++++++++++++++++++++++++---------------
 plugins/t/check_ssh.t | 109 +++++++++++++++++++++++++++++++++++---------
 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
 {
        int sd;
        int result;
+       int len = 0;
+       ssize_t byte_offset = 0;
+       ssize_t recv_ret = 0;
+       char *version_control_string = NULL;
        char *output = NULL;
        char *buffer = NULL;
+       char *tmp= NULL, *saveptr = NULL;
        char *ssh_proto = NULL;
        char *ssh_server = NULL;
        static char *rev_no = VERSION;
@@ -231,51 +236,94 @@ ssh_connect (char *haddr, int hport, char 
*remote_version, char *remote_protocol
                return result;
 
        output = (char *) malloc (BUFF_SZ + 1);
-       memset (output, 0, BUFF_SZ + 1);
-       recv (sd, output, BUFF_SZ, 0);
-       if (strncmp (output, "SSH", 3)) {
-               printf (_("Server answer: %s"), output);
-               close(sd);
+       memset(output, 0, BUFF_SZ+1);
+       while (!version_control_string && (recv_ret = recv(sd, 
output+byte_offset, BUFF_SZ - byte_offset, 0)) > 0) {
+               if (strchr(output, '\n')) { /* we've got at least one full 
line, start parsing*/
+                       byte_offset = 0;
+                       while (strchr(output+byte_offset, '\n') != NULL) {
+                               /*Partition the buffer so that this line is a 
separate string,
+                                * by replacing the newline with NUL*/
+                               output[(strchr(output+byte_offset, 
'\n')-output)]= '\0';
+                               len = strlen(output+byte_offset);
+                               if (len >= 4) {
+                                       /*if the string starts with SSH-, this 
_should_ be a valid version control string*/
+                                       if (strncmp (output+byte_offset, 
"SSH-", 4) == 0) {
+                                               version_control_string = 
output+byte_offset;
+                                               break;
+                                       }
+                               }
+
+                               /*the start of the next line (if one exists) 
will be after the current one (+ NUL)*/
+                               byte_offset+=len+1;
+                       }
+                       if(!version_control_string) {
+                               /* move unconsumed data to beginning of buffer, 
null rest */
+                               memmove((void *)output, (void 
*)output+byte_offset+1, BUFF_SZ - len+1);
+                               memset(output+byte_offset+1, 0, 
BUFF_SZ-byte_offset+1);
+
+                               /*start reading from end of current line chunk 
on next recv*/
+                               byte_offset = strlen(output);
+                       }
+               }
+               else {
+                       byte_offset += recv_ret;
+               }
+       }
+       tmp = NULL;
+       if (recv_ret < 0) {
+               printf("SSH CRITICAL - %s", strerror(errno));
+               exit(STATE_CRITICAL);
+       }
+       if (!version_control_string) {
+               printf("SSH CRITICAL - No version control string received");
+               exit(STATE_CRITICAL);
+       }
+       strip (version_control_string);
+       if (verbose)
+               printf ("%s\n", version_control_string);
+       ssh_proto = version_control_string + 4;
+       ssh_server = ssh_proto + strspn (ssh_proto, "-0123456789.");
+
+       /* If there's a space in the version string, whatever's after the space 
is a comment
+        * (which is NOT part of the server name/version)*/
+       tmp = strchr(ssh_server, ' ');
+       if (tmp) {
+               ssh_server[tmp - ssh_server] = '\0';
+       }
+       if (strlen(ssh_proto) == 0 || strlen(ssh_server) == 0) {
+               printf(_("SSH CRITICAL - Invalid protocol version control 
string %s\n"), version_control_string);
                exit (STATE_CRITICAL);
        }
-       else {
-               strip (output);
-               if (verbose)
-                       printf ("%s\n", output);
-               ssh_proto = output + 4;
-               ssh_server = ssh_proto + strspn (ssh_proto, "-0123456789. ");
-               ssh_proto[strspn (ssh_proto, "0123456789. ")] = 0;
-
-               xasprintf (&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, 
rev_no);
-               send (sd, buffer, strlen (buffer), MSG_DONTWAIT);
-               if (verbose)
-                       printf ("%s\n", buffer);
-
-               if (remote_version && strcmp(remote_version, ssh_server)) {
-                       printf
-                               (_("SSH CRITICAL - %s (protocol %s) version 
mismatch, expected '%s'\n"),
-                                ssh_server, ssh_proto, remote_version);
-                       close(sd);
-                       exit (STATE_CRITICAL);
-               }
+       ssh_proto[strspn (ssh_proto, "0123456789. ")] = 0;
 
-               if (remote_protocol && strcmp(remote_protocol, ssh_proto)) {
-                       printf
-                               (_("SSH CRITICAL - %s (protocol %s) protocol 
version mismatch, expected '%s'\n"),
-                                ssh_server, ssh_proto, remote_protocol);
-                       close(sd);
-                       exit (STATE_CRITICAL);
-               }
+       xasprintf (&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
+       send (sd, buffer, strlen (buffer), MSG_DONTWAIT);
+       if (verbose)
+               printf ("%s\n", buffer);
 
-               elapsed_time = (double)deltime(tv) / 1.0e6;
+       if (remote_version && strcmp(remote_version, ssh_server)) {
+               printf
+                       (_("SSH CRITICAL - %s (protocol %s) version mismatch, 
expected '%s'\n"),
+                        ssh_server, ssh_proto, remote_version);
+               close(sd);
+               exit (STATE_CRITICAL);
+       }
 
+       if (remote_protocol && strcmp(remote_protocol, ssh_proto)) {
                printf
-                       (_("SSH OK - %s (protocol %s) | %s\n"),
-                        ssh_server, ssh_proto, fperfdata("time", elapsed_time, 
"s",
-                        FALSE, 0, FALSE, 0, TRUE, 0, TRUE, 
(int)socket_timeout));
+                       (_("SSH CRITICAL - %s (protocol %s) protocol version 
mismatch, expected '%s'\n"),
+                        ssh_server, ssh_proto, remote_protocol);
                close(sd);
-               exit (STATE_OK);
+               exit (STATE_CRITICAL);
        }
+       elapsed_time = (double)deltime(tv) / 1.0e6;
+
+       printf
+               (_("SSH OK - %s (protocol %s) | %s\n"),
+                ssh_server, ssh_proto, fperfdata("time", elapsed_time, "s",
+                        FALSE, 0, FALSE, 0, TRUE, 0, TRUE, 
(int)socket_timeout));
+       close(sd);
+       exit (STATE_OK);
 }
 
 
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;
 use NPTest;
 
 # Required parameters
-my $ssh_host           = getTestParameter("NP_SSH_HOST", "A host providing SSH 
service", "localhost");
-my $host_nonresponsive = getTestParameter("NP_HOST_NONRESPONSIVE", "The 
hostname of system not responsive to network requests", "10.0.0.1" );
-my $hostname_invalid   = getTestParameter("NP_HOSTNAME_INVALID", "An invalid 
(not known to DNS) hostname", "nosuchhost" );
+my $ssh_host           = getTestParameter("NP_SSH_HOST",
+                                          "A host providing SSH service",
+                                          "localhost");
+my $host_nonresponsive = getTestParameter("NP_HOST_NONRESPONSIVE",
+                                          "The hostname of system not 
responsive to network requests",
+                                          "10.0.0.1" );
 
+my $hostname_invalid   = getTestParameter("NP_HOSTNAME_INVALID",
+                                          "An invalid (not known to DNS) 
hostname",
+                                          "nosuchhost" );
 
-plan skip_all => "SSH_HOST must be defined" unless $ssh_host;
-plan tests    => 6;
+my $res;
 
 
-my $result = NPTest->testCmd(
-    "./check_ssh -H $ssh_host"
-    );
-cmp_ok($result->return_code, '==', 0, "Exit with return code 0 (OK)");
-like($result->output, '/^SSH OK - /', "Status text if command returned none 
(OK)");
+plan tests => 18;
+SKIP: {
 
+       skip "No netcat available", 12 unless (system("which nc > /dev/null") 
== 0);
 
-$result = NPTest->testCmd(
-    "./check_ssh -H $host_nonresponsive -t 2"
-    );
-cmp_ok($result->return_code, '==', 2, "Exit with return code 0 (OK)");
-like($result->output, '/^CRITICAL - Socket timeout after 2 seconds/', "Status 
text if command returned none (OK)");
+       my $nc_flags = "-l 5003 -i 1";
+       #A valid protocol version control string has the form
+       #       SSH-protoversion-softwareversion SP comments CR LF
+       #
+       # where `comments` is optional, protoversion is the SSH protocol 
version and
+       # softwareversion is an arbitrary string representing the server 
software version
+       open(NC, "echo 'SSH-2.0-nagiosplug.ssh.0.1' | nc ${nc_flags}|");
+       sleep 1;
+       $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
+       cmp_ok( $res->return_code, '==', 0, "Got SSH protocol version control 
string");
+       like( $res->output, '/^SSH OK - nagiosplug.ssh.0.1 \(protocol 2.0\)/', 
"Output OK");
+       close NC;
 
+       open(NC, "echo 'SSH-2.0-nagiosplug.ssh.0.1 this is a comment' | nc 
${nc_flags} |");
+       sleep 1;
+       $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003 -r 
nagiosplug.ssh.0.1" );
+       cmp_ok( $res->return_code, '==', 0, "Got SSH protocol version control 
string, and parsed comment appropriately");
+       like( $res->output, '/^SSH OK - nagiosplug.ssh.0.1 \(protocol 2.0\)/', 
"Output OK");
+       close NC;
 
 
-$result = NPTest->testCmd(
-    "./check_ssh -H $hostname_invalid -t 2"
-    );
-cmp_ok($result->return_code, '==', 3, "Exit with return code 0 (OK)");
-like($result->output, '/^check_ssh: Invalid hostname/', "Status text if 
command returned none (OK)");
+       open(NC, "echo 'SSH-' | nc ${nc_flags}|");
+       sleep 1;
+       $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
+       cmp_ok( $res->return_code, '==', 2, "Got invalid SSH protocol version 
control string");
+       like( $res->output, '/^SSH CRITICAL/', "Output OK");
+       close NC;
 
+       open(NC, "echo '' | nc ${nc_flags}|");
+       sleep 1;
+       $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
+       cmp_ok( $res->return_code, '==', 2, "No version control string 
received");
+       like( $res->output, '/^SSH CRITICAL - No version control string 
received/', "Output OK");
+       close NC;
+
+       open(NC, "echo 'Not a version control string' | nc ${nc_flags}|");
+       sleep 1;
+       $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
+       cmp_ok( $res->return_code, '==', 2, "No version control string 
received");
+       like( $res->output, '/^SSH CRITICAL - No version control string 
received/', "Output OK");
+       close NC;
+
+
+       #RFC 4253 permits servers to send any number of data lines prior to 
sending the protocol version control string
+       open(NC, "echo 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n
+               BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n
+               CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n
+               DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\n
+               Some\nPrepended\nData\nLines\nSSH-2.0-nagiosplug.ssh.0.2' | nc 
${nc_flags}|");
+       sleep 1;
+       $res = NPTest->testCmd( "./check_ssh -H localhost -p 5003" );
+       cmp_ok( $res->return_code, '==', 0, "Got delayed SSH protocol version 
control string");
+       like( $res->output, '/^SSH OK - nagiosplug.ssh.0.2 \(protocol 2.0\)/', 
"Output OK");
+       close NC;
+}
+
+SKIP {
+       skip "SSH_HOST must be defined", 6 unless $ssh_host;
+       $res = NPTest->testCmd(
+           "./check_ssh -H $ssh_host"
+           );
+       cmp_ok($result->return_code, '==', 0, "Exit with return code 0 (OK)");
+       like($result->output, '/^SSH OK - /', "Status text if command returned 
none (OK)");
+
+
+       $res = NPTest->testCmd(
+           "./check_ssh -H $host_nonresponsive -t 2"
+           );
+       cmp_ok($result->return_code, '==', 2, "Exit with return code 0 (OK)");
+       like($result->output, '/^CRITICAL - Socket timeout after 2 seconds/', 
"Status text if command returned none (OK)");
+
+
+
+       $res = NPTest->testCmd(
+           "./check_ssh -H $hostname_invalid -t 2"
+           );
+       cmp_ok($result->return_code, '==', 3, "Exit with return code 0 (OK)");
+       like($result->output, '/^check_ssh: Invalid hostname/', "Status text if 
command returned none (OK)");
+
+}

Reply via email to