root/src/modules/ssh2.c

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

Allow 'port' config option

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