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