root/src/noit_console.c

Revision 6480ae14408dabfad215b4f1369579f864f4b558, 13.1 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

fixes #105... it's a hack, but it appears to work... loses invalid sequences, but I think that's the desired outcome... luckily

  • 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 #ifdef HAVE_ALLOCA_H
11 #include <alloca.h>
12 #endif
13 #include <errno.h>
14 #include <sys/ioctl.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #ifdef HAVE_STROPTS_H
19 #include <stropts.h>
20 #endif
21 #ifdef HAVE_SYS_STREAM_H
22 #include <sys/stream.h>
23 #endif
24 #ifdef HAVE_TERMIOS_H
25 #include <termios.h>
26 #endif
27 #ifdef HAVE_UTIL_H
28 #include <util.h>
29 #endif
30 #include <arpa/telnet.h>
31 #include <signal.h>
32
33 #include "eventer/eventer.h"
34 #include "utils/noit_log.h"
35 #include "noit_listener.h"
36 #include "noit_console.h"
37 #include "noit_tokenizer.h"
38
39 static void
40 nc_telnet_cooker(noit_console_closure_t ncct) {
41   char *tmpbuf, *p, *n;
42   int r;
43
44   tmpbuf = ncct->outbuf;
45   if(ncct->outbuf_len == 0) return;
46
47   p = ncct->outbuf + ncct->outbuf_completed;
48   r = ncct->outbuf_len - ncct->outbuf_completed;
49   n = memchr(p, '\n', r);
50   /* No '\n'? Nothin' to do */
51   if(!n) {
52     ncct->outbuf_cooked = ncct->outbuf_len;
53     return;
54   }
55
56   /* Forget the outbuf -- it is now tmpbuf */
57   ncct->outbuf = NULL;
58   ncct->outbuf_allocd = 0;
59   ncct->outbuf_len = 0;
60   ncct->outbuf_completed = 0;
61   ncct->outbuf_cooked = 0;
62   do {
63     if(n == tmpbuf || *(n-1) != '\r') {
64       nc_write(ncct, p, n-p);   r -= n-p;
65       nc_write(ncct, "\r", 1);
66       p = n;
67     }
68     n = memchr(p+1, '\n', r-1);
69   } while(n);
70   nc_write(ncct, p, r);
71   ncct->outbuf_cooked = ncct->outbuf_len;
72   free(tmpbuf);
73 }
74 int
75 nc_printf(noit_console_closure_t ncct, const char *fmt, ...) {
76   int len;
77   va_list arg;
78   va_start(arg, fmt);
79   len = nc_vprintf(ncct, fmt, arg);
80   va_end(arg);
81   return len;
82 }
83 int
84 nc_vprintf(noit_console_closure_t ncct, const char *fmt, va_list arg) {
85 #ifdef va_copy
86   va_list copy;
87 #endif
88   int lenwanted;
89  
90   if(!ncct->outbuf_allocd) {
91     ncct->outbuf = malloc(4096);
92     if(!ncct->outbuf) return 0;
93     ncct->outbuf_allocd = 4096;
94   }
95   while(1) {
96     char *newbuf;
97 #ifdef va_copy
98     va_copy(copy, arg);
99     lenwanted = vsnprintf(ncct->outbuf + ncct->outbuf_len,
100                           ncct->outbuf_allocd - ncct->outbuf_len,
101                           fmt, copy);
102     va_end(copy);
103 #else
104     lenwanted = vsnprintf(ncct->outbuf + ncct->outbuf_len,
105                           ncct->outbuf_allocd - ncct->outbuf_len,
106                           fmt, arg);
107 #endif
108     if(ncct->outbuf_len + lenwanted < ncct->outbuf_allocd) {
109       /* All went well, things are as we want them. */
110       ncct->outbuf_len += lenwanted;
111       return lenwanted;
112     }
113
114     /* We need to enlarge the buffer */
115     lenwanted += ncct->outbuf_len;
116     lenwanted /= 4096;
117     lenwanted += 1;
118     lenwanted *= 4096;
119     newbuf = realloc(ncct->outbuf, lenwanted);
120     if(!newbuf) {
121       return 0;
122     }
123     ncct->outbuf = newbuf;
124     ncct->outbuf_allocd = lenwanted;
125   }
126   /* NOTREACHED */
127 }
128 int
129 nc_write(noit_console_closure_t ncct, const void *buf, int len) {
130   if(!ncct->outbuf_allocd) {
131     ncct->outbuf = malloc(len);
132     if(!ncct->outbuf) return 0;
133     ncct->outbuf_allocd = len;
134   }
135   else if(ncct->outbuf_allocd < ncct->outbuf_len + len) {
136     char *newbuf;
137     newbuf = realloc(ncct->outbuf, ncct->outbuf_len + len);
138     if(!newbuf) return 0;
139     ncct->outbuf = newbuf;
140   }
141   memcpy(ncct->outbuf + ncct->outbuf_len, buf, len);
142   ncct->outbuf_len += len;
143   return len;
144 }
145
146 static void
147 noit_console_userdata_free(void *data) {
148   noit_console_userdata_t *userdata = data;
149   if(userdata) {
150     if(userdata->name) free(userdata->name);
151     if(userdata->freefunc)
152       userdata->freefunc(userdata->data);
153     free(userdata);
154   }
155 }
156 void
157 noit_console_closure_free(noit_console_closure_t ncct) {
158   noit_log_stream_t lf;
159   if(ncct->el) el_end(ncct->el);
160   if(ncct->hist) history_end(ncct->hist);
161   if(ncct->pty_master >= 0) close(ncct->pty_master);
162   if(ncct->pty_slave >= 0) close(ncct->pty_slave);
163   if(ncct->outbuf) free(ncct->outbuf);
164   if(ncct->telnet) noit_console_telnet_free(ncct->telnet);
165   noit_hash_destroy(&ncct->userdata, NULL, noit_console_userdata_free);
166   while(ncct->state_stack) {
167     noit_console_state_stack_t *tmp;
168     tmp = ncct->state_stack;
169     ncct->state_stack = tmp->last;
170     free(tmp);
171   }
172   lf = noit_log_stream_find(ncct->feed_path);
173   noit_log_stream_remove(ncct->feed_path);
174   if(lf) {
175     noit_log_stream_free(lf);
176   }
177   free(ncct);
178 }
179
180 noit_console_closure_t
181 noit_console_closure_alloc() {
182   noit_console_closure_t new_ncct;
183   new_ncct = calloc(1, sizeof(*new_ncct));
184   noit_hash_init(&new_ncct->userdata);
185   noit_console_state_push_state(new_ncct, noit_console_state_initial());
186   new_ncct->pty_master = -1;
187   new_ncct->pty_slave = -1;
188   return new_ncct;
189 }
190
191 void
192 noit_console_userdata_set(struct __noit_console_closure *ncct,
193                           const char *name, void *data,
194                           state_userdata_free_func_t freefunc) {
195   noit_console_userdata_t *item;
196   item = calloc(1, sizeof(*item));
197   item->name = strdup(name);
198   item->data = data;
199   item->freefunc = freefunc;
200   noit_hash_replace(&ncct->userdata, item->name, strlen(item->name),
201                     item, NULL, noit_console_userdata_free);
202 }
203  
204 void *
205 noit_console_userdata_get(struct __noit_console_closure *ncct,
206                           const char *name) {
207   noit_console_userdata_t *item;
208   if(noit_hash_retrieve(&ncct->userdata, name, strlen(name),
209                         (void **)&item))
210     return item->data;
211   return NULL;
212 }
213
214
215 int
216 noit_console_continue_sending(noit_console_closure_t ncct,
217                               int *mask) {
218   int len;
219   eventer_t e = ncct->e;
220   if(!ncct->outbuf_len) return 0;
221   if(ncct->output_cooker) ncct->output_cooker(ncct);
222   while(ncct->outbuf_len > ncct->outbuf_completed) {
223     len = e->opset->write(e->fd, ncct->outbuf + ncct->outbuf_completed,
224                           ncct->outbuf_len - ncct->outbuf_completed,
225                           mask, e);
226     if(len < 0) {
227       if(errno == EAGAIN) return -1;
228       /* Do something else here? */
229       return -1;
230     }
231     ncct->outbuf_completed += len;
232   }
233   len = ncct->outbuf_len;
234   free(ncct->outbuf);
235   ncct->outbuf = NULL;
236   ncct->outbuf_allocd = ncct->outbuf_len =
237     ncct->outbuf_completed = ncct->outbuf_cooked = 0;
238   return len;
239 }
240
241 void
242 noit_console_dispatch(eventer_t e, const char *buffer,
243                       noit_console_closure_t ncct) {
244   char **cmds;
245   HistEvent ev;
246   int i, cnt = 32;
247
248   cmds = alloca(32 * sizeof(*cmds));
249   i = noit_tokenize(buffer, cmds, &cnt);
250
251   /* < 0 is an error, that's fine.  We want it in the history to "fix" */
252   /* > 0 means we had arguments, so let's put it in the history */
253   /* 0 means nothing -- and that isn't worthy of history inclusion */
254   if(i) history(ncct->hist, &ev, H_ENTER, buffer);
255
256   if(i>cnt) nc_printf(ncct, "Command length too long.\n");
257   else if(i<0) nc_printf(ncct, "Error at offset: %d\n", 0-i);
258   else noit_console_state_do(ncct, cnt, cmds);
259 }
260
261 void
262 noit_console_motd(eventer_t e, acceptor_closure_t *ac,
263                   noit_console_closure_t ncct) {
264   int ssl;
265   ssl = eventer_get_eventer_ssl_ctx(e) ? 1 : 0;
266   nc_printf(ncct, "noitd%s: %s\n",
267             ssl ? "(secure)" : "",
268             ac->remote_cn ? ac->remote_cn : "(no auth)");
269 }
270
271 int
272 allocate_pty(int *master, int *slave) {
273   long on = 1;
274 #ifdef HAVE_OPENPTY
275   if(openpty(master, slave, NULL, NULL, NULL)) return -1;
276 #else
277   /* STREAMS... sigh */
278   char   *slavename;
279   extern char *ptsname();
280
281   *master = open("/dev/ptmx", O_RDWR);  /* open master */
282   if(*master < 0) return -1;
283   grantpt(*master);                     /* change permission of   slave */
284   unlockpt(*master);                    /* unlock slave */
285   slavename = ptsname(*master);         /* get name of slave */
286   *slave = open(slavename, O_RDWR);    /* open slave */
287   if(*slave < 0) {
288     close(*master);
289     *master = -1;
290     return -1;
291   }
292   /* This is a bit backwards as we using the PTY backwards.
293    * We want to make the master a tty instead of the slave... odd, I know.
294    */
295   ioctl(*master, I_PUSH, "ptem");       /* push ptem */
296   ioctl(*master, I_PUSH, "ldterm");     /* push ldterm*/
297 #endif
298   if(ioctl(*master, FIONBIO, &on)) return -1;
299   noitL(noit_debug, "allocate_pty -> %d,%d\n", *master, *slave);
300   return 0;
301 }
302
303 int
304 noit_console_handler(eventer_t e, int mask, void *closure,
305                      struct timeval *now) {
306   int newmask = EVENTER_READ | EVENTER_EXCEPTION;
307   int keep_going;
308   acceptor_closure_t *ac = closure;
309   noit_console_closure_t ncct = ac->service_ctx;
310
311   if(mask & EVENTER_EXCEPTION || (ncct && ncct->wants_shutdown)) {
312 socket_error:
313     /* Exceptions cause us to simply snip the connection */
314
315     /* This removes the log feed which is important to do before calling close */
316     eventer_remove_fd(e->fd);
317     if(ncct) noit_console_closure_free(ncct);
318     if(ac) acceptor_closure_free(ac);
319     e->opset->close(e->fd, &newmask, e);
320     return 0;
321   }
322
323   if(!ac->service_ctx) {
324     ncct = ac->service_ctx = noit_console_closure_alloc();
325   }
326   if(!ncct->initialized) {
327     ncct->e = e;
328     if(allocate_pty(&ncct->pty_master, &ncct->pty_slave)) {
329       nc_printf(ncct, "Failed to open pty: %s\n", strerror(errno));
330       ncct->wants_shutdown = 1;
331     }
332     else {
333       const char *line_protocol;
334       HistEvent ev;
335       ncct->hist = history_init();
336       history(ncct->hist, &ev, H_SETSIZE, 500);
337       ncct->el = el_init("noitd", ncct->pty_master, NULL,
338                          e->fd, e, e->fd, e);
339       if(el_set(ncct->el, EL_USERDATA, ncct)) {
340         noitL(noit_error, "Cannot set userdata on noitedit session\n");
341         goto socket_error;
342       }
343       if(el_set(ncct->el, EL_EDITOR, "emacs"))
344         noitL(noit_error, "Cannot set emacs mode on console\n");
345       if(el_set(ncct->el, EL_HIST, history, ncct->hist))
346         noitL(noit_error, "Cannot set history on console\n");
347       if(!noit_hash_retrieve(ac->config,
348                              "line_protocol", strlen("line_protocol"),
349                              (void **)&line_protocol)) {
350         line_protocol = NULL;
351       }
352       if(line_protocol && !strcasecmp(line_protocol, "telnet")) {
353         ncct->telnet = noit_console_telnet_alloc(ncct);
354         ncct->output_cooker = nc_telnet_cooker;
355       }
356       noit_console_state_init(ncct);
357     }
358     snprintf(ncct->feed_path, sizeof(ncct->feed_path), "console/%d", e->fd);
359     noit_log_stream_new(ncct->feed_path, "noit_console", ncct->feed_path,
360                         ncct, NULL);
361     noit_console_motd(e, ac, ncct);
362     ncct->initialized = 1;
363   }
364
365   /* If we still have data to send back to the client, this will take
366    * care of that
367    */
368   if(noit_console_continue_sending(ncct, &newmask) < 0) {
369     if(ncct->wants_shutdown || errno != EAGAIN) goto socket_error;
370     return newmask | EVENTER_EXCEPTION;
371   }
372
373   for(keep_going=1 ; keep_going ; ) {
374     int len, plen;
375     char sbuf[4096];
376     const char *buffer;
377
378     keep_going = 0;
379
380     buffer = el_gets(ncct->el, &plen);
381     if(!el_eagain(ncct->el)) {
382       if(!buffer) {
383         buffer = "exit";
384         plen = 4;
385         nc_write(ncct, "\n", 1);
386       }
387       keep_going++;
388     }
389
390     len = e->opset->read(e->fd, sbuf, sizeof(sbuf)-1, &newmask, e);
391     if(len == 0 || (len < 0 && errno != EAGAIN)) {
392       eventer_remove_fd(e->fd);
393       close(e->fd);
394       return 0;
395     }
396     if(len > 0) {
397       keep_going++;
398       sbuf[len] = '\0';
399       if(ncct->telnet) {
400         noit_console_telnet_telrcv(ncct, sbuf, len);
401         ptyflush(ncct);
402       }
403       else {
404         write(ncct->pty_slave, sbuf, len);
405       }
406     }
407     if(buffer) {
408       char *cmd_buffer;
409       cmd_buffer = malloc(plen+1);
410       memcpy(cmd_buffer, buffer, plen);
411       /* chomp */
412       cmd_buffer[plen] = '\0';
413       if(cmd_buffer[plen-1] == '\n') cmd_buffer[plen-1] = '\0';
414       noitL(noit_debug, "IN[%d]: '%s'\n", plen, cmd_buffer);
415       noit_console_dispatch(e, cmd_buffer, ncct);
416       free(cmd_buffer);
417     }
418     if(noit_console_continue_sending(ncct, &newmask) == -1) {
419       if(ncct->wants_shutdown || errno != EAGAIN) goto socket_error;
420       return newmask | EVENTER_EXCEPTION;
421     }
422     if(ncct->wants_shutdown) goto socket_error;
423   }
424   return newmask | EVENTER_EXCEPTION;
425 }
426
427 static int
428 noit_console_logio_open(noit_log_stream_t ls) {
429   return 0;
430 }
431 static int
432 noit_console_logio_reopen(noit_log_stream_t ls) {
433   /* no op */
434   return 0;
435 }
436 static int
437 noit_console_logio_write(noit_log_stream_t ls, const void *buf, size_t len) {
438   noit_console_closure_t ncct = ls->op_ctx;
439   int rv, rlen, mask;
440   if(!ncct) return 0;
441   rlen = nc_write(ls->op_ctx, buf, len);
442   while((rv = noit_console_continue_sending(ncct, &mask)) == -1 && errno == EINTR);
443   if(rv == -1 && errno == EAGAIN) {
444     eventer_update(ncct->e, mask | EVENTER_EXCEPTION);
445   }
446   return rlen;
447 }
448 static int
449 noit_console_logio_close(noit_log_stream_t ls) {
450   ls->op_ctx = NULL;
451   return 0;
452 }
453 static logops_t noit_console_logio_ops = {
454   noit_console_logio_open,
455   noit_console_logio_reopen,
456   noit_console_logio_write,
457   noit_console_logio_close,
458   NULL
459 };
460
461 int
462 noit_console_write_xml(void *vncct, const char *buffer, int len) {
463   noit_console_closure_t ncct = vncct;
464   return nc_write(ncct, buffer, len);
465 }
466
467 int
468 noit_console_close_xml(void *vncct) {
469   return 0;
470 }
471
472 void
473 noit_console_init() {
474   el_multi_init();
475   signal(SIGTTOU, SIG_IGN);
476   noit_register_logops("noit_console", &noit_console_logio_ops);
477   eventer_name_callback("noit_console", noit_console_handler);
478 }
479
Note: See TracBrowser for help on using the browser.