root/src/noit_console.c

Revision 680ff24492182d294fb819e53a04fa8f91769b84, 13.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

fixes #77

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