xref: /openbsd/games/hunt/hunt/hunt.c (revision d167610c)
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