1 /* $NetBSD: hunt.c,v 1.58 2014/03/30 09:11:50 skrll Exp $ */ 2 /* 3 * Copyright (c) 1983-2003, Regents of the University of California. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are 8 * met: 9 * 10 * + Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * + Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * + Neither the name of the University of California, San Francisco nor 16 * the names of its contributors may be used to endorse or promote 17 * products derived from this software without specific prior written 18 * permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 23 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include <sys/cdefs.h> 34 #ifndef lint 35 __RCSID("$NetBSD: hunt.c,v 1.58 2014/03/30 09:11:50 skrll Exp $"); 36 #endif /* not lint */ 37 38 #include <sys/param.h> 39 #include <sys/stat.h> 40 #include <sys/time.h> 41 #include <sys/poll.h> 42 #include <ctype.h> 43 #include <err.h> 44 #include <errno.h> 45 #include <curses.h> 46 #include <signal.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <unistd.h> 50 #include <assert.h> 51 52 #include "hunt_common.h" 53 #include "pathnames.h" 54 #include "hunt_private.h" 55 56 57 #ifdef OVERRIDE_PATH_HUNTD 58 static const char Driver[] = OVERRIDE_PATH_HUNTD; 59 #else 60 static const char Driver[] = PATH_HUNTD; 61 #endif 62 63 #ifdef INTERNET 64 static const char *contactportstr; 65 static uint16_t contactport = TEST_PORT; 66 static const char *contacthost; 67 #else 68 static const char huntsockpath[] = PATH_HUNTSOCKET; 69 #endif 70 71 72 bool Last_player = false; 73 #ifdef MONITOR 74 bool Am_monitor = false; 75 #endif 76 77 char Buf[BUFSIZ]; 78 79 /*static*/ int huntsocket; 80 #ifdef INTERNET 81 char *Send_message = NULL; 82 #endif 83 84 SOCKET Daemon; 85 #ifdef INTERNET 86 #define DAEMON_SIZE (sizeof Daemon) 87 #else 88 #define DAEMON_SIZE (sizeof Daemon - 1) 89 #endif 90 91 char map_key[256]; /* what to map keys to */ 92 bool no_beep; 93 94 static char name[WIRE_NAMELEN]; 95 static char team = ' '; 96 97 static int in_visual; 98 99 extern int cur_row, cur_col; 100 101 static void dump_scores(const struct sockaddr_storage *, socklen_t); 102 static int env_init(int); 103 static void fill_in_blanks(void); 104 static void fincurs(void); 105 static void rmnl(char *); 106 static void sigterm(int) __dead; 107 static void sigusr1(int) __dead; 108 static void find_driver(void); 109 static void start_driver(void); 110 111 extern int Otto_mode; 112 113 static const char * 114 lookuphost(const struct sockaddr_storage *host, socklen_t hostlen) 115 { 116 static char buf[NI_MAXHOST]; 117 int result; 118 119 result = getnameinfo((const struct sockaddr *)host, hostlen, 120 buf, sizeof(buf), NULL, 0, NI_NOFQDN); 121 if (result) { 122 leavex(1, "getnameinfo: %s", gai_strerror(result)); 123 } 124 return buf; 125 } 126 127 /* 128 * main: 129 * Main program for local process 130 */ 131 int 132 main(int ac, char **av) 133 { 134 char *term; 135 int c; 136 int enter_status; 137 bool Query_driver = false; 138 bool Show_scores = false; 139 140 enter_status = env_init(Q_CLOAK); 141 while ((c = getopt(ac, av, "Sbcfh:l:mn:op:qst:w:")) != -1) { 142 switch (c) { 143 case 'l': /* rsh compatibility */ 144 case 'n': 145 (void) strncpy(name, optarg, sizeof(name)); 146 break; 147 case 't': 148 team = *optarg; 149 if (!isdigit((unsigned char)team)) { 150 warnx("Team names must be numeric"); 151 team = ' '; 152 } 153 break; 154 case 'o': 155 #ifndef OTTO 156 warnx("The -o flag is reserved for future use."); 157 goto usage; 158 #else 159 Otto_mode = true; 160 break; 161 #endif 162 case 'm': 163 #ifdef MONITOR 164 Am_monitor = true; 165 #else 166 warnx("The monitor was not compiled in."); 167 #endif 168 break; 169 #ifdef INTERNET 170 case 'S': 171 Show_scores = true; 172 break; 173 case 'q': /* query whether hunt is running */ 174 Query_driver = true; 175 break; 176 case 'w': 177 Send_message = optarg; 178 break; 179 case 'h': 180 contacthost = optarg; 181 break; 182 case 'p': 183 contactportstr = optarg; 184 contactport = atoi(contactportstr); 185 break; 186 #else 187 case 'S': 188 case 'q': 189 case 'w': 190 case 'h': 191 case 'p': 192 wanrx("Need TCP/IP for S, q, w, h, and p options."); 193 break; 194 #endif 195 case 'c': 196 enter_status = Q_CLOAK; 197 break; 198 case 'f': 199 #ifdef FLY 200 enter_status = Q_FLY; 201 #else 202 warnx("The flying code was not compiled in."); 203 #endif 204 break; 205 case 's': 206 enter_status = Q_SCAN; 207 break; 208 case 'b': 209 no_beep = !no_beep; 210 break; 211 default: 212 usage: 213 fputs( 214 "usage:\thunt [-qmcsfS] [-n name] [-t team] [-p port] [-w message] [host]\n", 215 stderr); 216 exit(1); 217 } 218 } 219 #ifdef INTERNET 220 if (optind + 1 < ac) 221 goto usage; 222 else if (optind + 1 == ac) 223 contacthost = av[ac - 1]; 224 #else 225 if (optind < ac) 226 goto usage; 227 #endif 228 229 #ifdef INTERNET 230 serverlist_setup(contacthost, contactport); 231 232 if (Show_scores) { 233 const struct sockaddr_storage *host; 234 socklen_t hostlen; 235 u_short msg = C_SCORES; 236 unsigned i; 237 238 serverlist_query(msg); 239 for (i = 0; i < serverlist_num(); i++) { 240 host = serverlist_gethost(i, &hostlen); 241 dump_scores(host, hostlen); 242 } 243 exit(0); 244 } 245 if (Query_driver) { 246 const struct sockaddr_storage *host; 247 socklen_t hostlen; 248 u_short msg = C_MESSAGE; 249 u_short num_players; 250 unsigned i; 251 252 serverlist_query(msg); 253 for (i = 0; i < serverlist_num(); i++) { 254 host = serverlist_gethost(i, &hostlen); 255 num_players = ntohs(serverlist_getresponse(i)); 256 257 printf("%d player%s hunting on %s!\n", 258 num_players, (num_players == 1) ? "" : "s", 259 lookuphost(host, hostlen)); 260 } 261 exit(0); 262 } 263 #endif 264 #ifdef OTTO 265 if (Otto_mode) 266 (void) strncpy(name, "otto", sizeof(name)); 267 else 268 #endif 269 fill_in_blanks(); 270 271 (void) fflush(stdout); 272 if (!isatty(0) || (term = getenv("TERM")) == NULL) 273 errx(1, "no terminal type"); 274 if (!initscr()) 275 errx(0, "couldn't initialize screen"); 276 (void) noecho(); 277 (void) cbreak(); 278 in_visual = true; 279 if (LINES < SCREEN_HEIGHT || COLS < SCREEN_WIDTH) 280 leavex(1, "Need a larger window"); 281 clear_the_screen(); 282 (void) signal(SIGINT, intr); 283 (void) signal(SIGTERM, sigterm); 284 (void) signal(SIGUSR1, sigusr1); 285 (void) signal(SIGPIPE, SIG_IGN); 286 287 for (;;) { 288 #ifdef INTERNET 289 find_driver(); 290 291 if (Daemon.sin_port == 0) 292 leavex(1, "Game not found, try again"); 293 294 jump_in: 295 do { 296 int option; 297 298 huntsocket = socket(SOCK_FAMILY, SOCK_STREAM, 0); 299 if (huntsocket < 0) 300 err(1, "socket"); 301 option = 1; 302 if (setsockopt(huntsocket, SOL_SOCKET, SO_USELOOPBACK, 303 &option, sizeof option) < 0) 304 warn("setsockopt loopback"); 305 errno = 0; 306 if (connect(huntsocket, (struct sockaddr *) &Daemon, 307 DAEMON_SIZE) < 0) { 308 if (errno != ECONNREFUSED) { 309 leave(1, "connect"); 310 } 311 } 312 else 313 break; 314 sleep(1); 315 } while (close(huntsocket) == 0); 316 #else /* !INTERNET */ 317 /* 318 * set up a socket 319 */ 320 321 if ((huntsocket = socket(SOCK_FAMILY, SOCK_STREAM, 0)) < 0) 322 err(1, "socket"); 323 324 /* 325 * attempt to connect the socket to a name; if it fails that 326 * usually means that the driver isn't running, so we start 327 * up the driver. 328 */ 329 330 Daemon.sun_family = SOCK_FAMILY; 331 (void) strcpy(Daemon.sun_path, huntsockpath); 332 if (connect(huntsocket, &Daemon, DAEMON_SIZE) < 0) { 333 if (errno != ENOENT) { 334 leavex(1, "connect2"); 335 } 336 start_driver(); 337 338 do { 339 (void) close(huntsocket); 340 if ((huntsocket = socket(SOCK_FAMILY, SOCK_STREAM, 341 0)) < 0) 342 err(1, "socket"); 343 sleep(2); 344 } while (connect(huntsocket, &Daemon, DAEMON_SIZE) < 0); 345 } 346 #endif 347 348 do_connect(name, sizeof(name), team, enter_status); 349 #ifdef INTERNET 350 if (Send_message != NULL) { 351 do_message(); 352 if (enter_status == Q_MESSAGE) 353 break; 354 Send_message = NULL; 355 /* don't continue as that will call find_driver */ 356 goto jump_in; 357 } 358 #endif 359 playit(); 360 if ((enter_status = quit(enter_status)) == Q_QUIT) 361 break; 362 } 363 leavex(0, NULL); 364 /* NOTREACHED */ 365 return(0); 366 } 367 368 #ifdef INTERNET 369 static void 370 find_driver(void) 371 { 372 u_short msg; 373 const struct sockaddr_storage *host; 374 socklen_t hostlen; 375 unsigned num; 376 int i, c; 377 378 msg = C_PLAYER; 379 #ifdef MONITOR 380 if (Am_monitor) { 381 msg = C_MONITOR; 382 } 383 #endif 384 385 serverlist_query(msg); 386 num = serverlist_num(); 387 if (num == 0) { 388 start_driver(); 389 sleep(2); 390 /* try again */ 391 serverlist_query(msg); 392 num = serverlist_num(); 393 if (num == 0) { 394 /* give up */ 395 return; 396 } 397 } 398 399 if (num == 1) { 400 host = serverlist_gethost(0, &hostlen); 401 } else { 402 clear_the_screen(); 403 move(1, 0); 404 addstr("Pick one:"); 405 for (i = 0; i < HEIGHT - 4 && i < (int)num; i++) { 406 move(3 + i, 0); 407 host = serverlist_gethost(i, &hostlen); 408 printw("%8c %.64s", 'a' + i, 409 lookuphost(host, hostlen)); 410 } 411 move(4 + i, 0); 412 addstr("Enter letter: "); 413 refresh(); 414 while (1) { 415 c = getchar(); 416 if (c == EOF) { 417 leavex(1, "EOF on stdin"); 418 } 419 if (islower((unsigned char)c) && c - 'a' < i) { 420 break; 421 } 422 beep(); 423 refresh(); 424 } 425 clear_the_screen(); 426 host = serverlist_gethost(c - 'a', &hostlen); 427 } 428 429 /* XXX fix this (won't work in ipv6) */ 430 assert(hostlen == sizeof(Daemon)); 431 memcpy(&Daemon, host, sizeof(Daemon)); 432 } 433 434 static void 435 dump_scores(const struct sockaddr_storage *host, socklen_t hostlen) 436 { 437 int s; 438 char buf[BUFSIZ]; 439 ssize_t cnt; 440 441 printf("\n%s:\n", lookuphost(host, hostlen)); 442 fflush(stdout); 443 444 s = socket(host->ss_family, SOCK_STREAM, 0); 445 if (s < 0) 446 err(1, "socket"); 447 if (connect(s, (const struct sockaddr *)host, hostlen) < 0) 448 err(1, "connect"); 449 while ((cnt = read(s, buf, BUFSIZ)) > 0) 450 write(fileno(stdout), buf, cnt); 451 (void) close(s); 452 } 453 454 #endif 455 456 static void 457 start_driver(void) 458 { 459 int procid; 460 461 #ifdef MONITOR 462 if (Am_monitor) { 463 leavex(1, "No one playing."); 464 /* NOTREACHED */ 465 } 466 #endif 467 468 #ifdef INTERNET 469 if (contacthost != NULL) { 470 sleep(3); 471 return; 472 } 473 #endif 474 475 move(HEIGHT, 0); 476 addstr("Starting..."); 477 refresh(); 478 procid = fork(); 479 if (procid == -1) { 480 leave(1, "fork failed."); 481 } 482 if (procid == 0) { 483 (void) signal(SIGINT, SIG_IGN); 484 #ifndef INTERNET 485 (void) close(huntsocket); 486 #else 487 if (contactportstr == NULL) 488 #endif 489 execl(Driver, "HUNT", (char *) NULL); 490 #ifdef INTERNET 491 else 492 execl(Driver, "HUNT", "-p", contactportstr, 493 (char *) NULL); 494 #endif 495 /* only get here if exec failed */ 496 (void) kill(getppid(), SIGUSR1); /* tell mom */ 497 _exit(1); 498 } 499 move(HEIGHT, 0); 500 addstr("Connecting..."); 501 refresh(); 502 } 503 504 /* 505 * bad_con: 506 * We had a bad connection. For the moment we assume that this 507 * means the game is full. 508 */ 509 void 510 bad_con(void) 511 { 512 leavex(1, "The game is full. Sorry."); 513 /* NOTREACHED */ 514 } 515 516 /* 517 * bad_ver: 518 * version number mismatch. 519 */ 520 void 521 bad_ver(void) 522 { 523 leavex(1, "Version number mismatch. No go."); 524 /* NOTREACHED */ 525 } 526 527 /* 528 * sigterm: 529 * Handle a terminate signal 530 */ 531 static void 532 sigterm(int dummy __unused) 533 { 534 leavex(0, NULL); 535 /* NOTREACHED */ 536 } 537 538 539 /* 540 * sigusr1: 541 * Handle a usr1 signal 542 */ 543 static void 544 sigusr1(int dummy __unused) 545 { 546 leavex(1, "Unable to start driver. Try again."); 547 /* NOTREACHED */ 548 } 549 550 /* 551 * rmnl: 552 * Remove a '\n' at the end of a string if there is one 553 */ 554 static void 555 rmnl(char *s) 556 { 557 char *cp; 558 559 cp = strrchr(s, '\n'); 560 if (cp != NULL) 561 *cp = '\0'; 562 } 563 564 /* 565 * intr: 566 * Handle a interrupt signal 567 */ 568 void 569 intr(int dummy __unused) 570 { 571 int ch; 572 bool explained; 573 int y, x; 574 575 (void) signal(SIGINT, SIG_IGN); 576 getyx(stdscr, y, x); 577 move(HEIGHT, 0); 578 addstr("Really quit? "); 579 clrtoeol(); 580 refresh(); 581 explained = false; 582 for (;;) { 583 ch = getchar(); 584 if (isupper(ch)) 585 ch = tolower(ch); 586 if (ch == 'y') { 587 if (huntsocket != 0) { 588 (void) write(huntsocket, "q", 1); 589 (void) close(huntsocket); 590 } 591 leavex(0, NULL); 592 } 593 else if (ch == 'n') { 594 (void) signal(SIGINT, intr); 595 move(y, x); 596 refresh(); 597 return; 598 } 599 if (!explained) { 600 addstr("(Yes or No) "); 601 refresh(); 602 explained = true; 603 } 604 beep(); 605 refresh(); 606 } 607 } 608 609 static void 610 fincurs(void) 611 { 612 if (in_visual) { 613 move(HEIGHT, 0); 614 refresh(); 615 endwin(); 616 } 617 } 618 619 /* 620 * leave: 621 * Leave the game somewhat gracefully, restoring all current 622 * tty stats, and print errno. 623 */ 624 void 625 leave(int exitval, const char *fmt, ...) 626 { 627 int serrno = errno; 628 va_list ap; 629 630 fincurs(); 631 va_start(ap, fmt); 632 errno = serrno; 633 verr(exitval, fmt, ap); 634 va_end(ap); 635 } 636 637 /* 638 * leavex: 639 * Leave the game somewhat gracefully, restoring all current 640 * tty stats. 641 */ 642 void 643 leavex(int exitval, const char *fmt, ...) 644 { 645 va_list ap; 646 647 fincurs(); 648 va_start(ap, fmt); 649 verrx(exitval, fmt, ap); 650 va_end(ap); 651 } 652 653 static int 654 env_init(int enter_status) 655 { 656 int i; 657 char *envp, *envname, *s; 658 659 for (i = 0; i < 256; i++) 660 map_key[i] = (char) i; 661 662 envname = NULL; 663 if ((envp = getenv("HUNT")) != NULL) { 664 while ((s = strpbrk(envp, "=,")) != NULL) { 665 if (strncmp(envp, "cloak,", s - envp + 1) == 0) { 666 enter_status = Q_CLOAK; 667 envp = s + 1; 668 } 669 else if (strncmp(envp, "scan,", s - envp + 1) == 0) { 670 enter_status = Q_SCAN; 671 envp = s + 1; 672 } 673 else if (strncmp(envp, "fly,", s - envp + 1) == 0) { 674 enter_status = Q_FLY; 675 envp = s + 1; 676 } 677 else if (strncmp(envp, "nobeep,", s - envp + 1) == 0) { 678 no_beep = true; 679 envp = s + 1; 680 } 681 else if (strncmp(envp, "name=", s - envp + 1) == 0) { 682 envname = s + 1; 683 if ((s = strchr(envp, ',')) == NULL) { 684 *envp = '\0'; 685 strncpy(name, envname, sizeof(name)); 686 break; 687 } 688 *s = '\0'; 689 strncpy(name, envname, sizeof(name)); 690 envp = s + 1; 691 } 692 #ifdef INTERNET 693 else if (strncmp(envp, "port=", s - envp + 1) == 0) { 694 contactportstr = s + 1; 695 contactport = atoi(contactportstr); 696 if ((s = strchr(envp, ',')) == NULL) { 697 *envp = '\0'; 698 break; 699 } 700 *s = '\0'; 701 envp = s + 1; 702 } 703 else if (strncmp(envp, "host=", s - envp + 1) == 0) { 704 contacthost = s + 1; 705 if ((s = strchr(envp, ',')) == NULL) { 706 *envp = '\0'; 707 break; 708 } 709 *s = '\0'; 710 envp = s + 1; 711 } 712 else if (strncmp(envp, "message=", s - envp + 1) == 0) { 713 Send_message = s + 1; 714 if ((s = strchr(envp, ',')) == NULL) { 715 *envp = '\0'; 716 break; 717 } 718 *s = '\0'; 719 envp = s + 1; 720 } 721 #endif 722 else if (strncmp(envp, "team=", s - envp + 1) == 0) { 723 team = *(s + 1); 724 if (!isdigit((unsigned char)team)) 725 team = ' '; 726 if ((s = strchr(envp, ',')) == NULL) { 727 *envp = '\0'; 728 break; 729 } 730 *s = '\0'; 731 envp = s + 1; 732 } /* must be last option */ 733 else if (strncmp(envp, "mapkey=", s - envp + 1) == 0) { 734 for (s = s + 1; *s != '\0'; s += 2) { 735 map_key[(unsigned int) *s] = *(s + 1); 736 if (*(s + 1) == '\0') { 737 break; 738 } 739 } 740 *envp = '\0'; 741 break; 742 } else { 743 *s = '\0'; 744 printf("unknown option %s\n", envp); 745 if ((s = strchr(envp, ',')) == NULL) { 746 *envp = '\0'; 747 break; 748 } 749 envp = s + 1; 750 } 751 } 752 if (*envp != '\0') { 753 if (envname == NULL) 754 strncpy(name, envp, sizeof(name)); 755 else 756 printf("unknown option %s\n", envp); 757 } 758 } 759 return enter_status; 760 } 761 762 static void 763 fill_in_blanks(void) 764 { 765 int i; 766 char *cp; 767 768 again: 769 if (name[0] != '\0') { 770 printf("Entering as '%s'", name); 771 if (team != ' ') 772 printf(" on team %c.\n", team); 773 else 774 putchar('\n'); 775 } else { 776 printf("Enter your code name: "); 777 if (fgets(name, sizeof(name), stdin) == NULL) 778 exit(1); 779 } 780 rmnl(name); 781 if (name[0] == '\0') { 782 name[0] = '\0'; 783 printf("You have to have a code name!\n"); 784 goto again; 785 } 786 for (cp = name; *cp != '\0'; cp++) 787 if (!isprint((unsigned char)*cp)) { 788 name[0] = '\0'; 789 printf("Illegal character in your code name.\n"); 790 goto again; 791 } 792 if (team == ' ') { 793 printf("Enter your team (0-9 or nothing): "); 794 i = getchar(); 795 if (isdigit(i)) 796 team = i; 797 while (i != '\n' && i != EOF) 798 i = getchar(); 799 } 800 } 801