root/src/noit_console.c

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

refs #37... lots of progress here

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