root/src/noit_console.c

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

various changes to avoid dereferencing type-punned pointers and breaking strict-aliasing rules, refs #34

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