root/src/modules/ssh2.c

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

fix ssh2 module to use new stasts API

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