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 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 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 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 * 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 * 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 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 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 432 ReadCtrlSocket(void) 433 { 434 MsgRead(); 435 } 436 437 static 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 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 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 * 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 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 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 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 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 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 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