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