root/src/modules/ssh2.c

Revision 70c30ef22f6d1312375e0392164d755db5824cb5, 13.8 kB (checked in by Phil Maddox <philip.maddox@circonus.com>, 3 months ago)

Use mtevAssert and mtevFatal Instead Of assert() and abort()

Use libmtev calls to safely flush logs and abort rather than calling
the assert and abort calls directly.

  • 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 <math.h>
40 #ifdef HAVE_SYS_FILIO_H
41 #include <sys/filio.h>
42 #endif
43 #include <dlfcn.h>
44
45 #include <mtev_hash.h>
46
47 #include "noit_module.h"
48 #include "noit_check.h"
49 #include "noit_check_tools.h"
50 #include "noit_mtev_bridge.h"
51
52 #include <libssh2.h>
53 #ifdef HAVE_GCRYPT_H
54 #include <gcrypt.h>
55 #endif
56
57 #define DEFAULT_SSH_PORT 22
58
59 typedef struct {
60   noit_module_t *self;
61   noit_check_t *check;
62   struct {
63     char *kex;
64     char *hostkey;
65     char *crypt_cs;
66     char *crypt_sc;
67     char *mac_cs;
68     char *mac_sc;
69     char *comp_cs;
70     char *comp_sc;
71   } methods;
72   enum {
73     WANT_CONNECT = 0,
74     WANT_CLOSE = 1
75   } state;
76   LIBSSH2_SESSION *session;
77   int available;
78   int timed_out;
79   char *error;
80   char fingerprint[33]; /* 32 hex characters */
81   eventer_t synch_fd_event;
82   eventer_t timeout_event; /* Only used for connect() */
83 } ssh2_check_info_t;
84
85 static mtev_log_stream_t nlerr = NULL;
86 static mtev_log_stream_t nldeb = NULL;
87
88 static void ssh2_cleanup(noit_module_t *self, noit_check_t *check) {
89   ssh2_check_info_t *ci = check->closure;
90   if(ci) {
91     if(ci->timeout_event) {
92       eventer_remove(ci->timeout_event);
93       eventer_free(ci->timeout_event);
94     }
95     if(ci->session) {
96       libssh2_session_disconnect(ci->session, "Bye!");
97       libssh2_session_free(ci->session);
98     }
99     if(ci->methods.kex) free(ci->methods.kex);
100     if(ci->methods.hostkey) free(ci->methods.hostkey);
101     if(ci->methods.crypt_cs) free(ci->methods.crypt_cs);
102     if(ci->methods.crypt_sc) free(ci->methods.crypt_sc);
103     if(ci->methods.mac_cs) free(ci->methods.mac_cs);
104     if(ci->methods.mac_sc) free(ci->methods.mac_sc);
105     if(ci->methods.comp_cs) free(ci->methods.comp_cs);
106     if(ci->methods.comp_sc) free(ci->methods.comp_sc);
107     if(ci->error) free(ci->error);
108     memset(ci, 0, sizeof(*ci));
109   }
110 }
111
112 #ifdef HAVE_GCRYPT_H
113 GCRY_THREAD_OPTION_PTHREAD_IMPL;
114 #endif
115
116 static int ssh2_init(noit_module_t *self) {
117 #ifdef HAVE_GCRYPT_H
118   gcry_error_t (*control)(enum gcry_ctl_cmds CMD, ...);
119 #ifndef RTLD_DEFAULT
120 #define RTLD_DEFAULT ((void *)0)
121 #endif
122   control = dlsym(RTLD_DEFAULT, "gcry_control");
123   if(control) control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
124 #endif
125   return 0;
126 }
127 static int ssh2_config(noit_module_t *self, mtev_hash_table *options) {
128   return 0;
129 }
130 static void ssh2_log_results(noit_module_t *self, noit_check_t *check) {
131   struct timeval duration, now;
132   u_int32_t mduration;
133   ssh2_check_info_t *ci = check->closure;
134
135   gettimeofday(&now, NULL);
136   sub_timeval(now, check->last_fire_time, &duration);
137   mduration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
138   noit_stats_set_whence(check, &now);
139   noit_stats_set_duration(check, mduration);
140   noit_stats_set_available(check, ci->available ? NP_AVAILABLE : NP_UNAVAILABLE);
141   noit_stats_set_state(check, ci->fingerprint[0] ? NP_GOOD : NP_BAD);
142
143   if(ci->error) noit_stats_set_status(check, ci->error);
144   else if(ci->timed_out) noit_stats_set_status(check, "timeout");
145   else if(ci->fingerprint[0]) noit_stats_set_status(check, ci->fingerprint);
146   else noit_stats_set_status(check, "internal error");
147
148   if(ci->fingerprint[0]) {
149     noit_stats_set_metric(check, "duration", METRIC_UINT32, &mduration);
150     noit_stats_set_metric(check, "fingerprint", METRIC_STRING,
151                           ci->fingerprint);
152   }
153   noit_check_set_stats(check);
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       mtevFatal(mtev_error, "Unknown mask: 0x%04x\n", mask);
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   mtevAssert(ci->timeout_event);
250   asynch_e = eventer_remove(ci->timeout_event);
251   mtevAssert(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(mtev_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(mtev_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(mtev_image_t *self) {
421   nlerr = mtev_log_stream_find("error/ssh2");
422   nldeb = mtev_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.