check_ssh: clang-format
[monitoring-plugins.git] / plugins / check_ssh.c
blob9f805db49f900a52b72e72fc01385283fbaf49fc
1 /*****************************************************************************
3 * Monitoring check_ssh plugin
5 * License: GPL
6 * Copyright (c) 2000-2024 Monitoring Plugins Development Team
8 * Description:
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-2024";
33 const char *email = "devel@monitoring-plugins.org";
35 #include "./common.h"
36 #include "./netutils.h"
37 #include "utils.h"
39 #ifndef MSG_DONTWAIT
40 # define MSG_DONTWAIT 0
41 #endif
43 #define SSH_DFL_PORT 22
44 #define BUFF_SZ 256
46 int port = -1;
47 char *server_name = NULL;
48 char *remote_version = NULL;
49 char *remote_protocol = NULL;
50 bool verbose = false;
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);
59 int main(int argc, char **argv) {
60 int result = STATE_UNKNOWN;
62 setlocale(LC_ALL, "");
63 bindtextdomain(PACKAGE, LOCALEDIR);
64 textdomain(PACKAGE);
66 /* Parse extra opts if any */
67 argv = np_extra_opts(&argc, argv, progname);
69 if (process_arguments(argc, argv) == ERROR)
70 usage4(_("Could not parse arguments"));
72 /* initialize alarm signal handling */
73 signal(SIGALRM, socket_timeout_alarm_handler);
75 alarm(socket_timeout);
77 /* ssh_connect exits if error is found */
78 result = ssh_connect(server_name, port, remote_version, remote_protocol);
80 alarm(0);
82 return (result);
85 /* process command-line arguments */
86 int process_arguments(int argc, char **argv) {
87 int c;
89 int option = 0;
90 static struct option longopts[] = {{"help", no_argument, 0, 'h'},
91 {"version", no_argument, 0, 'V'},
92 {"host", required_argument, 0, 'H'}, /* backward compatibility */
93 {"hostname", required_argument, 0, 'H'},
94 {"port", required_argument, 0, 'p'},
95 {"use-ipv4", no_argument, 0, '4'},
96 {"use-ipv6", no_argument, 0, '6'},
97 {"timeout", required_argument, 0, 't'},
98 {"verbose", no_argument, 0, 'v'},
99 {"remote-version", required_argument, 0, 'r'},
100 {"remote-protocol", required_argument, 0, 'P'},
101 {0, 0, 0, 0}};
103 if (argc < 2)
104 return ERROR;
106 for (c = 1; c < argc; c++)
107 if (strcmp("-to", argv[c]) == 0)
108 strcpy(argv[c], "-t");
110 while (1) {
111 c = getopt_long(argc, argv, "+Vhv46t:r:H:p:P:", longopts, &option);
113 if (c == -1 || c == EOF)
114 break;
116 switch (c) {
117 case '?': /* help */
118 usage5();
119 case 'V': /* version */
120 print_revision(progname, NP_VERSION);
121 exit(STATE_UNKNOWN);
122 case 'h': /* help */
123 print_help();
124 exit(STATE_UNKNOWN);
125 case 'v': /* verbose */
126 verbose = true;
127 break;
128 case 't': /* timeout period */
129 if (!is_integer(optarg))
130 usage2(_("Timeout interval must be a positive integer"), optarg);
131 else
132 socket_timeout = atoi(optarg);
133 break;
134 case '4':
135 address_family = AF_INET;
136 break;
137 case '6':
138 #ifdef USE_IPV6
139 address_family = AF_INET6;
140 #else
141 usage4(_("IPv6 support not available"));
142 #endif
143 break;
144 case 'r': /* remote version */
145 remote_version = optarg;
146 break;
147 case 'P': /* remote version */
148 remote_protocol = optarg;
149 break;
150 case 'H': /* host */
151 if (!is_host(optarg))
152 usage2(_("Invalid hostname/address"), optarg);
153 server_name = optarg;
154 break;
155 case 'p': /* port */
156 if (is_intpos(optarg)) {
157 port = atoi(optarg);
158 } else {
159 usage2(_("Port number must be a positive integer"), optarg);
164 c = optind;
165 if (server_name == NULL && c < argc) {
166 if (is_host(argv[c])) {
167 server_name = argv[c++];
171 if (port == -1 && c < argc) {
172 if (is_intpos(argv[c])) {
173 port = atoi(argv[c++]);
174 } else {
175 print_usage();
176 exit(STATE_UNKNOWN);
180 return validate_arguments();
183 int validate_arguments(void) {
184 if (server_name == NULL)
185 return ERROR;
186 if (port == -1) /* funky, but allows -p to override stray integer in args */
187 port = SSH_DFL_PORT;
188 return OK;
191 /************************************************************************
193 * Try to connect to SSH server at specified server and port
195 *-----------------------------------------------------------------------*/
197 int ssh_connect(char *haddr, int hport, char *remote_version, char *remote_protocol) {
198 int sd;
199 int result;
200 int len = 0;
201 ssize_t recv_ret = 0;
202 char *version_control_string = NULL;
203 char *buffer = NULL;
204 char *ssh_proto = NULL;
205 char *ssh_server = NULL;
206 static char *rev_no = VERSION;
207 struct timeval tv;
209 gettimeofday(&tv, NULL);
211 result = my_tcp_connect(haddr, hport, &sd);
213 if (result != STATE_OK)
214 return result;
216 char *output = (char *)calloc(BUFF_SZ + 1, sizeof(char));
218 ssize_t byte_offset = 0;
220 while ((version_control_string == NULL) && (recv_ret = recv(sd, output + byte_offset, BUFF_SZ - byte_offset, 0) > 0)) {
222 if (strchr(output, '\n')) { /* we've got at least one full line, start parsing*/
223 byte_offset = 0;
225 char *index = NULL;
226 while ((index = strchr(output + byte_offset, '\n')) != NULL) {
227 /*Partition the buffer so that this line is a separate string,
228 * by replacing the newline with NUL*/
229 output[(index - output)] = '\0';
230 len = strlen(output + byte_offset);
232 if ((len >= 4) && (strncmp(output + byte_offset, "SSH-", 4) == 0)) {
233 /*if the string starts with SSH-, this _should_ be a valid version control string*/
234 version_control_string = output + byte_offset;
235 break;
238 /*the start of the next line (if one exists) will be after the current one (+ NUL)*/
239 byte_offset += (len + 1);
242 if (version_control_string == NULL) {
243 /* move unconsumed data to beginning of buffer, null rest */
244 memmove((void *)output, (void *)output + byte_offset + 1, BUFF_SZ - len + 1);
245 memset(output + byte_offset + 1, 0, BUFF_SZ - byte_offset + 1);
247 /*start reading from end of current line chunk on next recv*/
248 byte_offset = strlen(output);
250 } else {
251 byte_offset += recv_ret;
255 if (recv_ret < 0) {
256 printf("SSH CRITICAL - %s", strerror(errno));
257 exit(STATE_CRITICAL);
260 if (version_control_string == NULL) {
261 printf("SSH CRITICAL - No version control string received");
262 exit(STATE_CRITICAL);
265 * "When the connection has been established, both sides MUST send an
266 * identification string. This identification string MUST be
268 * SSH-protoversion-softwareversion SP comments CR LF"
269 * - RFC 4253:4.2
271 strip(version_control_string);
272 if (verbose)
273 printf("%s\n", version_control_string);
274 ssh_proto = version_control_string + 4;
277 * We assume the protoversion is of the form Major.Minor, although
278 * this is not _strictly_ required. See
280 * "Both the 'protoversion' and 'softwareversion' strings MUST consist of
281 * printable US-ASCII characters, with the exception of whitespace
282 * characters and the minus sign (-)"
283 * - RFC 4253:4.2
284 * and,
286 * "As stated earlier, the 'protoversion' specified for this protocol is
287 * "2.0". Earlier versions of this protocol have not been formally
288 * documented, but it is widely known that they use 'protoversion' of
289 * "1.x" (e.g., "1.5" or "1.3")."
290 * - RFC 4253:5
292 ssh_server = ssh_proto + strspn(ssh_proto, "0123456789.") + 1; /* (+1 for the '-' separating protoversion from softwareversion) */
294 /* If there's a space in the version string, whatever's after the space is a comment
295 * (which is NOT part of the server name/version)*/
296 char *tmp = strchr(ssh_server, ' ');
297 if (tmp) {
298 ssh_server[tmp - ssh_server] = '\0';
300 if (strlen(ssh_proto) == 0 || strlen(ssh_server) == 0) {
301 printf(_("SSH CRITICAL - Invalid protocol version control string %s\n"), version_control_string);
302 exit(STATE_CRITICAL);
304 ssh_proto[strspn(ssh_proto, "0123456789. ")] = 0;
306 xasprintf(&buffer, "SSH-%s-check_ssh_%s\r\n", ssh_proto, rev_no);
307 send(sd, buffer, strlen(buffer), MSG_DONTWAIT);
308 if (verbose)
309 printf("%s\n", buffer);
311 if (remote_version && strcmp(remote_version, ssh_server)) {
312 printf(_("SSH CRITICAL - %s (protocol %s) version mismatch, expected '%s'\n"), ssh_server, ssh_proto, remote_version);
313 close(sd);
314 exit(STATE_CRITICAL);
317 double elapsed_time = (double)deltime(tv) / 1.0e6;
318 if (remote_protocol && strcmp(remote_protocol, ssh_proto)) {
319 printf(_("SSH CRITICAL - %s (protocol %s) protocol version mismatch, expected '%s' | %s\n"), ssh_server, ssh_proto, remote_protocol,
320 fperfdata("time", elapsed_time, "s", false, 0, false, 0, true, 0, true, (int)socket_timeout));
321 close(sd);
322 exit(STATE_CRITICAL);
325 printf(_("SSH OK - %s (protocol %s) | %s\n"), ssh_server, ssh_proto,
326 fperfdata("time", elapsed_time, "s", false, 0, false, 0, true, 0, true, (int)socket_timeout));
327 close(sd);
328 exit(STATE_OK);
331 void print_help(void) {
332 char *myport;
333 xasprintf(&myport, "%d", SSH_DFL_PORT);
335 print_revision(progname, NP_VERSION);
337 printf("Copyright (c) 1999 Remi Paulmier <remi@sinfomic.fr>\n");
338 printf(COPYRIGHT, copyright, email);
340 printf("%s\n", _("Try to connect to an SSH server at specified server and port"));
342 printf("\n\n");
344 print_usage();
346 printf(UT_HELP_VRSN);
347 printf(UT_EXTRA_OPTS);
349 printf(UT_HOST_PORT, 'p', myport);
351 printf(UT_IPv46);
353 printf(UT_CONN_TIMEOUT, DEFAULT_SOCKET_TIMEOUT);
355 printf(" %s\n", "-r, --remote-version=STRING");
356 printf(" %s\n", _("Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)"));
358 printf(" %s\n", "-P, --remote-protocol=STRING");
359 printf(" %s\n", _("Alert if protocol doesn't match expected protocol version (ex: 2.0)"));
361 printf(UT_VERBOSE);
363 printf(UT_SUPPORT);
366 void print_usage(void) {
367 printf("%s\n", _("Usage:"));
368 printf("%s [-4|-6] [-t <timeout>] [-r <remote version>] [-p <port>] <host>\n", progname);