root/src/modules/ssh2.c

Revision 3596dc15e229f7b814203e71305d82868dac6c05, 8.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

timeout in initial connect needs to cleanup socket based on synch_fd_event, not the subject of the callback

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