1 /*****************************************************************************
3 * Monitoring check_ssh plugin
6 * Copyright (c) 2000-2007 Monitoring Plugins Development Team
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-2007";
33 const char *email
= "devel@monitoring-plugins.org";
36 #include "./netutils.h"
40 #define MSG_DONTWAIT 0
43 #define SSH_DFL_PORT 22
47 char *server_name
= NULL
;
48 char *remote_version
= NULL
;
49 char *remote_protocol
= NULL
;
52 int process_arguments (int, char **);
53 int validate_arguments (void);
54 void print_help (void);
55 void print_usage (void);
57 int ssh_connect (char *haddr
, int hport
, char *remote_version
, char *remote_protocol
);
61 main (int argc
, char **argv
)
63 int result
= STATE_UNKNOWN
;
65 setlocale (LC_ALL
, "");
66 bindtextdomain (PACKAGE
, LOCALEDIR
);
69 /* Parse extra opts if any */
70 argv
=np_extra_opts (&argc
, argv
, progname
);
72 if (process_arguments (argc
, argv
) == ERROR
)
73 usage4 (_("Could not parse arguments"));
75 /* initialize alarm signal handling */
76 signal (SIGALRM
, socket_timeout_alarm_handler
);
78 alarm (socket_timeout
);
80 /* ssh_connect exits if error is found */
81 result
= ssh_connect (server_name
, port
, remote_version
, remote_protocol
);
90 /* process command-line arguments */
92 process_arguments (int argc
, char **argv
)
97 static struct option longopts
[] = {
98 {"help", no_argument
, 0, 'h'},
99 {"version", no_argument
, 0, 'V'},
100 {"host", required_argument
, 0, 'H'}, /* backward compatibility */
101 {"hostname", required_argument
, 0, 'H'},
102 {"port", required_argument
, 0, 'p'},
103 {"use-ipv4", no_argument
, 0, '4'},
104 {"use-ipv6", no_argument
, 0, '6'},
105 {"timeout", required_argument
, 0, 't'},
106 {"verbose", no_argument
, 0, 'v'},
107 {"remote-version", required_argument
, 0, 'r'},
108 {"remote-protocol", required_argument
, 0, 'P'},
115 for (c
= 1; c
< argc
; c
++)
116 if (strcmp ("-to", argv
[c
]) == 0)
117 strcpy (argv
[c
], "-t");
120 c
= getopt_long (argc
, argv
, "+Vhv46t:r:H:p:P:", longopts
, &option
);
122 if (c
== -1 || c
== EOF
)
128 case 'V': /* version */
129 print_revision (progname
, NP_VERSION
);
130 exit (STATE_UNKNOWN
);
133 exit (STATE_UNKNOWN
);
134 case 'v': /* verbose */
137 case 't': /* timeout period */
138 if (!is_integer (optarg
))
139 usage2 (_("Timeout interval must be a positive integer"), optarg
);
141 socket_timeout
= atoi (optarg
);
144 address_family
= AF_INET
;
148 address_family
= AF_INET6
;
150 usage4 (_("IPv6 support not available"));
153 case 'r': /* remote version */
154 remote_version
= optarg
;
156 case 'P': /* remote version */
157 remote_protocol
= optarg
;
160 if (!is_host (optarg
))
161 usage2 (_("Invalid hostname/address"), optarg
);
162 server_name
= optarg
;
165 if (is_intpos (optarg
)) {
166 port
= atoi (optarg
);
169 usage2 (_("Port number must be a positive integer"), optarg
);
175 if (server_name
== NULL
&& c
< argc
) {
176 if (is_host (argv
[c
])) {
177 server_name
= argv
[c
++];
181 if (port
== -1 && c
< argc
) {
182 if (is_intpos (argv
[c
])) {
183 port
= atoi (argv
[c
++]);
187 exit (STATE_UNKNOWN
);
191 return validate_arguments ();
195 validate_arguments (void)
197 if (server_name
== NULL
)
199 if (port
== -1) /* funky, but allows -p to override stray integer in args */
205 /************************************************************************
207 * Try to connect to SSH server at specified server and port
209 *-----------------------------------------------------------------------*/
213 ssh_connect (char *haddr
, int hport
, char *remote_version
, char *remote_protocol
)
218 ssize_t recv_ret
= 0;
219 char *version_control_string
= NULL
;
221 char *ssh_proto
= NULL
;
222 char *ssh_server
= NULL
;
223 static char *rev_no
= VERSION
;
227 gettimeofday(&tv
, NULL
);
229 result
= my_tcp_connect (haddr
, hport
, &sd
);
231 if (result
!= STATE_OK
)
234 char *output
= (char *) calloc (BUFF_SZ
+ 1, sizeof(char));
236 unsigned int iteration
= 0;
237 ssize_t byte_offset
= 0;
239 while ((version_control_string
== NULL
) && (recv_ret
= recv(sd
, output
+byte_offset
, BUFF_SZ
- byte_offset
, 0) > 0)) {
241 if (strchr(output
, '\n')) { /* we've got at least one full line, start parsing*/
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
);
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
;
257 /*the start of the next line (if one exists) will be after the current one (+ NUL)*/
258 byte_offset
+= (len
+ 1);
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);
266 /*start reading from end of current line chunk on next recv*/
267 byte_offset
= strlen(output
);
270 byte_offset
+= recv_ret
;
275 printf("SSH CRITICAL - %s", strerror(errno
));
276 exit(STATE_CRITICAL
);
279 if (version_control_string
== NULL
) {
280 printf("SSH CRITICAL - No version control string received");
281 exit(STATE_CRITICAL
);
284 * "When the connection has been established, both sides MUST send an
285 * identification string. This identification string MUST be
287 * SSH-protoversion-softwareversion SP comments CR LF"
290 strip (version_control_string
);
292 printf ("%s\n", version_control_string
);
293 ssh_proto
= version_control_string
+ 4;
296 * We assume the protoversion is of the form Major.Minor, although
297 * this is not _strictly_ required. See
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 (-)"
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")."
311 ssh_server
= ssh_proto
+ strspn (ssh_proto
, "0123456789.") + 1; /* (+1 for the '-' separating protoversion from softwareversion) */
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
, ' ');
317 ssh_server
[tmp
- ssh_server
] = '\0';
319 if (strlen(ssh_proto
) == 0 || strlen(ssh_server
) == 0) {
320 printf(_("SSH CRITICAL - Invalid protocol version control string %s\n"), version_control_string
);
321 exit (STATE_CRITICAL
);
323 ssh_proto
[strspn (ssh_proto
, "0123456789. ")] = 0;
325 xasprintf (&buffer
, "SSH-%s-check_ssh_%s\r\n", ssh_proto
, rev_no
);
326 send (sd
, buffer
, strlen (buffer
), MSG_DONTWAIT
);
328 printf ("%s\n", buffer
);
330 if (remote_version
&& strcmp(remote_version
, ssh_server
)) {
332 (_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"),
333 ssh_server
, ssh_proto
, remote_version
);
335 exit (STATE_CRITICAL
);
338 if (remote_protocol
&& strcmp(remote_protocol
, ssh_proto
)) {
340 (_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s' | %s\n"),
341 ssh_server
, ssh_proto
, remote_protocol
, fperfdata("time", elapsed_time
, "s",
342 false, 0, false, 0, true, 0, true, (int)socket_timeout
));
344 exit (STATE_CRITICAL
);
346 elapsed_time
= (double)deltime(tv
) / 1.0e6
;
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
));
362 xasprintf (&myport
, "%d", SSH_DFL_PORT
);
364 print_revision (progname
, NP_VERSION
);
366 printf ("Copyright (c) 1999 Remi Paulmier <remi@sinfomic.fr>\n");
367 printf (COPYRIGHT
, copyright
, email
);
369 printf ("%s\n", _("Try to connect to an SSH server at specified server and port"));
375 printf (UT_HELP_VRSN
);
376 printf (UT_EXTRA_OPTS
);
378 printf (UT_HOST_PORT
, 'p', myport
);
382 printf (UT_CONN_TIMEOUT
, DEFAULT_SOCKET_TIMEOUT
);
384 printf (" %s\n", "-r, --remote-version=STRING");
385 printf (" %s\n", _("Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)"));
387 printf (" %s\n", "-P, --remote-protocol=STRING");
388 printf (" %s\n", _("Alert if protocol doesn't match expected protocol version (ex: 2.0)"));
400 printf ("%s\n", _("Usage:"));
401 printf ("%s [-4|-6] [-t <timeout>] [-r <remote version>] [-p <port>] <host>\n", progname
);