root/src/modules/ssh2.c

Revision 4ed37cf09ac9817ced9312616da97a3a1e90c6b3, 13.9 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 3 months ago)

cleanup of modules, verbose structure setting

  • 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 #include <dlfcn.h>
44
45 #include "noit_module.h"
46 #include "noit_check.h"
47 #include "noit_check_tools.h"
48 #include "utils/noit_log.h"
49 #include "utils/noit_hash.h"
50
51 #include <libssh2.h>
52 #ifdef HAVE_GCRYPT_H
53 #include <gcrypt.h>
54 #endif
55
56 #define DEFAULT_SSH_PORT 22
57
58 typedef struct {
59   noit_module_t *self;
60   noit_check_t *check;
61   struct {
62     char *kex;
63     char *hostkey;
64     char *crypt_cs;
65     char *crypt_sc;
66     char *mac_cs;
67     char *mac_sc;
68     char *comp_cs;
69     char *comp_sc;
70   } methods;
71   enum {
72     WANT_CONNECT = 0,
73     WANT_CLOSE = 1
74   } state;
75   LIBSSH2_SESSION *session;
76   int available;
77   int timed_out;
78   char *error;
79   char fingerprint[33]; /* 32 hex characters */
80   eventer_t synch_fd_event;
81   eventer_t timeout_event; /* Only used for connect() */
82 } ssh2_check_info_t;
83
84 static noit_log_stream_t nlerr = NULL;
85 static noit_log_stream_t nldeb = NULL;
86
87 static void ssh2_cleanup(noit_module_t *self, noit_check_t *check) {
88   ssh2_check_info_t *ci = check->closure;
89   if(ci) {
90     if(ci->timeout_event) {
91       eventer_remove(ci->timeout_event);
92       eventer_free(ci->timeout_event);
93     }
94     if(ci->session) {
95       libssh2_session_disconnect(ci->session, "Bye!");
96       libssh2_session_free(ci->session);
97     }
98     if(ci->methods.kex) free(ci->methods.kex);
99     if(ci->methods.hostkey) free(ci->methods.hostkey);
100     if(ci->methods.crypt_cs) free(ci->methods.crypt_cs);
101     if(ci->methods.crypt_sc) free(ci->methods.crypt_sc);
102     if(ci->methods.mac_cs) free(ci->methods.mac_cs);
103     if(ci->methods.mac_sc) free(ci->methods.mac_sc);
104     if(ci->methods.comp_cs) free(ci->methods.comp_cs);
105     if(ci->methods.comp_sc) free(ci->methods.comp_sc);
106     if(ci->error) free(ci->error);
107     memset(ci, 0, sizeof(*ci));
108   }
109 }
110
111 #ifdef HAVE_GCRYPT_H
112 GCRY_THREAD_OPTION_PTHREAD_IMPL;
113 #endif
114
115 static int ssh2_init(noit_module_t *self) {
116 #ifdef HAVE_GCRYPT_H
117   gcry_error_t (*control)(enum gcry_ctl_cmds CMD, ...);
118 #ifndef RTLD_DEFAULT
119 #define RTLD_DEFAULT ((void *)0)
120 #endif
121   control = dlsym(RTLD_DEFAULT, "gcry_control");
122   if(control) control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
123 #endif
124   return 0;
125 }
126 static int ssh2_config(noit_module_t *self, noit_hash_table *options) {
127   return 0;
128 }
129 static void ssh2_log_results(noit_module_t *self, noit_check_t *check) {
130   struct timeval duration;
131   ssh2_check_info_t *ci = check->closure;
132
133   noit_check_stats_clear(check, &check->stats.inprogress);
134
135   gettimeofday(&check->stats.inprogress.whence, NULL);
136   sub_timeval(check->stats.inprogress.whence, check->last_fire_time, &duration);
137   check->stats.inprogress.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
138   check->stats.inprogress.available = ci->available ? NP_AVAILABLE : NP_UNAVAILABLE;
139   check->stats.inprogress.state = ci->fingerprint[0] ? NP_GOOD : NP_BAD;
140
141   if(ci->error) check->stats.inprogress.status = ci->error;
142   else if(ci->timed_out) check->stats.inprogress.status = "timeout";
143   else if(ci->fingerprint[0]) check->stats.inprogress.status = ci->fingerprint;
144   else check->stats.inprogress.status = "internal error";
145
146   if(ci->fingerprint[0]) {
147     u_int32_t mduration = check->stats.inprogress.duration;
148     noit_stats_set_metric(check, &check->stats.inprogress, "duration", METRIC_UINT32, &mduration);
149     noit_stats_set_metric(check, &check->stats.inprogress, "fingerprint", METRIC_STRING,
150                           ci->fingerprint);
151   }
152   noit_check_set_stats(check, &check->stats.inprogress);
153   noit_check_stats_clear(check, &check->stats.inprogress);
154 }
155 static int ssh2_drive_session(eventer_t e, int mask, void *closure,
156                               struct timeval *now) {
157   int i;
158   const char *fingerprint;
159   ssh2_check_info_t *ci = closure;
160   struct timeval diff;
161   int timeout_ms = 10; /* 10ms, gets set below */
162   if(ci->state == WANT_CLOSE) {
163     noit_check_t *check = ci->check;
164     ssh2_log_results(ci->self, ci->check);
165     ssh2_cleanup(ci->self, ci->check);
166     eventer_remove_fd(e->fd);
167     e->opset->close(e->fd, &mask, e);
168     check->flags &= ~NP_RUNNING;
169     return 0;
170   }
171   switch(mask) {
172     case EVENTER_ASYNCH_WORK:
173       if(eventer_set_fd_blocking(e->fd)) {
174         ci->timed_out = 0;
175         ci->error = strdup("socket error");
176         return 0;
177       }
178       ci->session = libssh2_session_init();
179 #define set_method(a,b) do { \
180   int rv; \
181   if(ci->methods.a && \
182      (rv = libssh2_session_method_pref(ci->session, b, ci->methods.a)) != 0) { \
183     ci->timed_out = 0; \
184     ci->error = strdup((rv == LIBSSH2_ERROR_METHOD_NOT_SUPPORTED) ? \
185                          #a " method not supported" : "error setting " #a); \
186     return 0; \
187   } \
188 } while(0)
189       set_method(kex, LIBSSH2_METHOD_KEX);
190       set_method(hostkey, LIBSSH2_METHOD_HOSTKEY);
191       set_method(crypt_cs, LIBSSH2_METHOD_CRYPT_CS);
192       set_method(crypt_sc, LIBSSH2_METHOD_CRYPT_SC);
193       set_method(mac_cs, LIBSSH2_METHOD_MAC_CS);
194       set_method(mac_sc, LIBSSH2_METHOD_MAC_SC);
195       set_method(comp_cs, LIBSSH2_METHOD_COMP_CS);
196       set_method(comp_sc, LIBSSH2_METHOD_COMP_SC);
197       if(compare_timeval(*now, e->whence) < 0) {
198         sub_timeval(e->whence, *now, &diff);
199         timeout_ms = diff.tv_sec * 1000 + diff.tv_usec / 1000;
200       }
201 #if LIBSSH2_VERSION_NUM >= 0x010209
202       libssh2_session_set_timeout(ci->session, timeout_ms);
203 #endif
204       if (libssh2_session_startup(ci->session, e->fd)) {
205         ci->timed_out = 0;
206         ci->error = strdup("ssh session startup failed");
207         return 0;
208       }
209       fingerprint = libssh2_hostkey_hash(ci->session, LIBSSH2_HOSTKEY_HASH_MD5);
210       for(i=0;i<16;i++) {
211         snprintf(ci->fingerprint + (i*2), 3, "%02x",
212                  (unsigned char)fingerprint[i]);
213       }
214       ci->fingerprint[32] = '\0';
215       ci->timed_out = 0;
216       return 0;
217       break;
218     case EVENTER_ASYNCH_CLEANUP:
219       if(ci->session) {
220         libssh2_session_disconnect(ci->session, "Bye!");
221         libssh2_session_free(ci->session);
222         ci->session = NULL;
223       }
224       ci->state = WANT_CLOSE;
225       break;
226     default:
227       abort();
228   }
229   return 0;
230 }
231 static int ssh2_needs_bytes_as_libssh2_is_impatient(eventer_t e, int mask, void *closure,
232                                                     struct timeval *now) {
233   ssh2_check_info_t *ci = closure;
234   eventer_t asynch_e;
235
236   if(mask & EVENTER_EXCEPTION) {
237     noit_check_t *check = ci->check;
238     ci->timed_out = 0;
239     ci->error = strdup("ssh connection failed");
240     ssh2_log_results(ci->self, ci->check);
241     ssh2_cleanup(ci->self, ci->check);
242     eventer_remove_fd(e->fd);
243     e->opset->close(e->fd, &mask, e);
244     check->flags &= ~NP_RUNNING;
245     return 0;
246   }
247
248   /* We steal the timeout_event as it has the exact timeout we want. */
249   assert(ci->timeout_event);
250   asynch_e = eventer_remove(ci->timeout_event);
251   assert(asynch_e);
252   ci->timeout_event = NULL;
253
254   ci->synch_fd_event = NULL;
255   asynch_e->fd = e->fd;
256   asynch_e->callback = ssh2_drive_session;
257   asynch_e->closure = closure;
258   asynch_e->mask = EVENTER_ASYNCH;
259   eventer_add(asynch_e);
260
261   eventer_remove_fd(e->fd);
262   return 0;
263 }
264 static int ssh2_connect_complete(eventer_t e, int mask, void *closure,
265                                  struct timeval *now) {
266   ssh2_check_info_t *ci = closure;
267
268   if(mask & EVENTER_EXCEPTION) {
269     noit_check_t *check = ci->check;
270     ci->timed_out = 0;
271     ci->error = strdup("ssh connection failed");
272     ssh2_log_results(ci->self, ci->check);
273     ssh2_cleanup(ci->self, ci->check);
274     eventer_remove_fd(e->fd);
275     e->opset->close(e->fd, &mask, e);
276     check->flags &= ~NP_RUNNING;
277     return 0;
278   }
279
280   ci->available = 1;
281   e->callback = ssh2_needs_bytes_as_libssh2_is_impatient;
282   e->mask = EVENTER_READ | EVENTER_EXCEPTION;
283   return e->mask;
284 }
285 static int ssh2_connect_timeout(eventer_t e, int mask, void *closure,
286                                 struct timeval *now) {
287   eventer_t fde;
288   ssh2_check_info_t *ci = closure;
289   noit_check_t *check = ci->check;
290  
291   ci->timeout_event = NULL; /* This is us, return 0 will free this */
292   ci->error = strdup("ssh connect timeout");
293   if(ci->synch_fd_event) {
294     fde = ci->synch_fd_event;
295     eventer_remove_fd(fde->fd);
296     fde->opset->close(fde->fd, &mask, fde);
297     eventer_free(fde);
298      ci->synch_fd_event = NULL;
299   }
300   ssh2_log_results(ci->self, ci->check);
301   ssh2_cleanup(ci->self, ci->check);
302   check->flags &= ~NP_RUNNING;
303   return 0;
304 }
305 static int ssh2_initiate(noit_module_t *self, noit_check_t *check,
306                          noit_check_t *cause) {
307   ssh2_check_info_t *ci = check->closure;
308   struct timeval p_int, __now;
309   int fd = -1, rv = -1;
310   eventer_t e;
311   union {
312     struct sockaddr_in sin;
313     struct sockaddr_in6 sin6;
314   } sockaddr;
315   socklen_t sockaddr_len;
316   unsigned short ssh_port = DEFAULT_SSH_PORT;
317   const char *port_str = NULL;
318
319   /* We cannot be running */
320   BAIL_ON_RUNNING_CHECK(check);
321   check->flags |= NP_RUNNING;
322
323   ci->self = self;
324   ci->check = check;
325
326   ci->timed_out = 1;
327   if(ci->timeout_event) {
328     eventer_remove(ci->timeout_event);
329     free(ci->timeout_event->closure);
330     eventer_free(ci->timeout_event);
331     ci->timeout_event = NULL;
332   }
333   gettimeofday(&__now, NULL);
334   memcpy(&check->last_fire_time, &__now, sizeof(__now));
335
336   if(check->target_ip[0] == '\0') {
337     ci->error = strdup("name resolution failure");
338     goto fail;
339   }
340   /* Open a socket */
341   fd = socket(check->target_family, NE_SOCK_CLOEXEC|SOCK_STREAM, 0);
342   if(fd < 0) goto fail;
343
344   /* Make it non-blocking */
345   if(eventer_set_fd_nonblocking(fd)) goto fail;
346
347   if(noit_hash_retr_str(check->config, "port", strlen("port"),
348                         &port_str)) {
349     ssh_port = (unsigned short)atoi(port_str);
350   }
351 #define config_method(a) do { \
352   const char *v; \
353   if(noit_hash_retr_str(check->config, "method_" #a, strlen("method_" #a), \
354                         &v)) \
355     ci->methods.a = strdup(v); \
356 } while(0)
357   config_method(kex);
358   config_method(hostkey);
359   config_method(crypt_cs);
360   config_method(crypt_sc);
361   config_method(mac_cs);
362   config_method(mac_sc);
363   config_method(comp_cs);
364   config_method(comp_sc);
365   memset(&sockaddr, 0, sizeof(sockaddr));
366   sockaddr.sin6.sin6_family = check->target_family;
367   if(check->target_family == AF_INET) {
368     memcpy(&sockaddr.sin.sin_addr,
369            &check->target_addr.addr, sizeof(sockaddr.sin.sin_addr));
370     sockaddr.sin.sin_port = htons(ssh_port);
371     sockaddr_len = sizeof(sockaddr.sin);
372   }
373   else {
374     memcpy(&sockaddr.sin6.sin6_addr,
375            &check->target_addr.addr6, sizeof(sockaddr.sin6.sin6_addr));
376     sockaddr.sin6.sin6_port = htons(ssh_port);
377     sockaddr_len = sizeof(sockaddr.sin6);
378   }
379
380   /* Initiate a connection */
381   rv = connect(fd, (struct sockaddr *)&sockaddr, sockaddr_len);
382   if(rv == -1 && errno != EINPROGRESS) goto fail;
383
384   /* Register a handler for connection completion */
385   e = eventer_alloc();
386   e->fd = fd;
387   e->mask = EVENTER_READ | EVENTER_WRITE | EVENTER_EXCEPTION;
388   e->callback = ssh2_connect_complete;
389   e->closure =  ci;
390   ci->synch_fd_event = e;
391   eventer_add(e);
392
393   e = eventer_alloc();
394   e->mask = EVENTER_TIMER;
395   e->callback = ssh2_connect_timeout;
396   e->closure = ci;
397   memcpy(&e->whence, &__now, sizeof(__now));
398   p_int.tv_sec = check->timeout / 1000;
399   p_int.tv_usec = (check->timeout % 1000) * 1000;
400   add_timeval(e->whence, p_int, &e->whence);
401   ci->timeout_event = e;
402   eventer_add(e);
403   return 0;
404
405  fail:
406   if(fd >= 0) close(fd);
407   ssh2_log_results(ci->self, ci->check);
408   ssh2_cleanup(ci->self, ci->check);
409   check->flags &= ~NP_RUNNING;
410   return -1;
411 }
412
413 static int ssh2_initiate_check(noit_module_t *self, noit_check_t *check,
414                                int once, noit_check_t *cause) {
415   if(!check->closure) check->closure = calloc(1, sizeof(ssh2_check_info_t));
416   INITIATE_CHECK(ssh2_initiate, self, check, cause);
417   return 0;
418 }
419
420 static int ssh2_onload(noit_image_t *self) {
421   nlerr = noit_log_stream_find("error/ssh2");
422   nldeb = noit_log_stream_find("debug/ssh2");
423   if(!nlerr) nlerr = noit_stderr;
424   if(!nldeb) nldeb = noit_debug;
425
426   eventer_name_callback("http/ssh2_connect_complete", ssh2_connect_complete);
427   eventer_name_callback("http/ssh2_drive_session", ssh2_drive_session);
428   return 0;
429 }
430
431 #include "ssh2.xmlh"
432 noit_module_t ssh2 = {
433   {
434     .magic = NOIT_MODULE_MAGIC,
435     .version = NOIT_MODULE_ABI_VERSION,
436     .name = "ssh2",
437     .description = "Secure Shell version 2 checker",
438     .xml_description = ssh2_xml_description,
439     .onload = ssh2_onload
440   },
441   ssh2_config,
442   ssh2_init,
443   ssh2_initiate_check,
444   ssh2_cleanup
445 };
446
Note: See TracBrowser for help on using the browser.