root/src/modules/ssh2.c

Revision fbc0571a39360132c925626107056f75eead8548, 12.7 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 4 years ago)

should address libssh2 bailing without a banner present, fixes #279

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <assert.h>
39 #include <math.h>
40 #ifdef HAVE_SYS_FILIO_H
41 #include <sys/filio.h>
42 #endif
43
44 #include "noit_module.h"
45 #include "noit_check.h"
46 #include "noit_check_tools.h"
47 #include "utils/noit_log.h"
48 #include "utils/noit_hash.h"
49
50 #include <libssh2.h>
51
52 #define DEFAULT_SSH_PORT 22
53
54 typedef struct {
55   noit_module_t *self;
56   noit_check_t *check;
57   struct {
58     char *kex;
59     char *hostkey;
60     char *crypt_cs;
61     char *crypt_sc;
62     char *mac_cs;
63     char *mac_sc;
64     char *comp_cs;
65     char *comp_sc;
66   } methods;
67   enum {
68     WANT_CONNECT = 0,
69     WANT_CLOSE = 1
70   } state;
71   LIBSSH2_SESSION *session;
72   int available;
73   int timed_out;
74   char *error;
75   char fingerprint[33]; /* 32 hex characters */
76   eventer_t synch_fd_event;
77   eventer_t timeout_event; /* Only used for connect() */
78 } ssh2_check_info_t;
79
80 static noit_log_stream_t nlerr = NULL;
81 static noit_log_stream_t nldeb = NULL;
82
83 static void ssh2_cleanup(noit_module_t *self, noit_check_t *check) {
84   ssh2_check_info_t *ci = check->closure;
85   if(ci) {
86     if(ci->timeout_event) {
87       eventer_remove(ci->timeout_event);
88       eventer_free(ci->timeout_event);
89     }
90     if(ci->session) {
91       libssh2_session_disconnect(ci->session, "Bye!");
92       libssh2_session_free(ci->session);
93     }
94     if(ci->methods.kex) free(ci->methods.kex);
95     if(ci->methods.hostkey) free(ci->methods.hostkey);
96     if(ci->methods.crypt_cs) free(ci->methods.crypt_cs);
97     if(ci->methods.crypt_sc) free(ci->methods.crypt_sc);
98     if(ci->methods.mac_cs) free(ci->methods.mac_cs);
99     if(ci->methods.mac_sc) free(ci->methods.mac_sc);
100     if(ci->methods.comp_cs) free(ci->methods.comp_cs);
101     if(ci->methods.comp_sc) free(ci->methods.comp_sc);
102     if(ci->error) free(ci->error);
103     memset(ci, 0, sizeof(*ci));
104   }
105 }
106 static int ssh2_init(noit_module_t *self) {
107   return 0;
108 }
109 static int ssh2_config(noit_module_t *self, noit_hash_table *options) {
110   return 0;
111 }
112 static void ssh2_log_results(noit_module_t *self, noit_check_t *check) {
113   stats_t current;
114   struct timeval duration;
115   ssh2_check_info_t *ci = check->closure;
116
117   noit_check_stats_clear(&current);
118
119   gettimeofday(&current.whence, NULL);
120   sub_timeval(current.whence, check->last_fire_time, &duration);
121   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
122   current.available = ci->available ? NP_AVAILABLE : NP_UNAVAILABLE;
123   current.state = ci->fingerprint[0] ? NP_GOOD : NP_BAD;
124
125   if(ci->error) current.status = ci->error;
126   else if(ci->timed_out) current.status = "timeout";
127   else if(ci->fingerprint[0]) current.status = ci->fingerprint;
128   else current.status = "internal error";
129
130   if(ci->fingerprint[0]) {
131     u_int32_t mduration = current.duration;
132     noit_stats_set_metric(&current, "duration", METRIC_UINT32, &mduration);
133     noit_stats_set_metric(&current, "fingerprint", METRIC_STRING,
134                           ci->fingerprint);
135   }
136   noit_check_set_stats(self, check, &current);
137 }
138 static int ssh2_drive_session(eventer_t e, int mask, void *closure,
139                               struct timeval *now) {
140   int i;
141   const char *fingerprint;
142   ssh2_check_info_t *ci = closure;
143   if(ci->state == WANT_CLOSE) {
144     noit_check_t *check = ci->check;
145     ssh2_log_results(ci->self, ci->check);
146     ssh2_cleanup(ci->self, ci->check);
147     eventer_remove_fd(e->fd);
148     e->opset->close(e->fd, &mask, e);
149     check->flags &= ~NP_RUNNING;
150     return 0;
151   }
152   switch(mask) {
153     case EVENTER_ASYNCH_WORK:
154       if(eventer_set_fd_blocking(e->fd)) {
155         ci->timed_out = 0;
156         ci->error = strdup("socket error");
157         return 0;
158       }
159       ci->session = libssh2_session_init();
160 #define set_method(a,b) do { \
161   int rv; \
162   if(ci->methods.a && \
163      (rv = libssh2_session_method_pref(ci->session, b, ci->methods.a)) != 0) { \
164     ci->timed_out = 0; \
165     ci->error = strdup((rv == LIBSSH2_ERROR_METHOD_NOT_SUPPORTED) ? \
166                          #a " method not supported" : "error setting " #a); \
167     return 0; \
168   } \
169 } while(0)
170       set_method(kex, LIBSSH2_METHOD_KEX);
171       set_method(hostkey, LIBSSH2_METHOD_HOSTKEY);
172       set_method(crypt_cs, LIBSSH2_METHOD_CRYPT_CS);
173       set_method(crypt_sc, LIBSSH2_METHOD_CRYPT_SC);
174       set_method(mac_cs, LIBSSH2_METHOD_MAC_CS);
175       set_method(mac_sc, LIBSSH2_METHOD_MAC_SC);
176       set_method(comp_cs, LIBSSH2_METHOD_COMP_CS);
177       set_method(comp_sc, LIBSSH2_METHOD_COMP_SC);
178       if (libssh2_session_startup(ci->session, e->fd)) {
179         ci->timed_out = 0;
180         ci->error = strdup("ssh session startup failed");
181         return 0;
182       }
183       fingerprint = libssh2_hostkey_hash(ci->session, LIBSSH2_HOSTKEY_HASH_MD5);
184       for(i=0;i<16;i++) {
185         snprintf(ci->fingerprint + (i*2), 3, "%02x",
186                  (unsigned char)fingerprint[i]);
187       }
188       ci->fingerprint[32] = '\0';
189       ci->timed_out = 0;
190       return 0;
191       break;
192     case EVENTER_ASYNCH_CLEANUP:
193       if(ci->session) {
194         libssh2_session_disconnect(ci->session, "Bye!");
195         libssh2_session_free(ci->session);
196         ci->session = NULL;
197       }
198       ci->state = WANT_CLOSE;
199       break;
200     default:
201       abort();
202   }
203   return 0;
204 }
205 static int ssh2_needs_bytes_as_libssh2_is_impatient(eventer_t e, int mask, void *closure,
206                                                     struct timeval *now) {
207   ssh2_check_info_t *ci = closure;
208   eventer_t asynch_e;
209
210   if(mask & EVENTER_EXCEPTION) {
211     noit_check_t *check = ci->check;
212     ci->timed_out = 0;
213     ci->error = strdup("ssh connection failed");
214     ssh2_log_results(ci->self, ci->check);
215     ssh2_cleanup(ci->self, ci->check);
216     eventer_remove_fd(e->fd);
217     e->opset->close(e->fd, &mask, e);
218     check->flags &= ~NP_RUNNING;
219     return 0;
220   }
221
222   /* We steal the timeout_event as it has the exact timeout we want. */
223   assert(ci->timeout_event);
224   asynch_e = eventer_remove(ci->timeout_event);
225   assert(asynch_e);
226   ci->timeout_event = NULL;
227
228   ci->synch_fd_event = NULL;
229   asynch_e->fd = e->fd;
230   asynch_e->callback = ssh2_drive_session;
231   asynch_e->closure = closure;
232   asynch_e->mask = EVENTER_ASYNCH;
233   eventer_add(asynch_e);
234
235   eventer_remove_fd(e->fd);
236   return 0;
237 }
238 static int ssh2_connect_complete(eventer_t e, int mask, void *closure,
239                                  struct timeval *now) {
240   ssh2_check_info_t *ci = closure;
241
242   if(mask & EVENTER_EXCEPTION) {
243     noit_check_t *check = ci->check;
244     ci->timed_out = 0;
245     ci->error = strdup("ssh connection failed");
246     ssh2_log_results(ci->self, ci->check);
247     ssh2_cleanup(ci->self, ci->check);
248     eventer_remove_fd(e->fd);
249     e->opset->close(e->fd, &mask, e);
250     check->flags &= ~NP_RUNNING;
251     return 0;
252   }
253
254   ci->available = 1;
255   e->callback = ssh2_needs_bytes_as_libssh2_is_impatient;
256   e->mask = EVENTER_READ | EVENTER_EXCEPTION;
257   return e->mask;
258 }
259 static int ssh2_connect_timeout(eventer_t e, int mask, void *closure,
260                                 struct timeval *now) {
261   eventer_t fde;
262   ssh2_check_info_t *ci = closure;
263   noit_check_t *check = ci->check;
264  
265   ci->timeout_event = NULL; /* This is us, return 0 will free this */
266   ci->error = strdup("ssh connect timeout");
267   if(ci->synch_fd_event) {
268     fde = ci->synch_fd_event;
269     eventer_remove_fd(fde->fd);
270     fde->opset->close(fde->fd, &mask, fde);
271     eventer_free(fde);
272      ci->synch_fd_event = NULL;
273   }
274   ssh2_log_results(ci->self, ci->check);
275   ssh2_cleanup(ci->self, ci->check);
276   check->flags &= ~NP_RUNNING;
277   return 0;
278 }
279 static int ssh2_initiate(noit_module_t *self, noit_check_t *check) {
280   ssh2_check_info_t *ci = check->closure;
281   struct timeval p_int, __now;
282   int fd, rv;
283   eventer_t e;
284   union {
285     struct sockaddr_in sin;
286     struct sockaddr_in6 sin6;
287   } sockaddr;
288   socklen_t sockaddr_len;
289   unsigned short ssh_port = DEFAULT_SSH_PORT;
290   const char *port_str = NULL;
291
292   /* We cannot be running */
293   assert(!(check->flags & NP_RUNNING));
294   check->flags |= NP_RUNNING;
295
296   ci->self = self;
297   ci->check = check;
298
299   ci->timed_out = 1;
300   if(ci->timeout_event) {
301     eventer_remove(ci->timeout_event);
302     free(ci->timeout_event->closure);
303     eventer_free(ci->timeout_event);
304     ci->timeout_event = NULL;
305   }
306   gettimeofday(&__now, NULL);
307   memcpy(&check->last_fire_time, &__now, sizeof(__now));
308
309   /* Open a socket */
310   fd = socket(check->target_family, SOCK_STREAM, 0);
311   if(fd < 0) goto fail;
312
313   /* Make it non-blocking */
314   if(eventer_set_fd_nonblocking(fd)) goto fail;
315
316   if(noit_hash_retr_str(check->config, "port", strlen("port"),
317                         &port_str)) {
318     ssh_port = (unsigned short)atoi(port_str);
319   }
320 #define config_method(a) do { \
321   const char *v; \
322   if(noit_hash_retr_str(check->config, "method_" #a, strlen("method_" #a), \
323                         &v)) \
324     ci->methods.a = strdup(v); \
325 } while(0)
326   config_method(kex);
327   config_method(hostkey);
328   config_method(crypt_cs);
329   config_method(crypt_sc);
330   config_method(mac_cs);
331   config_method(mac_sc);
332   config_method(comp_cs);
333   config_method(comp_sc);
334   memset(&sockaddr, 0, sizeof(sockaddr));
335   sockaddr.sin6.sin6_family = check->target_family;
336   if(check->target_family == AF_INET) {
337     memcpy(&sockaddr.sin.sin_addr,
338            &check->target_addr.addr, sizeof(sockaddr.sin.sin_addr));
339     sockaddr.sin.sin_port = htons(ssh_port);
340     sockaddr_len = sizeof(sockaddr.sin);
341   }
342   else {
343     memcpy(&sockaddr.sin6.sin6_addr,
344            &check->target_addr.addr6, sizeof(sockaddr.sin6.sin6_addr));
345     sockaddr.sin6.sin6_port = htons(ssh_port);
346     sockaddr_len = sizeof(sockaddr.sin6);
347   }
348
349   /* Initiate a connection */
350   rv = connect(fd, (struct sockaddr *)&sockaddr, sockaddr_len);
351   if(rv == -1 && errno != EINPROGRESS) goto fail;
352
353   /* Register a handler for connection completion */
354   e = eventer_alloc();
355   e->fd = fd;
356   e->mask = EVENTER_READ | EVENTER_WRITE | EVENTER_EXCEPTION;
357   e->callback = ssh2_connect_complete;
358   e->closure =  ci;
359   ci->synch_fd_event = e;
360   eventer_add(e);
361
362   e = eventer_alloc();
363   e->mask = EVENTER_TIMER;
364   e->callback = ssh2_connect_timeout;
365   e->closure = ci;
366   memcpy(&e->whence, &__now, sizeof(__now));
367   p_int.tv_sec = check->timeout / 1000;
368   p_int.tv_usec = (check->timeout % 1000) * 1000;
369   add_timeval(e->whence, p_int, &e->whence);
370   ci->timeout_event = e;
371   eventer_add(e);
372   return 0;
373
374  fail:
375   if(fd >= 0) close(fd);
376   ssh2_log_results(ci->self, ci->check);
377   ssh2_cleanup(ci->self, ci->check);
378   check->flags &= ~NP_RUNNING;
379   return -1;
380 }
381
382 static int ssh2_initiate_check(noit_module_t *self, noit_check_t *check,
383                                int once, noit_check_t *parent) {
384   if(!check->closure) check->closure = calloc(1, sizeof(ssh2_check_info_t));
385   INITIATE_CHECK(ssh2_initiate, self, check);
386   return 0;
387 }
388
389 static int ssh2_onload(noit_image_t *self) {
390   nlerr = noit_log_stream_find("error/ssh2");
391   nldeb = noit_log_stream_find("debug/ssh2");
392   if(!nlerr) nlerr = noit_stderr;
393   if(!nldeb) nldeb = noit_debug;
394
395   eventer_name_callback("http/ssh2_connect_complete", ssh2_connect_complete);
396   eventer_name_callback("http/ssh2_drive_session", ssh2_drive_session);
397   return 0;
398 }
399
400 #include "ssh2.xmlh"
401 noit_module_t ssh2 = {
402   {
403     NOIT_MODULE_MAGIC,
404     NOIT_MODULE_ABI_VERSION,
405     "ssh2",
406     "Secure Shell version 2 checker",
407     ssh2_xml_description,
408     ssh2_onload
409   },
410   ssh2_config,
411   ssh2_init,
412   ssh2_initiate_check,
413   ssh2_cleanup
414 };
415
Note: See TracBrowser for help on using the browser.