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 *
lookuphost(const struct sockaddr_storage * host,socklen_t hostlen)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
main(int ac,char ** av)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
find_driver(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
dump_scores(const struct sockaddr_storage * host,socklen_t hostlen)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
start_driver(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
bad_con(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
bad_ver(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
sigterm(int dummy __unused)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
sigusr1(int dummy __unused)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
rmnl(char * s)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
intr(int dummy __unused)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
fincurs(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
leave(int exitval,const char * fmt,...)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
leavex(int exitval,const char * fmt,...)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
env_init(int enter_status)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
fill_in_blanks(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