root/src/modules/ssh2.c

Revision 304ec80b8cf842fc0abe5f9029790908b6455957, 14.0 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 2 months ago)

Convert to libmtev.

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