root/src/modules/ssh2.c

Revision 88a71780101cbf23034aa0cb840f9f0368fda2dd, 10.2 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

fixes #126

  • 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   enum {
58     WANT_CONNECT = 0,
59     WANT_CLOSE = 1
60   } state;
61   LIBSSH2_SESSION *session;
62   int available;
63   int timed_out;
64   char *error;
65   char fingerprint[33]; /* 32 hex characters */
66   eventer_t synch_fd_event;
67   eventer_t timeout_event; /* Only used for connect() */
68 } ssh2_check_info_t;
69
70 static noit_log_stream_t nlerr = NULL;
71 static noit_log_stream_t nldeb = NULL;
72
73 static void ssh2_cleanup(noit_module_t *self, noit_check_t *check) {
74   ssh2_check_info_t *ci = check->closure;
75   if(ci) {
76     if(ci->timeout_event) {
77       eventer_remove(ci->timeout_event);
78       eventer_free(ci->timeout_event);
79     }
80     if(ci->session) {
81       libssh2_session_disconnect(ci->session, "Bye!");
82       libssh2_session_free(ci->session);
83     }
84     if(ci->error) free(ci->error);
85     memset(ci, 0, sizeof(*ci));
86   }
87 }
88 static int ssh2_init(noit_module_t *self) {
89   return 0;
90 }
91 static int ssh2_config(noit_module_t *self, noit_hash_table *options) {
92   return 0;
93 }
94 static void ssh2_log_results(noit_module_t *self, noit_check_t *check) {
95   stats_t current;
96   struct timeval duration;
97   ssh2_check_info_t *ci = check->closure;
98
99   noit_check_stats_clear(&current);
100
101   gettimeofday(&current.whence, NULL);
102   sub_timeval(current.whence, check->last_fire_time, &duration);
103   current.duration = duration.tv_sec * 1000 + duration.tv_usec / 1000;
104   current.available = ci->available ? NP_AVAILABLE : NP_UNAVAILABLE;
105   current.state = ci->fingerprint[0] ? NP_GOOD : NP_BAD;
106
107   if(ci->error) current.status = ci->error;
108   else if(ci->timed_out) current.status = "timeout";
109   else if(ci->fingerprint[0]) current.status = ci->fingerprint;
110   else current.status = "internal error";
111
112   if(ci->fingerprint[0])
113     noit_stats_set_metric(&current, "fingerprint", METRIC_STRING,
114                           ci->fingerprint);
115   noit_check_set_stats(self, check, &current);
116 }
117 static int ssh2_drive_session(eventer_t e, int mask, void *closure,
118                               struct timeval *now) {
119   int i;
120   long on;
121   const char *fingerprint;
122   ssh2_check_info_t *ci = closure;
123   if(ci->state == WANT_CLOSE) {
124     noit_check_t *check = ci->check;
125     ssh2_log_results(ci->self, ci->check);
126     ssh2_cleanup(ci->self, ci->check);
127     eventer_remove_fd(e->fd);
128     e->opset->close(e->fd, &mask, e);
129     check->flags &= ~NP_RUNNING;
130     return 0;
131   }
132   switch(mask) {
133     case EVENTER_ASYNCH_WORK:
134       on = 0;
135       if(ioctl(e->fd, FIONBIO, &on)) {
136         ci->timed_out = 0;
137         ci->error = strdup("socket error");
138         return 0;
139       }
140       ci->session = libssh2_session_init();
141       if (libssh2_session_startup(ci->session, e->fd)) {
142         ci->timed_out = 0;
143         ci->error = strdup("ssh session startup failed");
144         return 0;
145       }
146       fingerprint = libssh2_hostkey_hash(ci->session, LIBSSH2_HOSTKEY_HASH_MD5);
147       for(i=0;i<16;i++) {
148         snprintf(ci->fingerprint + (i*2), 3, "%02x",
149                  (unsigned char)fingerprint[i]);
150       }
151       ci->fingerprint[32] = '\0';
152       ci->timed_out = 0;
153       return 0;
154       break;
155     case EVENTER_ASYNCH_CLEANUP:
156       if(ci->session) {
157         libssh2_session_disconnect(ci->session, "Bye!");
158         libssh2_session_free(ci->session);
159         ci->session = NULL;
160       }
161       ci->state = WANT_CLOSE;
162       break;
163     default:
164       abort();
165   }
166   return 0;
167 }
168 static int ssh2_connect_complete(eventer_t e, int mask, void *closure,
169                                  struct timeval *now) {
170   ssh2_check_info_t *ci = closure;
171   eventer_t asynch_e;
172
173   if(mask & EVENTER_EXCEPTION) {
174     noit_check_t *check = ci->check;
175     ci->timed_out = 0;
176     ci->error = strdup("ssh connection failed");
177     ssh2_log_results(ci->self, ci->check);
178     ssh2_cleanup(ci->self, ci->check);
179     eventer_remove_fd(e->fd);
180     e->opset->close(e->fd, &mask, e);
181     check->flags &= ~NP_RUNNING;
182     return 0;
183   }
184
185   ci->available = 1;
186
187   /* We steal the timeout_event as it has the exact timeout we want. */
188   assert(ci->timeout_event);
189   asynch_e = eventer_remove(ci->timeout_event);
190   assert(asynch_e);
191   ci->timeout_event = NULL;
192
193   ci->synch_fd_event = NULL;
194   asynch_e->fd = e->fd;
195   asynch_e->callback = ssh2_drive_session;
196   asynch_e->closure = closure;
197   asynch_e->mask = EVENTER_ASYNCH;
198   eventer_add(asynch_e);
199
200   eventer_remove_fd(e->fd);
201   return 0;
202 }
203 static int ssh2_connect_timeout(eventer_t e, int mask, void *closure,
204                                 struct timeval *now) {
205   eventer_t fde;
206   ssh2_check_info_t *ci = closure;
207   noit_check_t *check = ci->check;
208  
209   ci->timeout_event = NULL; /* This is us, return 0 will free this */
210   ci->error = strdup("ssh connect timeout");
211   if(ci->synch_fd_event) {
212     fde = ci->synch_fd_event;
213     eventer_remove_fd(fde->fd);
214     fde->opset->close(fde->fd, &mask, fde);
215     eventer_free(fde);
216      ci->synch_fd_event = NULL;
217   }
218   ssh2_log_results(ci->self, ci->check);
219   ssh2_cleanup(ci->self, ci->check);
220   check->flags &= ~NP_RUNNING;
221   return 0;
222 }
223 static int ssh2_initiate(noit_module_t *self, noit_check_t *check) {
224   ssh2_check_info_t *ci = check->closure;
225   struct timeval p_int, __now;
226   int fd, rv;
227   eventer_t e;
228   union {
229     struct sockaddr_in sin;
230     struct sockaddr_in6 sin6;
231   } sockaddr;
232   socklen_t sockaddr_len;
233   unsigned short ssh_port = DEFAULT_SSH_PORT;
234   const char *port_str = NULL;
235   long on;
236
237   /* We cannot be running */
238   assert(!(check->flags & NP_RUNNING));
239   check->flags |= NP_RUNNING;
240
241   ci->self = self;
242   ci->check = check;
243
244   ci->timed_out = 1;
245   if(ci->timeout_event) {
246     eventer_remove(ci->timeout_event);
247     free(ci->timeout_event->closure);
248     eventer_free(ci->timeout_event);
249     ci->timeout_event = NULL;
250   }
251   gettimeofday(&__now, NULL);
252   memcpy(&check->last_fire_time, &__now, sizeof(__now));
253
254   /* Open a socket */
255   fd = socket(check->target_family, SOCK_STREAM, 0);
256   if(fd < 0) goto fail;
257
258   /* Make it non-blocking */
259   on = 1;
260   if(ioctl(fd, FIONBIO, &on)) goto fail;
261
262   if(noit_hash_retr_str(check->config, "port", strlen("port"),
263                         &port_str)) {
264     ssh_port = (unsigned short)atoi(port_str);
265   }
266   memset(&sockaddr, 0, sizeof(sockaddr));
267   sockaddr.sin6.sin6_family = check->target_family;
268   if(check->target_family == AF_INET) {
269     memcpy(&sockaddr.sin.sin_addr,
270            &check->target_addr.addr, sizeof(sockaddr.sin.sin_addr));
271     sockaddr.sin.sin_port = htons(ssh_port);
272     sockaddr_len = sizeof(sockaddr.sin);
273   }
274   else {
275     memcpy(&sockaddr.sin6.sin6_addr,
276            &check->target_addr.addr6, sizeof(sockaddr.sin6.sin6_addr));
277     sockaddr.sin6.sin6_port = htons(ssh_port);
278     sockaddr_len = sizeof(sockaddr.sin6);
279   }
280
281   /* Initiate a connection */
282   rv = connect(fd, (struct sockaddr *)&sockaddr, sockaddr_len);
283   if(rv == -1 && errno != EINPROGRESS) goto fail;
284
285   /* Register a handler for connection completion */
286   e = eventer_alloc();
287   e->fd = fd;
288   e->mask = EVENTER_READ | EVENTER_WRITE | EVENTER_EXCEPTION;
289   e->callback = ssh2_connect_complete;
290   e->closure =  ci;
291   ci->synch_fd_event = e;
292   eventer_add(e);
293
294   e = eventer_alloc();
295   e->mask = EVENTER_TIMER;
296   e->callback = ssh2_connect_timeout;
297   e->closure = ci;
298   memcpy(&e->whence, &__now, sizeof(__now));
299   p_int.tv_sec = check->timeout / 1000;
300   p_int.tv_usec = (check->timeout % 1000) * 1000;
301   add_timeval(e->whence, p_int, &e->whence);
302   ci->timeout_event = e;
303   eventer_add(e);
304   return 0;
305
306  fail:
307   if(fd >= 0) close(fd);
308   ssh2_log_results(ci->self, ci->check);
309   ssh2_cleanup(ci->self, ci->check);
310   check->flags &= ~NP_RUNNING;
311   return -1;
312 }
313
314 static int ssh2_initiate_check(noit_module_t *self, noit_check_t *check,
315                                int once, noit_check_t *parent) {
316   if(!check->closure) check->closure = calloc(1, sizeof(ssh2_check_info_t));
317   INITIATE_CHECK(ssh2_initiate, self, check);
318   return 0;
319 }
320
321 static int ssh2_onload(noit_image_t *self) {
322   nlerr = noit_log_stream_find("error/ssh2");
323   nldeb = noit_log_stream_find("debug/ssh2");
324   if(!nlerr) nlerr = noit_stderr;
325   if(!nldeb) nldeb = noit_debug;
326
327   eventer_name_callback("http/ssh2_connect_complete", ssh2_connect_complete);
328   eventer_name_callback("http/ssh2_drive_session", ssh2_drive_session);
329   return 0;
330 }
331
332 #include "ssh2.xmlh"
333 noit_module_t ssh2 = {
334   {
335     NOIT_MODULE_MAGIC,
336     NOIT_MODULE_ABI_VERSION,
337     "ssh2",
338     "Secure Shell version 2 checker",
339     ssh2_xml_description,
340     ssh2_onload
341   },
342   ssh2_config,
343   ssh2_init,
344   ssh2_initiate_check,
345   ssh2_cleanup
346 };
347
Note: See TracBrowser for help on using the browser.