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