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