1 /*- 2 * Copyright (c) 1983-2003, Regents of the University of California. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of the University of California, San Francisco nor 15 * the names of its contributors may be used to endorse or promote 16 * products derived from this software without specific prior written 17 * permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 * 31 * $OpenBSD: hunt.c,v 1.13 2008/03/17 09:17:56 sobrado Exp $ 32 * $NetBSD: hunt.c,v 1.8 1998/09/13 15:27:28 hubertf Exp $ 33 */ 34 35 #include <ctype.h> 36 #include <err.h> 37 #include <errno.h> 38 #include <curses.h> 39 #include <signal.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 #include <netdb.h> 44 45 #include <sys/stat.h> 46 #include <sys/time.h> 47 #include <sys/types.h> 48 #include <sys/socket.h> 49 #include <sys/ioctl.h> 50 #include <sys/sockio.h> 51 52 #include <netinet/in.h> 53 #include <net/if.h> 54 55 #include <arpa/inet.h> 56 57 #include "hunt.h" 58 #include "display.h" 59 #include "client.h" 60 #include "list.h" 61 62 #ifndef __GNUC__ 63 #define __attribute__(x) 64 #endif 65 66 FLAG Am_monitor = FALSE; 67 int Socket; 68 char map_key[256]; /* what to map keys to */ 69 FLAG no_beep = FALSE; 70 char *Send_message = NULL; 71 72 static char *Sock_host; 73 static char *use_port; 74 static FLAG Query_driver = FALSE; 75 static FLAG Show_scores = FALSE; 76 static struct sockaddr Daemon; 77 78 79 static char name[NAMELEN]; 80 static char team = '-'; 81 82 static int in_visual; 83 84 static void dump_scores(void); 85 static long env_init(long); 86 static void fill_in_blanks(void); 87 static void leave(int, const char *) __attribute__((__noreturn__)); 88 static void sigterm(int); 89 static int find_driver(void); 90 91 /* 92 * main: 93 * Main program for local process 94 */ 95 int 96 main(int ac, char **av) 97 { 98 int c; 99 long enter_status; 100 int option; 101 struct servent *se; 102 103 enter_status = env_init((long) Q_CLOAK); 104 while ((c = getopt(ac, av, "Sbcfh:l:mn:op:qst:w:")) != -1) { 105 switch (c) { 106 case 'l': /* rsh compatibility */ 107 case 'n': 108 (void) strlcpy(name, optarg, sizeof name); 109 break; 110 case 't': 111 team = *optarg; 112 if (!isdigit(team) && team != ' ') { 113 warnx("Team names must be numeric or space"); 114 team = '-'; 115 } 116 break; 117 case 'o': 118 Otto_mode = TRUE; 119 break; 120 case 'm': 121 Am_monitor = TRUE; 122 break; 123 case 'S': 124 Show_scores = TRUE; 125 break; 126 case 'q': /* query whether hunt is running */ 127 Query_driver = TRUE; 128 break; 129 case 'w': 130 Send_message = optarg; 131 break; 132 case 'h': 133 Sock_host = optarg; 134 break; 135 case 'p': 136 use_port = optarg; 137 Server_port = atoi(use_port); 138 break; 139 case 'c': 140 enter_status = Q_CLOAK; 141 break; 142 case 'f': 143 enter_status = Q_FLY; 144 break; 145 case 's': 146 enter_status = Q_SCAN; 147 break; 148 case 'b': 149 no_beep = !no_beep; 150 break; 151 default: 152 usage: 153 fputs("usage: hunt [-bcfmqSs] [-n name] [-p port] " 154 "[-t team] [-w message] [[-h] host]\n", 155 stderr); 156 exit(1); 157 } 158 } 159 if (optind + 1 < ac) 160 goto usage; 161 else if (optind + 1 == ac) 162 Sock_host = av[ac - 1]; 163 164 if (Server_port == 0) { 165 se = getservbyname("hunt", "udp"); 166 if (se != NULL) 167 Server_port = ntohs(se->s_port); 168 else 169 Server_port = HUNT_PORT; 170 } 171 172 if (Show_scores) { 173 dump_scores(); 174 exit(0); 175 } 176 177 if (Query_driver) { 178 struct driver *driver; 179 180 probe_drivers(C_MESSAGE, Sock_host); 181 while ((driver = next_driver()) != NULL) { 182 printf("%d player%s hunting on %s!\n", 183 driver->response, 184 (driver->response == 1) ? "" : "s", 185 driver_name(driver)); 186 if (Sock_host) 187 break; 188 } 189 exit(0); 190 } 191 if (Otto_mode) { 192 if (Am_monitor) 193 errx(1, "otto mode incompatible with monitor mode"); 194 (void) strlcpy(name, "otto", sizeof name); 195 team = ' '; 196 } else 197 fill_in_blanks(); 198 199 (void) fflush(stdout); 200 display_open(); 201 in_visual = TRUE; 202 if (LINES < SCREEN_HEIGHT || COLS < SCREEN_WIDTH) { 203 errno = 0; 204 leave(1, "Need a larger window"); 205 } 206 display_clear_the_screen(); 207 (void) signal(SIGINT, intr); 208 (void) signal(SIGTERM, sigterm); 209 /* (void) signal(SIGPIPE, SIG_IGN); */ 210 211 Daemon.sa_len = 0; 212 ask_driver: 213 while (!find_driver()) { 214 if (Am_monitor) { 215 errno = 0; 216 leave(1, "No one playing"); 217 } 218 219 if (Sock_host == NULL) { 220 errno = 0; 221 leave(1, "huntd not running"); 222 } 223 224 sleep(3); 225 } 226 Socket = -1; 227 228 for (;;) { 229 if (Socket != -1) 230 close(Socket); 231 232 Socket = socket(Daemon.sa_family, SOCK_STREAM, 0); 233 if (Socket < 0) 234 leave(1, "socket"); 235 236 option = 1; 237 if (setsockopt(Socket, SOL_SOCKET, SO_USELOOPBACK, 238 &option, sizeof option) < 0) 239 warn("setsockopt loopback"); 240 241 errno = 0; 242 if (connect(Socket, &Daemon, Daemon.sa_len) == -1) { 243 if (errno == ECONNREFUSED) 244 goto ask_driver; 245 leave(1, "connect"); 246 } 247 248 do_connect(name, team, enter_status); 249 if (Send_message != NULL) { 250 do_message(); 251 if (enter_status == Q_MESSAGE) 252 break; 253 Send_message = NULL; 254 continue; 255 } 256 playit(); 257 if ((enter_status = quit(enter_status)) == Q_QUIT) 258 break; 259 } 260 leave(0, NULL); 261 /* NOTREACHED */ 262 return(0); 263 } 264 265 /* 266 * Set Daemon to be the address of a hunt driver, or return 0 on failure. 267 * 268 * We start quietly probing for drivers. As soon as one driver is found 269 * we show it in the list. If we run out of drivers and we only have one 270 * then we choose it. Otherwise we present a list of the found drivers. 271 */ 272 static int 273 find_driver(void) 274 { 275 int last_driver, numdrivers, waiting, is_current; 276 struct driver *driver; 277 int c; 278 char buf[80]; 279 const char *xname; 280 281 probe_drivers(Am_monitor ? C_MONITOR : C_PLAYER, Sock_host); 282 283 last_driver = -1; 284 numdrivers = 0; 285 waiting = 1; 286 for (;;) { 287 if (numdrivers == 0) { 288 /* Silently wait for at least one driver */ 289 driver = next_driver(); 290 } else if (!waiting || (driver = 291 next_driver_fd(STDIN_FILENO)) == (struct driver *)-1) { 292 /* We have a key waiting, or no drivers left */ 293 c = getchar(); 294 if (c == '\r' || c == '\n' || c == ' ') { 295 if (numdrivers == 1) 296 c = 'a'; 297 else if (last_driver != -1) 298 c = 'a' + last_driver; 299 } 300 if (c < 'a' || c >= numdrivers + 'a') { 301 display_beep(); 302 continue; 303 } 304 driver = &drivers[c - 'a']; 305 break; 306 } 307 308 if (driver == NULL) { 309 waiting = 0; 310 if (numdrivers == 0) { 311 probe_cleanup(); 312 return 0; /* Failure */ 313 } 314 if (numdrivers == 1) { 315 driver = &drivers[0]; 316 break; 317 } 318 continue; 319 } 320 321 /* Use the preferred host straight away. */ 322 if (Sock_host) 323 break; 324 325 if (numdrivers == 0) { 326 display_clear_the_screen(); 327 display_move(1, 0); 328 display_put_str("Pick one:"); 329 } 330 331 /* Mark the last driver we used with an asterisk */ 332 is_current = (last_driver == -1 && Daemon.sa_len != 0 && 333 memcmp(&Daemon, &driver->addr, Daemon.sa_len) == 0); 334 if (is_current) 335 last_driver = numdrivers; 336 337 /* Display it in the list if there is room */ 338 if (numdrivers < HEIGHT - 3) { 339 xname = driver_name(driver); 340 display_move(3 + numdrivers, 0); 341 snprintf(buf, sizeof buf, "%6c %c %s", 342 is_current ? '*' : ' ', 'a' + numdrivers, xname); 343 display_put_str(buf); 344 } 345 346 /* Clear the last 'Enter letter' line if any */ 347 display_move(4 + numdrivers, 0); 348 display_clear_eol(); 349 350 if (last_driver != -1) 351 snprintf(buf, sizeof buf, "Enter letter [%c]: ", 352 'a' + last_driver); 353 else 354 snprintf(buf, sizeof buf, "Enter letter: "); 355 356 display_move(5 + numdrivers, 0); 357 display_put_str(buf); 358 display_refresh(); 359 360 numdrivers++; 361 } 362 363 display_clear_the_screen(); 364 Daemon = driver->addr; 365 366 probe_cleanup(); 367 return 1; /* Success */ 368 } 369 370 static void 371 dump_scores(void) 372 { 373 struct driver *driver; 374 int s, cnt, i; 375 char buf[1024]; 376 377 probe_drivers(C_SCORES, Sock_host); 378 while ((driver = next_driver()) != NULL) { 379 printf("\n%s:\n", driver_name(driver)); 380 fflush(stdout); 381 382 if ((s = socket(driver->addr.sa_family, SOCK_STREAM, 0)) < 0) { 383 warn("socket"); 384 continue; 385 } 386 if (connect(s, &driver->addr, driver->addr.sa_len) < 0) { 387 warn("connect"); 388 close(s); 389 continue; 390 } 391 while ((cnt = read(s, buf, sizeof buf)) > 0) { 392 /* Whittle out bad characters */ 393 for (i = 0; i < cnt; i++) 394 if ((buf[i] < ' ' || buf[i] > '~') && 395 buf[i] != '\n' && buf[i] != '\t') 396 buf[i] = '?'; 397 fwrite(buf, cnt, 1, stdout); 398 } 399 if (cnt < 0) 400 warn("read"); 401 (void)close(s); 402 if (Sock_host) 403 break; 404 } 405 probe_cleanup(); 406 } 407 408 409 /* 410 * bad_con: 411 * We had a bad connection. For the moment we assume that this 412 * means the game is full. 413 */ 414 void 415 bad_con(void) 416 { 417 leave(1, "lost connection to huntd"); 418 } 419 420 /* 421 * bad_ver: 422 * version number mismatch. 423 */ 424 void 425 bad_ver(void) 426 { 427 errno = 0; 428 leave(1, "Version number mismatch. No go."); 429 } 430 431 /* 432 * sigterm: 433 * Handle a terminate signal 434 */ 435 static void 436 sigterm(int signo __unused) 437 { 438 leave(0, NULL); 439 } 440 441 /* 442 * rmnl: 443 * Remove a '\n' at the end of a string if there is one 444 */ 445 static void 446 rmnl(char *s) 447 { 448 char *cp; 449 450 cp = strrchr(s, '\n'); 451 if (cp != NULL) 452 *cp = '\0'; 453 } 454 455 /* 456 * intr: 457 * Handle a interrupt signal 458 */ 459 void 460 intr(int dummy __unused) 461 { 462 int ch; 463 int explained; 464 int y, x; 465 466 (void) signal(SIGINT, SIG_IGN); 467 display_getyx(&y, &x); 468 display_move(HEIGHT, 0); 469 display_put_str("Really quit? "); 470 display_clear_eol(); 471 display_refresh(); 472 explained = FALSE; 473 for (;;) { 474 ch = getchar(); 475 if (isupper(ch)) 476 ch = tolower(ch); 477 if (ch == 'y') { 478 if (Socket != 0) { 479 (void) write(Socket, "q", 1); 480 (void) close(Socket); 481 } 482 leave(0, NULL); 483 } 484 else if (ch == 'n') { 485 (void) signal(SIGINT, intr); 486 display_move(y, x); 487 display_refresh(); 488 return; 489 } 490 if (!explained) { 491 display_put_str("(Yes or No) "); 492 display_refresh(); 493 explained = TRUE; 494 } 495 display_beep(); 496 display_refresh(); 497 } 498 } 499 500 /* 501 * leave: 502 * Leave the game somewhat gracefully, restoring all current 503 * tty stats. 504 */ 505 static void 506 leave(int eval, const char *mesg) 507 { 508 int saved_errno; 509 510 saved_errno = errno; 511 if (in_visual) { 512 display_move(HEIGHT, 0); 513 display_refresh(); 514 display_end(); 515 } 516 errno = saved_errno; 517 518 if (errno == 0 && mesg != NULL) 519 errx(eval, "%s", mesg); 520 else if (mesg != NULL) 521 err(eval, "%s", mesg); 522 exit(eval); 523 } 524 525 /* 526 * env_init: 527 * initialise game parameters from the HUNT envvar 528 */ 529 static long 530 env_init(long enter_status) 531 { 532 int i; 533 char *envp, *envname, *s; 534 535 /* Map all keys to themselves: */ 536 for (i = 0; i < 256; i++) 537 map_key[i] = (char) i; 538 539 envname = NULL; 540 if ((envp = getenv("HUNT")) != NULL) { 541 while ((s = strpbrk(envp, "=,")) != NULL) { 542 if (strncmp(envp, "cloak,", s - envp + 1) == 0) { 543 enter_status = Q_CLOAK; 544 envp = s + 1; 545 } 546 else if (strncmp(envp, "scan,", s - envp + 1) == 0) { 547 enter_status = Q_SCAN; 548 envp = s + 1; 549 } 550 else if (strncmp(envp, "fly,", s - envp + 1) == 0) { 551 enter_status = Q_FLY; 552 envp = s + 1; 553 } 554 else if (strncmp(envp, "nobeep,", s - envp + 1) == 0) { 555 no_beep = TRUE; 556 envp = s + 1; 557 } 558 else if (strncmp(envp, "name=", s - envp + 1) == 0) { 559 envname = s + 1; 560 if ((s = strchr(envp, ',')) == NULL) { 561 *envp = '\0'; 562 strlcpy(name, envname, sizeof name); 563 break; 564 } 565 *s = '\0'; 566 strlcpy(name, envname, sizeof name); 567 envp = s + 1; 568 } 569 else if (strncmp(envp, "port=", s - envp + 1) == 0) { 570 use_port = s + 1; 571 Server_port = atoi(use_port); 572 if ((s = strchr(envp, ',')) == NULL) { 573 *envp = '\0'; 574 break; 575 } 576 *s = '\0'; 577 envp = s + 1; 578 } 579 else if (strncmp(envp, "host=", s - envp + 1) == 0) { 580 Sock_host = s + 1; 581 if ((s = strchr(envp, ',')) == NULL) { 582 *envp = '\0'; 583 break; 584 } 585 *s = '\0'; 586 envp = s + 1; 587 } 588 else if (strncmp(envp, "message=", s - envp + 1) == 0) { 589 Send_message = s + 1; 590 if ((s = strchr(envp, ',')) == NULL) { 591 *envp = '\0'; 592 break; 593 } 594 *s = '\0'; 595 envp = s + 1; 596 } 597 else if (strncmp(envp, "team=", s - envp + 1) == 0) { 598 team = *(s + 1); 599 if (!isdigit(team)) 600 team = ' '; 601 if ((s = strchr(envp, ',')) == NULL) { 602 *envp = '\0'; 603 break; 604 } 605 *s = '\0'; 606 envp = s + 1; 607 } /* must be last option */ 608 else if (strncmp(envp, "mapkey=", s - envp + 1) == 0) { 609 for (s = s + 1; *s != '\0'; s += 2) { 610 map_key[(unsigned int) *s] = *(s + 1); 611 if (*(s + 1) == '\0') { 612 break; 613 } 614 } 615 *envp = '\0'; 616 break; 617 } else { 618 *s = '\0'; 619 printf("unknown option %s\n", envp); 620 if ((s = strchr(envp, ',')) == NULL) { 621 *envp = '\0'; 622 break; 623 } 624 envp = s + 1; 625 } 626 } 627 if (*envp != '\0') { 628 if (envname == NULL) 629 strlcpy(name, envp, sizeof name); 630 else 631 printf("unknown option %s\n", envp); 632 } 633 } 634 return enter_status; 635 } 636 637 /* 638 * fill_in_blanks: 639 * quiz the user for the information they didn't provide earlier 640 */ 641 static void 642 fill_in_blanks(void) 643 { 644 int i; 645 char *cp; 646 647 again: 648 if (name[0] != '\0') { 649 printf("Entering as '%s'", name); 650 if (team != ' ' && team != '-') 651 printf(" on team %c.\n", team); 652 else 653 putchar('\n'); 654 } else { 655 printf("Enter your code name: "); 656 if (fgets(name, sizeof name, stdin) == NULL) 657 exit(1); 658 } 659 rmnl(name); 660 if (name[0] == '\0') { 661 printf("You have to have a code name!\n"); 662 goto again; 663 } 664 for (cp = name; *cp != '\0'; cp++) 665 if (!isprint(*cp)) { 666 name[0] = '\0'; 667 printf("Illegal character in your code name.\n"); 668 goto again; 669 } 670 if (team == '-') { 671 printf("Enter your team (0-9 or nothing): "); 672 i = getchar(); 673 if (isdigit(i)) 674 team = i; 675 else if (i == '\n' || i == EOF || i == ' ') 676 team = ' '; 677 /* ignore trailing chars */ 678 while (i != '\n' && i != EOF) 679 i = getchar(); 680 if (team == '-') { 681 printf("Teams must be numeric.\n"); 682 goto again; 683 } 684 } 685 } 686