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