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