1 /*
2 * main.c
3 *
4 * Copyright (c) 1996-1999 Whistle Communications, Inc.
5 * All rights reserved.
6 *
7 * Subject to the following obligations and disclaimer of warranty, use and
8 * redistribution of this software, in source or object code forms, with or
9 * without modifications are expressly permitted by Whistle Communications;
10 * provided, however, that:
11 * 1. Any and all reproductions of the source or object code must include the
12 * copyright notice above and the following disclaimer of warranties; and
13 * 2. No rights are granted, in any manner or form, to use Whistle
14 * Communications, Inc. trademarks, including the mark "WHISTLE
15 * COMMUNICATIONS" on advertising, endorsements, or otherwise except as
16 * such appears in the above copyright notice or in the software.
17 *
18 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
19 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
20 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
21 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
22 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
23 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
24 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
25 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
26 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
27 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
28 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
29 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
30 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
33 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
34 * OF SUCH DAMAGE.
35 *
36 * $Whistle: main.c,v 1.12 1999/11/29 19:17:46 archie Exp $
37 */
38
39 #include <sys/param.h>
40 #include <sys/socket.h>
41
42 #include <ctype.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <limits.h>
46 #include <poll.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <sysexits.h>
51 #include <unistd.h>
52 #ifdef EDITLINE
53 #include <signal.h>
54 #include <histedit.h>
55 #include <pthread.h>
56 #endif
57 #ifdef JAIL
58 #include <sys/jail.h>
59 #include <jail.h>
60 #endif
61
62 #include <netgraph.h>
63
64 #include "ngctl.h"
65
66 #define PROMPT "+ "
67 #define MAX_ARGS 512
68 #define WHITESPACE " \t\r\n\v\f"
69 #define DUMP_BYTES_PER_LINE 16
70
71 /* Internal functions */
72 static int ReadFile(FILE *fp);
73 static void ReadCtrlSocket(void);
74 static void ReadDataSocket(void);
75 static int DoParseCommand(const char *line);
76 static int DoCommand(int ac, char **av);
77 static int DoInteractive(void);
78 static const struct ngcmd *FindCommand(const char *string);
79 static int MatchCommand(const struct ngcmd *cmd, const char *s);
80 static void Usage(const char *msg);
81 static int ReadCmd(int ac, char **av);
82 static int HelpCmd(int ac, char **av);
83 static int QuitCmd(int ac, char **av);
84 #ifdef EDITLINE
85 static volatile sig_atomic_t unblock;
86 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
87 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
88 #endif
89
90 /* List of commands */
91 static const struct ngcmd *const cmds[] = {
92 &config_cmd,
93 &connect_cmd,
94 &debug_cmd,
95 &dot_cmd,
96 &help_cmd,
97 &list_cmd,
98 &mkpeer_cmd,
99 &msg_cmd,
100 &name_cmd,
101 &read_cmd,
102 &rmhook_cmd,
103 &show_cmd,
104 &shutdown_cmd,
105 &status_cmd,
106 &types_cmd,
107 &write_cmd,
108 &quit_cmd,
109 NULL
110 };
111
112 /* Commands defined in this file */
113 const struct ngcmd read_cmd = {
114 ReadCmd,
115 "read <filename>",
116 "Read and execute commands from a file",
117 NULL,
118 { "source", "." }
119 };
120 const struct ngcmd help_cmd = {
121 HelpCmd,
122 "help [command]",
123 "Show command summary or get more help on a specific command",
124 NULL,
125 { "?" }
126 };
127 const struct ngcmd quit_cmd = {
128 QuitCmd,
129 "quit",
130 "Exit program",
131 NULL,
132 { "exit" }
133 };
134
135 /* Our control and data sockets */
136 int csock, dsock;
137
138 /*
139 * main()
140 */
141 int
main(int ac,char * av[])142 main(int ac, char *av[])
143 {
144 char name[NG_NODESIZ];
145 int interactive = isatty(0) && isatty(1);
146 FILE *fp = NULL;
147 #ifdef JAIL
148 const char *jail_name = NULL;
149 int jid;
150 #endif
151 int ch, rtn = 0;
152
153 /* Set default node name */
154 snprintf(name, sizeof(name), "ngctl%d", getpid());
155
156 /* Parse command line */
157 while ((ch = getopt(ac, av, "df:j:n:")) != -1) {
158 switch (ch) {
159 case 'd':
160 NgSetDebug(NgSetDebug(-1) + 1);
161 break;
162 case 'f':
163 if (strcmp(optarg, "-") == 0)
164 fp = stdin;
165 else if ((fp = fopen(optarg, "r")) == NULL)
166 err(EX_NOINPUT, "%s", optarg);
167 break;
168 case 'j':
169 #ifdef JAIL
170 jail_name = optarg;
171 #else
172 errx(EX_UNAVAILABLE, "not built with jail support");
173 #endif
174 break;
175 case 'n':
176 snprintf(name, sizeof(name), "%s", optarg);
177 break;
178 default:
179 Usage((char *)NULL);
180 break;
181 }
182 }
183 ac -= optind;
184 av += optind;
185
186 #ifdef JAIL
187 if (jail_name != NULL) {
188 if (jail_name[0] == '\0')
189 Usage("invalid jail name");
190
191 jid = jail_getid(jail_name);
192
193 if (jid == -1)
194 errx((errno == EPERM) ? EX_NOPERM : EX_NOHOST,
195 "%s", jail_errmsg);
196 if (jail_attach(jid) != 0)
197 errx((errno == EPERM) ? EX_NOPERM : EX_OSERR,
198 "cannot attach to jail");
199 }
200 #endif
201
202 /* Create a new socket node */
203 if (NgMkSockNode(name, &csock, &dsock) < 0)
204 err(EX_OSERR, "can't create node");
205
206 /* Do commands as requested */
207 if (ac == 0) {
208 if (fp != NULL) {
209 rtn = ReadFile(fp);
210 } else if (interactive) {
211 rtn = DoInteractive();
212 } else
213 Usage("no command specified");
214 } else {
215 rtn = DoCommand(ac, av);
216 }
217
218 /* Convert command return code into system exit code */
219 switch (rtn) {
220 case CMDRTN_OK:
221 case CMDRTN_QUIT:
222 rtn = 0;
223 break;
224 case CMDRTN_USAGE:
225 rtn = EX_USAGE;
226 break;
227 case CMDRTN_ERROR:
228 rtn = EX_OSERR;
229 break;
230 }
231 return (rtn);
232 }
233
234 /*
235 * Process commands from a file
236 */
237 static int
ReadFile(FILE * fp)238 ReadFile(FILE *fp)
239 {
240 char *line = NULL;
241 ssize_t len;
242 size_t sz = 0;
243 unsigned int lineno = 0;
244 int rtn = CMDRTN_OK;
245
246 while ((len = getline(&line, &sz, fp)) > 0) {
247 lineno++;
248 if (*line == '#')
249 continue;
250 if ((rtn = DoParseCommand(line)) != CMDRTN_OK) {
251 warnx("line %d: error in file", lineno);
252 break;
253 }
254 }
255 if (len < 0)
256 rtn = CMDRTN_ERROR;
257 free(line);
258 return (rtn);
259 }
260
261 #ifdef EDITLINE
262 /* Signal handler for Monitor() thread. */
263 static void
Unblock(int signal __unused)264 Unblock(int signal __unused)
265 {
266 unblock = 1;
267 }
268
269 /*
270 * Thread that monitors csock and dsock while main thread
271 * can be blocked in el_gets().
272 */
273 static void *
Monitor(void * v __unused)274 Monitor(void *v __unused)
275 {
276 struct pollfd pfds[2] = {
277 { .fd = csock, .events = POLLIN },
278 { .fd = dsock, .events = POLLIN },
279 };
280 struct sigaction act;
281
282 act.sa_handler = Unblock;
283 sigemptyset(&act.sa_mask);
284 act.sa_flags = 0;
285 sigaction(SIGUSR1, &act, NULL);
286
287 pthread_mutex_lock(&mutex);
288 for (;;) {
289 unblock = 0;
290 if (poll(pfds, 2, INFTIM) <= 0) {
291 if (errno == EINTR) {
292 if (unblock == 1)
293 pthread_cond_wait(&cond, &mutex);
294 continue;
295 }
296 err(EX_OSERR, "poll");
297 }
298 if (pfds[0].revents != 0)
299 ReadCtrlSocket();
300 if (pfds[1].revents != 0)
301 ReadDataSocket();
302 }
303
304 return (NULL);
305 }
306
307 static char *
Prompt(EditLine * el __unused)308 Prompt(EditLine *el __unused)
309 {
310 return (PROMPT);
311 }
312
313 /*
314 * Here we start a thread, that will monitor the netgraph
315 * sockets and catch any unexpected messages or data on them,
316 * that can arrive while user edits his/her commands.
317 *
318 * Whenever we expect data on netgraph sockets, we send signal
319 * to monitoring thread. The signal forces it to exit select()
320 * system call and sleep on condvar until we wake it. While
321 * monitoring thread sleeps, we can do our work with netgraph
322 * sockets.
323 */
324 static int
DoInteractive(void)325 DoInteractive(void)
326 {
327 pthread_t monitor;
328 EditLine *el;
329 History *hist;
330 HistEvent hev = { 0, "" };
331
332 (*help_cmd.func)(0, NULL);
333 pthread_create(&monitor, NULL, Monitor, NULL);
334 el = el_init(getprogname(), stdin, stdout, stderr);
335 if (el == NULL)
336 return (CMDRTN_ERROR);
337 el_set(el, EL_PROMPT, Prompt);
338 el_set(el, EL_SIGNAL, 1);
339 el_set(el, EL_EDITOR, "emacs");
340 hist = history_init();
341 if (hist == NULL)
342 return (CMDRTN_ERROR);
343 history(hist, &hev, H_SETSIZE, 100);
344 history(hist, &hev, H_SETUNIQUE, 1);
345 el_set(el, EL_HIST, history, (const char *)hist);
346 el_source(el, NULL);
347
348 for (;;) {
349 const char *buf;
350 int count;
351
352 if ((buf = el_gets(el, &count)) == NULL) {
353 printf("\n");
354 break;
355 }
356 history(hist, &hev, H_ENTER, buf);
357 pthread_kill(monitor, SIGUSR1);
358 pthread_mutex_lock(&mutex);
359 if (DoParseCommand(buf) == CMDRTN_QUIT) {
360 pthread_mutex_unlock(&mutex);
361 break;
362 }
363 pthread_cond_signal(&cond);
364 pthread_mutex_unlock(&mutex);
365 }
366
367 history_end(hist);
368 el_end(el);
369 pthread_cancel(monitor);
370
371 return (CMDRTN_QUIT);
372 }
373
374 #else /* !EDITLINE */
375
376 /*
377 * Interactive mode w/o libedit functionality.
378 */
379 static int
DoInteractive(void)380 DoInteractive(void)
381 {
382 struct pollfd pfds[3] = {
383 { .fd = csock, .events = POLLIN },
384 { .fd = dsock, .events = POLLIN },
385 { .fd = STDIN_FILENO, .events = POLLIN },
386 };
387 char *line = NULL;
388 ssize_t len;
389 size_t sz = 0;
390
391 (*help_cmd.func)(0, NULL);
392 for (;;) {
393 /* See if any data or control messages are arriving */
394 if (poll(pfds, 2, 0) <= 0) {
395 /* Issue prompt and wait for anything to happen */
396 printf("%s", PROMPT);
397 fflush(stdout);
398 if (poll(pfds, 3, INFTIM) < 0 && errno != EINTR)
399 err(EX_OSERR, "poll");
400 } else {
401 pfds[2].revents = 0;
402 }
403
404 /* If not user input, print a newline first */
405 if (pfds[2].revents == 0)
406 printf("\n");
407
408 if (pfds[0].revents != 0)
409 ReadCtrlSocket();
410 if (pfds[1].revents != 0)
411 ReadDataSocket();
412
413 /* Get any user input */
414 if (pfds[2].revents != 0) {
415 if ((len = getline(&line, &sz, stdin)) <= 0) {
416 printf("\n");
417 break;
418 }
419 if (DoParseCommand(line) == CMDRTN_QUIT)
420 break;
421 }
422 }
423 free(line);
424 return (CMDRTN_QUIT);
425 }
426 #endif /* !EDITLINE */
427
428 /*
429 * Read and process data on netgraph control and data sockets.
430 */
431 static void
ReadCtrlSocket(void)432 ReadCtrlSocket(void)
433 {
434 MsgRead();
435 }
436
437 static void
ReadDataSocket(void)438 ReadDataSocket(void)
439 {
440 char hook[NG_HOOKSIZ];
441 u_char *buf;
442 int rl;
443
444 /* Read packet from socket. */
445 if ((rl = NgAllocRecvData(dsock, &buf, hook)) < 0)
446 err(EX_OSERR, "reading hook \"%s\"", hook);
447 if (rl == 0)
448 errx(EX_OSERR, "EOF from hook \"%s\"?", hook);
449
450 /* Write packet to stdout. */
451 printf("Rec'd data packet on hook \"%s\":\n", hook);
452 DumpAscii(buf, rl);
453 free(buf);
454 }
455
456 /*
457 * Parse a command line and execute the command
458 */
459 static int
DoParseCommand(const char * line)460 DoParseCommand(const char *line)
461 {
462 char *av[MAX_ARGS];
463 int ac;
464
465 /* Parse line */
466 for (ac = 0, av[0] = strtok((char *)line, WHITESPACE);
467 ac < MAX_ARGS - 1 && av[ac];
468 av[++ac] = strtok(NULL, WHITESPACE));
469
470 /* Do command */
471 return (DoCommand(ac, av));
472 }
473
474 /*
475 * Execute the command
476 */
477 static int
DoCommand(int ac,char ** av)478 DoCommand(int ac, char **av)
479 {
480 const struct ngcmd *cmd;
481 int rtn;
482
483 if (ac == 0 || *av[0] == 0)
484 return (CMDRTN_OK);
485 if ((cmd = FindCommand(av[0])) == NULL)
486 return (CMDRTN_ERROR);
487 if ((rtn = (*cmd->func)(ac, av)) == CMDRTN_USAGE)
488 warnx("usage: %s", cmd->cmd);
489 return (rtn);
490 }
491
492 /*
493 * Find a command
494 */
495 static const struct ngcmd *
FindCommand(const char * string)496 FindCommand(const char *string)
497 {
498 int k, found = -1;
499
500 for (k = 0; cmds[k] != NULL; k++) {
501 if (MatchCommand(cmds[k], string)) {
502 if (found != -1) {
503 warnx("\"%s\": ambiguous command", string);
504 return (NULL);
505 }
506 found = k;
507 }
508 }
509 if (found == -1) {
510 warnx("\"%s\": unknown command", string);
511 return (NULL);
512 }
513 return (cmds[found]);
514 }
515
516 /*
517 * See if string matches a prefix of "cmd" (or an alias) case insensitively
518 */
519 static int
MatchCommand(const struct ngcmd * cmd,const char * s)520 MatchCommand(const struct ngcmd *cmd, const char *s)
521 {
522 int a;
523
524 /* Try to match command, ignoring the usage stuff */
525 if (strlen(s) <= strcspn(cmd->cmd, WHITESPACE)) {
526 if (strncasecmp(s, cmd->cmd, strlen(s)) == 0)
527 return (1);
528 }
529
530 /* Try to match aliases */
531 for (a = 0; a < MAX_CMD_ALIAS && cmd->aliases[a] != NULL; a++) {
532 if (strlen(cmd->aliases[a]) >= strlen(s)) {
533 if (strncasecmp(s, cmd->aliases[a], strlen(s)) == 0)
534 return (1);
535 }
536 }
537
538 /* No match */
539 return (0);
540 }
541
542 /*
543 * ReadCmd()
544 */
545 static int
ReadCmd(int ac,char ** av)546 ReadCmd(int ac, char **av)
547 {
548 FILE *fp;
549 int rtn;
550
551 /* Open file */
552 switch (ac) {
553 case 2:
554 if ((fp = fopen(av[1], "r")) == NULL) {
555 warn("%s", av[1]);
556 return (CMDRTN_ERROR);
557 }
558 break;
559 default:
560 return (CMDRTN_USAGE);
561 }
562
563 /* Process it */
564 rtn = ReadFile(fp);
565 if (ferror(fp))
566 warn("%s", av[1]);
567 fclose(fp);
568 return (rtn);
569 }
570
571 /*
572 * HelpCmd()
573 */
574 static int
HelpCmd(int ac,char ** av)575 HelpCmd(int ac, char **av)
576 {
577 const struct ngcmd *cmd;
578 const char *s;
579 const int maxcol = 63;
580 int a, k, len;
581
582 switch (ac) {
583 case 0:
584 case 1:
585 /* Show all commands */
586 printf("Available commands:\n");
587 for (k = 0; cmds[k] != NULL; k++) {
588 cmd = cmds[k];
589 for (s = cmd->cmd; *s != '\0' && !isspace(*s); s++)
590 /* nothing */;
591 printf(" %.*s%*s %s\n", (int)(s - cmd->cmd), cmd->cmd,
592 (int)(10 - (s - cmd->cmd)), "", cmd->desc);
593 }
594 return (CMDRTN_OK);
595 default:
596 /* Show help on a specific command */
597 if ((cmd = FindCommand(av[1])) != NULL) {
598 printf("usage: %s\n", cmd->cmd);
599 if (cmd->aliases[0] != NULL) {
600 printf("Aliases: ");
601 for (a = 0; a < MAX_CMD_ALIAS &&
602 cmd->aliases[a] != NULL; a++) {
603 if (a > 0)
604 printf(", ");
605 printf("%s", cmd->aliases[a]);
606 }
607 printf("\n");
608 }
609 printf("Summary: %s\n", cmd->desc);
610 if (cmd->help == NULL)
611 break;
612 printf("Description:\n");
613 for (s = cmd->help; *s != '\0'; s += len) {
614 while (isspace(*s))
615 s++;
616 /* advance to the column limit */
617 for (len = 0; s[len] && len < maxcol; len++)
618 /* nothing */;
619 /* back up to previous interword space */
620 while (len > 0 && s[len] && !isblank(s[len]))
621 len--;
622 printf(" %.*s\n", len, s);
623 }
624 }
625 }
626 return (CMDRTN_OK);
627 }
628
629 /*
630 * QuitCmd()
631 */
632 static int
QuitCmd(int ac __unused,char ** av __unused)633 QuitCmd(int ac __unused, char **av __unused)
634 {
635 return (CMDRTN_QUIT);
636 }
637
638 /*
639 * Dump data in hex and ASCII form
640 */
641 void
DumpAscii(const u_char * buf,int len)642 DumpAscii(const u_char *buf, int len)
643 {
644 int k, count;
645
646 for (count = 0; count < len; count += DUMP_BYTES_PER_LINE) {
647 printf("%04x: ", count);
648 for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
649 if (count + k < len) {
650 printf("%02x ", buf[count + k]);
651 } else {
652 printf(" ");
653 }
654 }
655 printf(" ");
656 for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
657 if (count + k < len) {
658 printf("%c", isprint(buf[count + k]) ?
659 buf[count + k] : '.');
660 } else {
661 printf(" ");
662 }
663 }
664 printf("\n");
665 }
666 }
667
668 /*
669 * Usage()
670 */
671 static void
Usage(const char * msg)672 Usage(const char *msg)
673 {
674 if (msg)
675 warnx("%s", msg);
676 fprintf(stderr,
677 "usage: ngctl [-j jail] [-d] [-f filename] [-n nodename] "
678 "[command [argument ...]]\n");
679 exit(EX_USAGE);
680 }
681