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