main.c revision 158882
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 * $FreeBSD: head/usr.sbin/ngctl/main.c 158882 2006-05-24 14:46:55Z glebius $
38 * $Whistle: main.c,v 1.12 1999/11/29 19:17:46 archie Exp $
39 */
40
41#include <sys/param.h>
42#include <sys/socket.h>
43#include <sys/select.h>
44
45#include <ctype.h>
46#include <err.h>
47#include <errno.h>
48#include <limits.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <sysexits.h>
53#include <unistd.h>
54
55#include <netgraph.h>
56
57#include "ngctl.h"
58
59#define PROMPT			"+ "
60#define MAX_ARGS		512
61#define WHITESPACE		" \t\r\n\v\f"
62#define DUMP_BYTES_PER_LINE	16
63
64/* Internal functions */
65static int	ReadFile(FILE *fp);
66static int	DoParseCommand(char *line);
67static int	DoCommand(int ac, char **av);
68static int	DoInteractive(void);
69static const	struct ngcmd *FindCommand(const char *string);
70static int	MatchCommand(const struct ngcmd *cmd, const char *s);
71static void	Usage(const char *msg);
72static int	ReadCmd(int ac, char **av);
73static int	HelpCmd(int ac, char **av);
74static int	QuitCmd(int ac, char **av);
75
76/* List of commands */
77static const struct ngcmd *const cmds[] = {
78	&config_cmd,
79	&connect_cmd,
80	&debug_cmd,
81	&dot_cmd,
82	&help_cmd,
83	&list_cmd,
84	&mkpeer_cmd,
85	&msg_cmd,
86	&name_cmd,
87	&read_cmd,
88	&rmhook_cmd,
89	&show_cmd,
90	&shutdown_cmd,
91	&status_cmd,
92	&types_cmd,
93	&write_cmd,
94	&quit_cmd,
95	NULL
96};
97
98/* Commands defined in this file */
99const struct ngcmd read_cmd = {
100	ReadCmd,
101	"read <filename>",
102	"Read and execute commands from a file",
103	NULL,
104	{ "source", "." }
105};
106const struct ngcmd help_cmd = {
107	HelpCmd,
108	"help [command]",
109	"Show command summary or get more help on a specific command",
110	NULL,
111	{ "?" }
112};
113const struct ngcmd quit_cmd = {
114	QuitCmd,
115	"quit",
116	"Exit program",
117	NULL,
118	{ "exit" }
119};
120
121/* Our control and data sockets */
122int	csock, dsock;
123
124/*
125 * main()
126 */
127int
128main(int ac, char *av[])
129{
130	char	name[NG_NODESIZ];
131	int	interactive = isatty(0) && isatty(1);
132	FILE	*fp = NULL;
133	int	ch, rtn = 0;
134
135	/* Set default node name */
136	snprintf(name, sizeof(name), "ngctl%d", getpid());
137
138	/* Parse command line */
139	while ((ch = getopt(ac, av, "df:n:")) != EOF) {
140		switch (ch) {
141		case 'd':
142			NgSetDebug(NgSetDebug(-1) + 1);
143			break;
144		case 'f':
145			if (strcmp(optarg, "-") == 0)
146				fp = stdin;
147			else if ((fp = fopen(optarg, "r")) == NULL)
148				err(EX_NOINPUT, "%s", optarg);
149			break;
150		case 'n':
151			snprintf(name, sizeof(name), "%s", optarg);
152			break;
153		case '?':
154		default:
155			Usage((char *)NULL);
156			break;
157		}
158	}
159	ac -= optind;
160	av += optind;
161
162	/* Create a new socket node */
163	if (NgMkSockNode(name, &csock, &dsock) < 0)
164		err(EX_OSERR, "can't create node");
165
166	/* Do commands as requested */
167	if (ac == 0) {
168		if (fp != NULL) {
169			rtn = ReadFile(fp);
170		} else if (interactive) {
171			rtn = DoInteractive();
172		} else
173			Usage("no command specified");
174	} else {
175		rtn = DoCommand(ac, av);
176	}
177
178	/* Convert command return code into system exit code */
179	switch (rtn) {
180	case CMDRTN_OK:
181	case CMDRTN_QUIT:
182		rtn = 0;
183		break;
184	case CMDRTN_USAGE:
185		rtn = EX_USAGE;
186		break;
187	case CMDRTN_ERROR:
188		rtn = EX_OSERR;
189		break;
190	}
191	return(rtn);
192}
193
194/*
195 * Process commands from a file
196 */
197static int
198ReadFile(FILE *fp)
199{
200	char line[LINE_MAX];
201	int num, rtn;
202
203	for (num = 1; fgets(line, sizeof(line), fp) != NULL; num++) {
204		if (*line == '#')
205			continue;
206		if ((rtn = DoParseCommand(line)) != 0) {
207			warnx("line %d: error in file", num);
208			return(rtn);
209		}
210	}
211	return(CMDRTN_OK);
212}
213
214/*
215 * Interactive mode
216 */
217static int
218DoInteractive(void)
219{
220	const int maxfd = MAX(csock, dsock) + 1;
221
222	(*help_cmd.func)(0, NULL);
223	while (1) {
224		struct timeval tv;
225		fd_set rfds;
226
227		/* See if any data or control messages are arriving */
228		FD_ZERO(&rfds);
229		FD_SET(csock, &rfds);
230		FD_SET(dsock, &rfds);
231		memset(&tv, 0, sizeof(tv));
232		if (select(maxfd, &rfds, NULL, NULL, &tv) <= 0) {
233
234			/* Issue prompt and wait for anything to happen */
235			printf("%s", PROMPT);
236			fflush(stdout);
237			FD_ZERO(&rfds);
238			FD_SET(0, &rfds);
239			FD_SET(csock, &rfds);
240			FD_SET(dsock, &rfds);
241			if (select(maxfd, &rfds, NULL, NULL, NULL) < 0)
242				err(EX_OSERR, "select");
243
244			/* If not user input, print a newline first */
245			if (!FD_ISSET(0, &rfds))
246				printf("\n");
247		}
248
249		/* Display any incoming control message */
250		if (FD_ISSET(csock, &rfds))
251			MsgRead();
252
253		/* Display any incoming data packet */
254		if (FD_ISSET(dsock, &rfds)) {
255			u_char *buf;
256			char hook[NG_HOOKSIZ];
257			int rl;
258
259			/* Read packet from socket */
260			if ((rl = NgAllocRecvData(dsock, &buf, hook)) < 0)
261				err(EX_OSERR, "reading hook \"%s\"", hook);
262			if (rl == 0)
263				errx(EX_OSERR, "EOF from hook \"%s\"?", hook);
264
265			/* Write packet to stdout */
266			printf("Rec'd data packet on hook \"%s\":\n", hook);
267			DumpAscii(buf, rl);
268			free(buf);
269		}
270
271		/* Get any user input */
272		if (FD_ISSET(0, &rfds)) {
273			char buf[LINE_MAX];
274
275			if (fgets(buf, sizeof(buf), stdin) == NULL) {
276				printf("\n");
277				break;
278			}
279			if (DoParseCommand(buf) == CMDRTN_QUIT)
280				break;
281		}
282	}
283	return(CMDRTN_QUIT);
284}
285
286/*
287 * Parse a command line and execute the command
288 */
289static int
290DoParseCommand(char *line)
291{
292	char *av[MAX_ARGS];
293	int ac;
294
295	/* Parse line */
296	for (ac = 0, av[0] = strtok(line, WHITESPACE);
297	    ac < MAX_ARGS - 1 && av[ac];
298	    av[++ac] = strtok(NULL, WHITESPACE));
299
300	/* Do command */
301	return(DoCommand(ac, av));
302}
303
304/*
305 * Execute the command
306 */
307static int
308DoCommand(int ac, char **av)
309{
310	const struct ngcmd *cmd;
311	int rtn;
312
313	if (ac == 0 || *av[0] == 0)
314		return(CMDRTN_OK);
315	if ((cmd = FindCommand(av[0])) == NULL)
316		return(CMDRTN_ERROR);
317	if ((rtn = (*cmd->func)(ac, av)) == CMDRTN_USAGE)
318		warnx("usage: %s", cmd->cmd);
319	return(rtn);
320}
321
322/*
323 * Find a command
324 */
325static const struct ngcmd *
326FindCommand(const char *string)
327{
328	int k, found = -1;
329
330	for (k = 0; cmds[k] != NULL; k++) {
331		if (MatchCommand(cmds[k], string)) {
332			if (found != -1) {
333				warnx("\"%s\": ambiguous command", string);
334				return(NULL);
335			}
336			found = k;
337		}
338	}
339	if (found == -1) {
340		warnx("\"%s\": unknown command", string);
341		return(NULL);
342	}
343	return(cmds[found]);
344}
345
346/*
347 * See if string matches a prefix of "cmd" (or an alias) case insensitively
348 */
349static int
350MatchCommand(const struct ngcmd *cmd, const char *s)
351{
352	int a;
353
354	/* Try to match command, ignoring the usage stuff */
355	if (strlen(s) <= strcspn(cmd->cmd, WHITESPACE)) {
356		if (strncasecmp(s, cmd->cmd, strlen(s)) == 0)
357			return (1);
358	}
359
360	/* Try to match aliases */
361	for (a = 0; a < MAX_CMD_ALIAS && cmd->aliases[a] != NULL; a++) {
362		if (strlen(cmd->aliases[a]) >= strlen(s)) {
363			if (strncasecmp(s, cmd->aliases[a], strlen(s)) == 0)
364				return (1);
365		}
366	}
367
368	/* No match */
369	return (0);
370}
371
372/*
373 * ReadCmd()
374 */
375static int
376ReadCmd(int ac, char **av)
377{
378	FILE *fp;
379	int rtn;
380
381	/* Open file */
382	switch (ac) {
383	case 2:
384		if ((fp = fopen(av[1], "r")) == NULL) {
385			warn("%s", av[1]);
386			return(CMDRTN_ERROR);
387		}
388		break;
389	default:
390		return(CMDRTN_USAGE);
391	}
392
393	/* Process it */
394	rtn = ReadFile(fp);
395	fclose(fp);
396	return(rtn);
397}
398
399/*
400 * HelpCmd()
401 */
402static int
403HelpCmd(int ac, char **av)
404{
405	const struct ngcmd *cmd;
406	int k;
407
408	switch (ac) {
409	case 0:
410	case 1:
411		/* Show all commands */
412		printf("Available commands:\n");
413		for (k = 0; cmds[k] != NULL; k++) {
414			char *s, buf[100];
415
416			cmd = cmds[k];
417			snprintf(buf, sizeof(buf), "%s", cmd->cmd);
418			for (s = buf; *s != '\0' && !isspace(*s); s++);
419			*s = '\0';
420			printf("  %-10s %s\n", buf, cmd->desc);
421		}
422		return(CMDRTN_OK);
423	default:
424		/* Show help on a specific command */
425		if ((cmd = FindCommand(av[1])) != NULL) {
426			printf("usage:    %s\n", cmd->cmd);
427			if (cmd->aliases[0] != NULL) {
428				int a = 0;
429
430				printf("Aliases:  ");
431				while (1) {
432					printf("%s", cmd->aliases[a++]);
433					if (a == MAX_CMD_ALIAS
434					    || cmd->aliases[a] == NULL) {
435						printf("\n");
436						break;
437					}
438					printf(", ");
439				}
440			}
441			printf("Summary:  %s\n", cmd->desc);
442			if (cmd->help != NULL) {
443				const char *s;
444				char buf[65];
445				int tot, len, done;
446
447				printf("Description:\n");
448				for (s = cmd->help; *s != '\0'; s += len) {
449					while (isspace(*s))
450						s++;
451					tot = snprintf(buf,
452					    sizeof(buf), "%s", s);
453					len = strlen(buf);
454					done = len == tot;
455					if (!done) {
456						while (len > 0
457						    && !isspace(buf[len-1]))
458							buf[--len] = '\0';
459					}
460					printf("  %s\n", buf);
461				}
462			}
463		}
464	}
465	return(CMDRTN_OK);
466}
467
468/*
469 * QuitCmd()
470 */
471static int
472QuitCmd(int ac __unused, char **av __unused)
473{
474	return(CMDRTN_QUIT);
475}
476
477/*
478 * Dump data in hex and ASCII form
479 */
480void
481DumpAscii(const u_char *buf, int len)
482{
483	char ch, sbuf[100];
484	int k, count;
485
486	for (count = 0; count < len; count += DUMP_BYTES_PER_LINE) {
487		snprintf(sbuf, sizeof(sbuf), "%04x:  ", count);
488		for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
489			if (count + k < len) {
490				snprintf(sbuf + strlen(sbuf),
491				    sizeof(sbuf) - strlen(sbuf),
492				    "%02x ", buf[count + k]);
493			} else {
494				snprintf(sbuf + strlen(sbuf),
495				    sizeof(sbuf) - strlen(sbuf), "   ");
496			}
497		}
498		snprintf(sbuf + strlen(sbuf), sizeof(sbuf) - strlen(sbuf), " ");
499		for (k = 0; k < DUMP_BYTES_PER_LINE; k++) {
500			if (count + k < len) {
501				ch = isprint(buf[count + k]) ?
502				    buf[count + k] : '.';
503				snprintf(sbuf + strlen(sbuf),
504				    sizeof(sbuf) - strlen(sbuf), "%c", ch);
505			} else {
506				snprintf(sbuf + strlen(sbuf),
507				    sizeof(sbuf) - strlen(sbuf), " ");
508			}
509		}
510		printf("%s\n", sbuf);
511	}
512}
513
514/*
515 * Usage()
516 */
517static void
518Usage(const char *msg)
519{
520	if (msg)
521		warnx("%s", msg);
522	fprintf(stderr,
523		"usage: ngctl [-d] [-f file] [-n name] [command ...]\n");
524	exit(EX_USAGE);
525}
526