root/src/noit_console.c

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

should fix this. refs #200

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include <assert.h>
36 #include <stdio.h>
37 #include <unistd.h>
38 #ifdef HAVE_ALLOCA_H
39 #include <alloca.h>
40 #endif
41 #include <errno.h>
42 #include <sys/ioctl.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <fcntl.h>
46 #ifdef HAVE_STROPTS_H
47 #include <stropts.h>
48 #endif
49 #ifdef HAVE_SYS_STREAM_H
50 #include <sys/stream.h>
51 #endif
52 #ifdef HAVE_TERMIOS_H
53 #include <termios.h>
54 #endif
55 #ifdef HAVE_PTY_H
56 #include <pty.h>
57 #endif
58 #ifdef HAVE_UTIL_H
59 #include <util.h>
60 #endif
61 #include <arpa/telnet.h>
62 #include <signal.h>
63
64 #include "eventer/eventer.h"
65 #include "utils/noit_log.h"
66 #include "noit_listener.h"
67 #include "noit_console.h"
68 #include "noit_tokenizer.h"
69
70 #include "noitedit/sys.h"
71 #include "noitedit/el.h"
72 #include "noitedit/fcns.h"
73 #include "noitedit/map.h"
74
75 static void
76 nc_telnet_cooker(noit_console_closure_t ncct) {
77   char *tmpbuf, *p, *n;
78   int r;
79
80   tmpbuf = ncct->outbuf;
81   if(ncct->outbuf_len == 0) return;
82
83   p = ncct->outbuf + ncct->outbuf_completed;
84   r = ncct->outbuf_len - ncct->outbuf_completed;
85   n = memchr(p, '\n', r);
86   /* No '\n'? Nothin' to do */
87   if(!n) {
88     ncct->outbuf_cooked = ncct->outbuf_len;
89     return;
90   }
91
92   /* Forget the outbuf -- it is now tmpbuf */
93   ncct->outbuf = NULL;
94   ncct->outbuf_allocd = 0;
95   ncct->outbuf_len = 0;
96   ncct->outbuf_completed = 0;
97   ncct->outbuf_cooked = 0;
98   do {
99     nc_write(ncct, p, n-p);   r -= n-p;
100     if(n == tmpbuf || *(n-1) != '\r')
101       nc_write(ncct, "\r", 1);
102     p = n;
103     n = memchr(p+1, '\n', r-1);
104   } while(n);
105   nc_write(ncct, p, r);
106   ncct->outbuf_cooked = ncct->outbuf_len;
107   free(tmpbuf);
108 }
109 int
110 nc_printf(noit_console_closure_t ncct, const char *fmt, ...) {
111   int len;
112   va_list arg;
113   va_start(arg, fmt);
114   len = nc_vprintf(ncct, fmt, arg);
115   va_end(arg);
116   return len;
117 }
118 int
119 nc_vprintf(noit_console_closure_t ncct, const char *fmt, va_list arg) {
120 #ifdef va_copy
121   va_list copy;
122 #endif
123   int lenwanted;
124  
125   if(!ncct->outbuf_allocd) {
126     ncct->outbuf = malloc(4096);
127     if(!ncct->outbuf) return 0;
128     ncct->outbuf_allocd = 4096;
129   }
130   while(1) {
131     char *newbuf;
132 #ifdef va_copy
133     va_copy(copy, arg);
134     lenwanted = vsnprintf(ncct->outbuf + ncct->outbuf_len,
135                           ncct->outbuf_allocd - ncct->outbuf_len,
136                           fmt, copy);
137     va_end(copy);
138 #else
139     lenwanted = vsnprintf(ncct->outbuf + ncct->outbuf_len,
140                           ncct->outbuf_allocd - ncct->outbuf_len,
141                           fmt, arg);
142 #endif
143     if(ncct->outbuf_len + lenwanted < ncct->outbuf_allocd) {
144       /* All went well, things are as we want them. */
145       ncct->outbuf_len += lenwanted;
146       return lenwanted;
147     }
148
149     /* We need to enlarge the buffer */
150     lenwanted += ncct->outbuf_len;
151     lenwanted /= 4096;
152     lenwanted += 1;
153     lenwanted *= 4096;
154     newbuf = realloc(ncct->outbuf, lenwanted);
155     if(!newbuf) {
156       return 0;
157     }
158     ncct->outbuf = newbuf;
159     ncct->outbuf_allocd = lenwanted;
160   }
161   /* NOTREACHED */
162 }
163 int
164 nc_write(noit_console_closure_t ncct, const void *buf, int len) {
165   if(!ncct->outbuf_allocd) {
166     ncct->outbuf = malloc(len);
167     if(!ncct->outbuf) return 0;
168     ncct->outbuf_allocd = len;
169   }
170   else if(ncct->outbuf_allocd < ncct->outbuf_len + len) {
171     char *newbuf;
172     newbuf = realloc(ncct->outbuf, ncct->outbuf_len + len);
173     if(!newbuf) return 0;
174     ncct->outbuf = newbuf;
175   }
176   memcpy(ncct->outbuf + ncct->outbuf_len, buf, len);
177   ncct->outbuf_len += len;
178   return len;
179 }
180
181 static void
182 noit_console_userdata_free(void *data) {
183   noit_console_userdata_t *userdata = data;
184   if(userdata) {
185     if(userdata->name) free(userdata->name);
186     if(userdata->freefunc)
187       userdata->freefunc(userdata->data);
188     free(userdata);
189   }
190 }
191 void
192 noit_console_closure_free(noit_console_closure_t ncct) {
193   noit_log_stream_t lf;
194   if(ncct->el) el_end(ncct->el);
195   if(ncct->hist) history_end(ncct->hist);
196   if(ncct->pty_master >= 0) close(ncct->pty_master);
197   if(ncct->pty_slave >= 0) close(ncct->pty_slave);
198   if(ncct->outbuf) free(ncct->outbuf);
199   if(ncct->telnet) noit_console_telnet_free(ncct->telnet);
200   noit_hash_destroy(&ncct->userdata, NULL, noit_console_userdata_free);
201   while(ncct->state_stack) {
202     noit_console_state_stack_t *tmp;
203     tmp = ncct->state_stack;
204     ncct->state_stack = tmp->last;
205     free(tmp);
206   }
207   lf = noit_log_stream_find(ncct->feed_path);
208   noit_log_stream_remove(ncct->feed_path);
209   if(lf) {
210     noit_log_stream_free(lf);
211   }
212   free(ncct);
213 }
214
215 noit_console_closure_t
216 noit_console_closure_alloc() {
217   noit_console_closure_t new_ncct;
218   new_ncct = calloc(1, sizeof(*new_ncct));
219   noit_hash_init(&new_ncct->userdata);
220   noit_console_state_push_state(new_ncct, noit_console_state_initial());
221   new_ncct->pty_master = -1;
222   new_ncct->pty_slave = -1;
223   return new_ncct;
224 }
225
226 void
227 noit_console_userdata_set(struct __noit_console_closure *ncct,
228                           const char *name, void *data,
229                           state_userdata_free_func_t freefunc) {
230   noit_console_userdata_t *item;
231   item = calloc(1, sizeof(*item));
232   item->name = strdup(name);
233   item->data = data;
234   item->freefunc = freefunc;
235   noit_hash_replace(&ncct->userdata, item->name, strlen(item->name),
236                     item, NULL, noit_console_userdata_free);
237 }
238  
239 void *
240 noit_console_userdata_get(struct __noit_console_closure *ncct,
241                           const char *name) {
242   void *vitem;
243   if(noit_hash_retrieve(&ncct->userdata, name, strlen(name),
244                         &vitem))
245     return ((noit_console_userdata_t *)vitem)->data;
246   return NULL;
247 }
248
249
250 int
251 noit_console_continue_sending(noit_console_closure_t ncct,
252                               int *mask) {
253   int len;
254   eventer_t e = ncct->e;
255   if(!ncct->outbuf_len) return 0;
256   if(ncct->output_cooker) ncct->output_cooker(ncct);
257   while(ncct->outbuf_len > ncct->outbuf_completed) {
258     len = e->opset->write(e->fd, ncct->outbuf + ncct->outbuf_completed,
259                           ncct->outbuf_len - ncct->outbuf_completed,
260                           mask, e);
261     if(len < 0) {
262       if(errno == EAGAIN) return -1;
263       /* Do something else here? */
264       return -1;
265     }
266     ncct->outbuf_completed += len;
267   }
268   len = ncct->outbuf_len;
269   free(ncct->outbuf);
270   ncct->outbuf = NULL;
271   ncct->outbuf_allocd = ncct->outbuf_len =
272     ncct->outbuf_completed = ncct->outbuf_cooked = 0;
273   return len;
274 }
275
276 void
277 noit_console_dispatch(eventer_t e, const char *buffer,
278                       noit_console_closure_t ncct) {
279   char **cmds;
280   HistEvent ev;
281   int i, cnt = 32;
282
283   cmds = alloca(32 * sizeof(*cmds));
284   i = noit_tokenize(buffer, cmds, &cnt);
285
286   /* < 0 is an error, that's fine.  We want it in the history to "fix" */
287   /* > 0 means we had arguments, so let's put it in the history */
288   /* 0 means nothing -- and that isn't worthy of history inclusion */
289   if(i) history(ncct->hist, &ev, H_ENTER, buffer);
290
291   if(i>cnt) nc_printf(ncct, "Command length too long.\n");
292   else if(i<0) nc_printf(ncct, "Error at offset: %d\n", 0-i);
293   else noit_console_state_do(ncct, cnt, cmds);
294 }
295
296 void
297 noit_console_motd(eventer_t e, acceptor_closure_t *ac,
298                   noit_console_closure_t ncct) {
299   int ssl;
300   ssl = eventer_get_eventer_ssl_ctx(e) ? 1 : 0;
301   nc_printf(ncct, "noitd%s: %s\n",
302             ssl ? "(secure)" : "",
303             ac->remote_cn ? ac->remote_cn : "(no auth)");
304 }
305
306 int
307 allocate_pty(int *master, int *slave) {
308 #ifdef HAVE_OPENPTY
309   if(openpty(master, slave, NULL, NULL, NULL)) return -1;
310 #else
311   /* STREAMS... sigh */
312   char   *slavename;
313   extern char *ptsname();
314
315   *master = open("/dev/ptmx", O_RDWR);  /* open master */
316   if(*master < 0) return -1;
317   grantpt(*master);                     /* change permission of   slave */
318   unlockpt(*master);                    /* unlock slave */
319   slavename = ptsname(*master);         /* get name of slave */
320   *slave = open(slavename, O_RDWR);    /* open slave */
321   if(*slave < 0) {
322     close(*master);
323     *master = -1;
324     return -1;
325   }
326   /* This is a bit backwards as we using the PTY backwards.
327    * We want to make the master a tty instead of the slave... odd, I know.
328    */
329   ioctl(*master, I_PUSH, "ptem");       /* push ptem */
330   ioctl(*master, I_PUSH, "ldterm");     /* push ldterm*/
331 #endif
332   if(eventer_set_fd_nonblocking(*master)) return -1;
333   noitL(noit_debug, "allocate_pty -> %d,%d\n", *master, *slave);
334   return 0;
335 }
336
337 int
338 noit_console_handler(eventer_t e, int mask, void *closure,
339                      struct timeval *now) {
340   int newmask = EVENTER_READ | EVENTER_EXCEPTION;
341   int keep_going;
342   acceptor_closure_t *ac = closure;
343   noit_console_closure_t ncct = ac->service_ctx;
344
345   if(mask & EVENTER_EXCEPTION || (ncct && ncct->wants_shutdown)) {
346 socket_error:
347     /* Exceptions cause us to simply snip the connection */
348
349     /* This removes the log feed which is important to do before calling close */
350     eventer_remove_fd(e->fd);
351     if(ncct) noit_console_closure_free(ncct);
352     if(ac) acceptor_closure_free(ac);
353     e->opset->close(e->fd, &newmask, e);
354     return 0;
355   }
356
357   if(!ac->service_ctx) {
358     ncct = ac->service_ctx = noit_console_closure_alloc();
359   }
360   if(!ncct->initialized) {
361     ncct->e = e;
362     if(allocate_pty(&ncct->pty_master, &ncct->pty_slave)) {
363       nc_printf(ncct, "Failed to open pty: %s\n", strerror(errno));
364       ncct->wants_shutdown = 1;
365       goto socket_error;
366     }
367     else {
368       int i;
369       const char *line_protocol;
370       HistEvent ev;
371
372       ncct->hist = history_init();
373       history(ncct->hist, &ev, H_SETSIZE, 500);
374       ncct->el = el_init("noitd", ncct->pty_master, NULL,
375                          e->fd, e, e->fd, e);
376       if(!ncct->el) goto socket_error;
377       if(el_set(ncct->el, EL_USERDATA, ncct)) {
378         noitL(noit_error, "Cannot set userdata on noitedit session\n");
379         goto socket_error;
380       }
381       if(el_set(ncct->el, EL_EDITOR, "emacs"))
382         noitL(noit_error, "Cannot set emacs mode on console\n");
383       if(el_set(ncct->el, EL_HIST, history, ncct->hist))
384         noitL(noit_error, "Cannot set history on console\n");
385       el_set(ncct->el, EL_ADDFN, "noit_complete",
386              "auto completion functions for noit", noit_edit_complete);
387       el_set(ncct->el, EL_BIND, "^I", "noit_complete", NULL);
388       for(i=EL_NUM_FCNS; i < ncct->el->el_map.nfunc; i++) {
389         if(ncct->el->el_map.func[i] == noit_edit_complete) {
390           ncct->noit_edit_complete_cmdnum = i;
391           break;
392         }
393       }
394
395       if(!noit_hash_retr_str(ac->config,
396                              "line_protocol", strlen("line_protocol"),
397                              &line_protocol)) {
398         line_protocol = NULL;
399       }
400       if(line_protocol && !strcasecmp(line_protocol, "telnet")) {
401         ncct->telnet = noit_console_telnet_alloc(ncct);
402         ncct->output_cooker = nc_telnet_cooker;
403       }
404       noit_console_state_init(ncct);
405     }
406     snprintf(ncct->feed_path, sizeof(ncct->feed_path), "console/%d", e->fd);
407     noit_log_stream_new(ncct->feed_path, "noit_console", ncct->feed_path,
408                         ncct, NULL);
409     noit_console_motd(e, ac, ncct);
410     ncct->initialized = 1;
411   }
412
413   /* If we still have data to send back to the client, this will take
414    * care of that
415    */
416   if(noit_console_continue_sending(ncct, &newmask) < 0) {
417     if(ncct->wants_shutdown || errno != EAGAIN) goto socket_error;
418     return newmask | EVENTER_EXCEPTION;
419   }
420
421   for(keep_going=1 ; keep_going ; ) {
422     int len, plen;
423     char sbuf[4096];
424     const char *buffer;
425
426     keep_going = 0;
427
428     buffer = el_gets(ncct->el, &plen);
429     if(!el_eagain(ncct->el)) {
430       if(!buffer) {
431         buffer = "exit";
432         plen = 4;
433         nc_write(ncct, "\n", 1);
434       }
435       keep_going++;
436     }
437
438     len = e->opset->read(e->fd, sbuf, sizeof(sbuf)-1, &newmask, e);
439     if(len == 0 || (len < 0 && errno != EAGAIN)) {
440       eventer_remove_fd(e->fd);
441       close(e->fd);
442       return 0;
443     }
444     if(len > 0) {
445       keep_going++;
446       sbuf[len] = '\0';
447       if(ncct->telnet) {
448         noit_console_telnet_telrcv(ncct, sbuf, len);
449         ptyflush(ncct);
450       }
451       else {
452         int written;
453         written = write(ncct->pty_slave, sbuf, len);
454         if(written <= 0) goto socket_error;
455         assert(written == len);
456       }
457     }
458     if(buffer) {
459       char *cmd_buffer;
460       cmd_buffer = malloc(plen+1);
461       memcpy(cmd_buffer, buffer, plen);
462       /* chomp */
463       cmd_buffer[plen] = '\0';
464       if(cmd_buffer[plen-1] == '\n') cmd_buffer[plen-1] = '\0';
465       noitL(noit_debug, "IN[%d]: '%s'\n", plen, cmd_buffer);
466       noit_console_dispatch(e, cmd_buffer, ncct);
467       free(cmd_buffer);
468     }
469     if(noit_console_continue_sending(ncct, &newmask) == -1) {
470       if(ncct->wants_shutdown || errno != EAGAIN) goto socket_error;
471       return newmask | EVENTER_EXCEPTION;
472     }
473     if(ncct->wants_shutdown) goto socket_error;
474   }
475   return newmask | EVENTER_EXCEPTION;
476 }
477
478 static int
479 noit_console_logio_open(noit_log_stream_t ls) {
480   return 0;
481 }
482 static int
483 noit_console_logio_reopen(noit_log_stream_t ls) {
484   /* no op */
485   return 0;
486 }
487 static int
488 noit_console_logio_write(noit_log_stream_t ls, const void *buf, size_t len) {
489   noit_console_closure_t ncct = ls->op_ctx;
490   int rv, rlen, mask;
491   if(!ncct) return 0;
492   rlen = nc_write(ls->op_ctx, buf, len);
493   while((rv = noit_console_continue_sending(ncct, &mask)) == -1 && errno == EINTR);
494   if(rv == -1 && errno == EAGAIN) {
495     eventer_update(ncct->e, mask | EVENTER_EXCEPTION);
496   }
497   return rlen;
498 }
499 static int
500 noit_console_logio_close(noit_log_stream_t ls) {
501   ls->op_ctx = NULL;
502   return 0;
503 }
504 static logops_t noit_console_logio_ops = {
505   noit_console_logio_open,
506   noit_console_logio_reopen,
507   noit_console_logio_write,
508   noit_console_logio_close,
509   NULL
510 };
511
512 int
513 noit_console_write_xml(void *vncct, const char *buffer, int len) {
514   noit_console_closure_t ncct = vncct;
515   return nc_write(ncct, buffer, len);
516 }
517
518 int
519 noit_console_close_xml(void *vncct) {
520   return 0;
521 }
522
523 void
524 noit_console_init(const char *progname) {
525   if(progname) {
526     char buff[32];
527     snprintf(buff, sizeof(buff), "%s# ", progname);
528     noit_console_set_default_prompt(buff);
529   }
530   el_multi_init();
531   signal(SIGTTOU, SIG_IGN);
532   noit_register_logops("noit_console", &noit_console_logio_ops);
533   eventer_name_callback("noit_console", noit_console_handler);
534 }
535
Note: See TracBrowser for help on using the browser.