xref: /openbsd-src/games/hunt/huntd/answer.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: answer.c,v 1.11 2007/11/06 10:22:29 chl Exp $	*/
2 /*	$NetBSD: answer.c,v 1.3 1997/10/10 16:32:50 lukem 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 <ctype.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39 #include <stdio.h>
40 #include <tcpd.h>
41 #include <syslog.h>
42 #include <string.h>
43 #include <sys/socket.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 
47 #include "hunt.h"
48 #include "server.h"
49 #include "conf.h"
50 
51 /* Exported symbols for hosts_access(): */
52 int allow_severity	= LOG_INFO;
53 int deny_severity	= LOG_WARNING;
54 
55 
56 /* List of spawning connections: */
57 struct spawn		*Spawn = NULL;
58 
59 static void	stplayer(PLAYER *, int);
60 static void	stmonitor(PLAYER *);
61 static IDENT *	get_ident(struct sockaddr *, int, u_long, char *, char);
62 
63 void
64 answer_first()
65 {
66 	struct sockaddr		sockstruct;
67 	int			newsock;
68 	socklen_t		socklen;
69 	int			flags;
70 	struct request_info	ri;
71 	struct spawn *sp;
72 
73 	/* Answer the call to hunt: */
74 	socklen = sizeof sockstruct;
75 	newsock = accept(Socket, (struct sockaddr *) &sockstruct, &socklen);
76 	if (newsock < 0) {
77 		logit(LOG_ERR, "accept");
78 		return;
79 	}
80 
81 	/* Check for access permissions: */
82 	request_init(&ri, RQ_DAEMON, "huntd", RQ_FILE, newsock, 0);
83 	fromhost(&ri);
84 	if (hosts_access(&ri) == 0) {
85 		logx(LOG_INFO, "rejected connection from %s", eval_client(&ri));
86 		close(newsock);
87 		return;
88 	}
89 
90 	/* Remember this spawning connection: */
91 	sp = (struct spawn *)malloc(sizeof *sp);
92 	if (sp == NULL) {
93 		logit(LOG_ERR, "malloc");
94 		close(newsock);
95 		return;
96 	}
97 	memset(sp, '\0', sizeof *sp);
98 
99 	/* Keep the calling machine's source addr for ident purposes: */
100 	memcpy(&sp->source, &sockstruct, sizeof sp->source);
101 	sp->sourcelen = socklen;
102 
103 	/* Warn if we lose connection info: */
104 	if (socklen > sizeof Spawn->source)
105 		logx(LOG_WARNING,
106 		    "struct sockaddr is not big enough! (%d > %zu)",
107 		    socklen, sizeof Spawn->source);
108 
109 	/*
110 	 * Turn off blocking I/O, so a slow or dead terminal won't stop
111 	 * the game.  All subsequent reads check how many bytes they read.
112 	 */
113 	flags = fcntl(newsock, F_GETFL, 0);
114 	flags |= O_NDELAY;
115 	(void) fcntl(newsock, F_SETFL, flags);
116 
117 	/* Start listening to the spawning connection */
118 	sp->fd = newsock;
119 	FD_SET(sp->fd, &Fds_mask);
120 	if (sp->fd >= Num_fds)
121 		Num_fds = sp->fd + 1;
122 
123 	sp->reading_msg = 0;
124 	sp->inlen = 0;
125 
126 	/* Add to the spawning list */
127 	if ((sp->next = Spawn) != NULL)
128 		Spawn->prevnext = &sp->next;
129 	sp->prevnext = &Spawn;
130 	Spawn = sp;
131 }
132 
133 int
134 answer_next(sp)
135 	struct spawn *sp;
136 {
137 	PLAYER			*pp;
138 	char			*cp1, *cp2;
139 	u_int32_t		version;
140 	FILE			*conn;
141 	int			len;
142 	char 			teamstr[] = "[x]";
143 
144 	if (sp->reading_msg) {
145 		/* Receive a message from a player */
146 		len = read(sp->fd, sp->msg + sp->msglen,
147 		    sizeof sp->msg - sp->msglen);
148 		if (len < 0)
149 			goto error;
150 		sp->msglen += len;
151 		if (len && sp->msglen < sizeof sp->msg)
152 			return FALSE;
153 
154 		teamstr[1] = sp->team;
155 		outyx(ALL_PLAYERS, HEIGHT, 0, "%s%s: %.*s",
156 			sp->name,
157 			sp->team == ' ' ? "": teamstr,
158 			sp->msglen,
159 			sp->msg);
160 		ce(ALL_PLAYERS);
161 		sendcom(ALL_PLAYERS, REFRESH);
162 		sendcom(ALL_PLAYERS, READY, 0);
163 		flush(ALL_PLAYERS);
164 		goto close_it;
165 	}
166 
167 	/* Fill the buffer */
168 	len = read(sp->fd, sp->inbuf + sp->inlen,
169 	    sizeof sp->inbuf - sp->inlen);
170 	if (len <= 0)
171 		goto error;
172 	sp->inlen += len;
173 	if (sp->inlen < sizeof sp->inbuf)
174 		return FALSE;
175 
176 	/* Extract values from the buffer */
177 	cp1 = sp->inbuf;
178 	memcpy(&sp->uid, cp1, sizeof (u_int32_t));
179 	cp1+= sizeof(u_int32_t);
180 	memcpy(sp->name, cp1, NAMELEN);
181 	cp1+= NAMELEN;
182 	memcpy(&sp->team, cp1, sizeof (u_int8_t));
183 	cp1+= sizeof(u_int8_t);
184 	memcpy(&sp->enter_status, cp1, sizeof (u_int32_t));
185 	cp1+= sizeof(u_int32_t);
186 	memcpy(sp->ttyname, cp1, NAMELEN);
187 	cp1+= NAMELEN;
188 	memcpy(&sp->mode, cp1, sizeof (u_int32_t));
189 	cp1+= sizeof(u_int32_t);
190 
191 	/* Convert data from network byte order: */
192 	sp->uid = ntohl(sp->uid);
193 	sp->enter_status = ntohl(sp->enter_status);
194 	sp->mode = ntohl(sp->mode);
195 
196 	/*
197 	 * Make sure the name contains only printable characters
198 	 * since we use control characters for cursor control
199 	 * between driver and player processes
200 	 */
201 	sp->name[NAMELEN] = '\0';
202 	for (cp1 = cp2 = sp->name; *cp1 != '\0'; cp1++)
203 		if (isprint(*cp1) || *cp1 == ' ')
204 			*cp2++ = *cp1;
205 	*cp2 = '\0';
206 
207 	/* Make sure team name is valid */
208 	if (sp->team < '1' || sp->team > '9')
209 		sp->team = ' ';
210 
211 	/* Tell the other end this server's hunt driver version: */
212 	version = htonl((u_int32_t) HUNT_VERSION);
213 	(void) write(sp->fd, &version, sizeof version);
214 
215 	if (sp->mode == C_MESSAGE) {
216 		/* The clients only wants to send a message: */
217 		sp->msglen = 0;
218 		sp->reading_msg = 1;
219 		return FALSE;
220 	}
221 
222 	/* Use a stdio file descriptor from now on: */
223 	conn = fdopen(sp->fd, "w");
224 
225 	/* The player is a monitor: */
226 	if (sp->mode == C_MONITOR) {
227 		if (conf_monitor && End_monitor < &Monitor[MAXMON]) {
228 			pp = End_monitor++;
229 			if (sp->team == ' ')
230 				sp->team = '*';
231 		} else {
232 			/* Too many monitors */
233 			fprintf(conn, "Too many monitors\n");
234 			fflush(conn);
235 			logx(LOG_NOTICE, "too many monitors");
236 			goto close_it;
237 		}
238 
239 	/* The player is a normal hunter: */
240 	} else {
241 		if (End_player < &Player[MAXPL])
242 			pp = End_player++;
243 		else {
244 			fprintf(conn, "Too many players\n");
245 			fflush(conn);
246 			/* Too many players */
247 			logx(LOG_NOTICE, "too many players");
248 			goto close_it;
249 		}
250 	}
251 
252 	/* Find the player's running scorecard */
253 	pp->p_ident = get_ident(&sp->source, sp->sourcelen, sp->uid,
254 	    sp->name, sp->team);
255 	pp->p_output = conn;
256 	pp->p_death[0] = '\0';
257 	pp->p_fd = sp->fd;
258 
259 	/* No idea where the player starts: */
260 	pp->p_y = 0;
261 	pp->p_x = 0;
262 
263 	/* Mode-specific initialisation: */
264 	if (sp->mode == C_MONITOR)
265 		stmonitor(pp);
266 	else
267 		stplayer(pp, sp->enter_status);
268 
269 	/* And, they're off! Caller should remove and free sp. */
270 	return TRUE;
271 
272 error:
273 	if (len < 0)
274 		logit(LOG_WARNING, "read");
275 	else
276 		logx(LOG_WARNING, "lost connection to new client");
277 
278 close_it:
279 	/* Destroy the spawn */
280 	*sp->prevnext = sp->next;
281 	if (sp->next) sp->next->prevnext = sp->prevnext;
282 	FD_CLR(sp->fd, &Fds_mask);
283 	close(sp->fd);
284 	free(sp);
285 	return FALSE;
286 }
287 
288 /* Start a monitor: */
289 static void
290 stmonitor(pp)
291 	PLAYER	*pp;
292 {
293 
294 	/* Monitors get to see the entire maze: */
295 	memcpy(pp->p_maze, Maze, sizeof pp->p_maze);
296 	drawmaze(pp);
297 
298 	/* Put the monitor's name near the bottom right on all screens: */
299 	outyx(ALL_PLAYERS,
300 		STAT_MON_ROW + 1 + (pp - Monitor), STAT_NAME_COL,
301 		"%5.5s%c%-10.10s %c", " ",
302 		stat_char(pp), pp->p_ident->i_name, pp->p_ident->i_team);
303 
304 	/* Ready the monitor: */
305 	sendcom(pp, REFRESH);
306 	sendcom(pp, READY, 0);
307 	flush(pp);
308 }
309 
310 /* Start a player: */
311 static void
312 stplayer(newpp, enter_status)
313 	PLAYER	*newpp;
314 	int	enter_status;
315 {
316 	int	x, y;
317 	PLAYER	*pp;
318 	int len;
319 
320 	Nplayer++;
321 
322 	for (y = 0; y < UBOUND; y++)
323 		for (x = 0; x < WIDTH; x++)
324 			newpp->p_maze[y][x] = Maze[y][x];
325 	for (     ; y < DBOUND; y++) {
326 		for (x = 0; x < LBOUND; x++)
327 			newpp->p_maze[y][x] = Maze[y][x];
328 		for (     ; x < RBOUND; x++)
329 			newpp->p_maze[y][x] = SPACE;
330 		for (     ; x < WIDTH;  x++)
331 			newpp->p_maze[y][x] = Maze[y][x];
332 	}
333 	for (     ; y < HEIGHT; y++)
334 		for (x = 0; x < WIDTH; x++)
335 			newpp->p_maze[y][x] = Maze[y][x];
336 
337 	/* Drop the new player somewhere in the maze: */
338 	do {
339 		x = rand_num(WIDTH - 1) + 1;
340 		y = rand_num(HEIGHT - 1) + 1;
341 	} while (Maze[y][x] != SPACE);
342 	newpp->p_over = SPACE;
343 	newpp->p_x = x;
344 	newpp->p_y = y;
345 	newpp->p_undershot = FALSE;
346 
347 	/* Send them flying if needed */
348 	if (enter_status == Q_FLY && conf_fly) {
349 		newpp->p_flying = rand_num(conf_flytime);
350 		newpp->p_flyx = 2 * rand_num(conf_flystep + 1) - conf_flystep;
351 		newpp->p_flyy = 2 * rand_num(conf_flystep + 1) - conf_flystep;
352 		newpp->p_face = FLYER;
353 	} else {
354 		newpp->p_flying = -1;
355 		newpp->p_face = rand_dir();
356 	}
357 
358 	/* Initialize the new player's attributes: */
359 	newpp->p_damage = 0;
360 	newpp->p_damcap = conf_maxdam;
361 	newpp->p_nchar = 0;
362 	newpp->p_ncount = 0;
363 	newpp->p_nexec = 0;
364 	newpp->p_ammo = conf_ishots;
365 	newpp->p_nboots = 0;
366 
367 	/* Decide on what cloak/scan status to enter with */
368 	if (enter_status == Q_SCAN && conf_scan) {
369 		newpp->p_scan = conf_scanlen * Nplayer;
370 		newpp->p_cloak = 0;
371 	} else if (conf_cloak) {
372 		newpp->p_scan = 0;
373 		newpp->p_cloak = conf_cloaklen;
374 	} else {
375 		newpp->p_scan = 0;
376 		newpp->p_cloak = 0;
377 	}
378 	newpp->p_ncshot = 0;
379 
380 	/*
381 	 * For each new player, place a large mine and
382 	 * a small mine somewhere in the maze:
383 	 */
384 	do {
385 		x = rand_num(WIDTH - 1) + 1;
386 		y = rand_num(HEIGHT - 1) + 1;
387 	} while (Maze[y][x] != SPACE);
388 	Maze[y][x] = GMINE;
389 	for (pp = Monitor; pp < End_monitor; pp++)
390 		check(pp, y, x);
391 
392 	do {
393 		x = rand_num(WIDTH - 1) + 1;
394 		y = rand_num(HEIGHT - 1) + 1;
395 	} while (Maze[y][x] != SPACE);
396 	Maze[y][x] = MINE;
397 	for (pp = Monitor; pp < End_monitor; pp++)
398 		check(pp, y, x);
399 
400 	/* Create a score line for the new player: */
401 	(void) snprintf(Buf, sizeof Buf, "%5.2f%c%-10.10s %c",
402 		newpp->p_ident->i_score, stat_char(newpp),
403 		newpp->p_ident->i_name, newpp->p_ident->i_team);
404 	len = strlen(Buf);
405 	y = STAT_PLAY_ROW + 1 + (newpp - Player);
406 	for (pp = Player; pp < End_player; pp++) {
407 		if (pp != newpp) {
408 			/* Give everyone a few more shots: */
409 			pp->p_ammo += conf_nshots;
410 			newpp->p_ammo += conf_nshots;
411 			outyx(pp, y, STAT_NAME_COL, Buf, len);
412 			ammo_update(pp);
413 		}
414 	}
415 	for (pp = Monitor; pp < End_monitor; pp++)
416 		outyx(pp, y, STAT_NAME_COL, Buf, len);
417 
418 	/* Show the new player what they can see and where they are: */
419 	drawmaze(newpp);
420 	drawplayer(newpp, TRUE);
421 	look(newpp);
422 
423 	/* Make sure that the position they enter in will be erased: */
424 	if (enter_status == Q_FLY && conf_fly)
425 		showexpl(newpp->p_y, newpp->p_x, FLYER);
426 
427 	/* Ready the new player: */
428 	sendcom(newpp, REFRESH);
429 	sendcom(newpp, READY, 0);
430 	flush(newpp);
431 }
432 
433 /*
434  * rand_dir:
435  *	Return a random direction
436  */
437 int
438 rand_dir()
439 {
440 	switch (rand_num(4)) {
441 	  case 0:
442 		return LEFTS;
443 	  case 1:
444 		return RIGHT;
445 	  case 2:
446 		return BELOW;
447 	  case 3:
448 		return ABOVE;
449 	}
450 	/* NOTREACHED */
451 	return(-1);
452 }
453 
454 /*
455  * get_ident:
456  *	Get the score structure of a player
457  */
458 static IDENT *
459 get_ident(sa, salen, uid, name, team)
460 	struct sockaddr *sa;
461 	int	salen;
462 	u_long	uid;
463 	char	*name;
464 	char	team;
465 {
466 	IDENT		*ip;
467 	static IDENT	punt;
468 	u_int32_t	machine;
469 
470 	if (sa->sa_family == AF_INET)
471 		machine = ntohl((u_long)((struct sockaddr_in *)sa)->sin_addr.s_addr);
472 	else
473 		machine = 0;
474 
475 	for (ip = Scores; ip != NULL; ip = ip->i_next)
476 		if (ip->i_machine == machine
477 		&&  ip->i_uid == uid
478 		/* &&  ip->i_team == team */
479 		&&  strncmp(ip->i_name, name, NAMELEN) == 0)
480 			break;
481 
482 	if (ip != NULL) {
483 		if (ip->i_team != team) {
484 			logx(LOG_INFO, "player %s %s team %c",
485 				name,
486 				team == ' ' ? "left" : ip->i_team == ' ' ?
487 					"joined" : "changed to",
488 				team == ' ' ? ip->i_team : team);
489 			ip->i_team = team;
490 		}
491 		if (ip->i_entries < conf_scoredecay)
492 			ip->i_entries++;
493 		else
494 			ip->i_kills = (ip->i_kills * (conf_scoredecay - 1))
495 				/ conf_scoredecay;
496 		ip->i_score = ip->i_kills / (double) ip->i_entries;
497 	}
498 	else {
499 		/* Alloc new entry -- it is released in clear_scores() */
500 		ip = (IDENT *) malloc(sizeof (IDENT));
501 		if (ip == NULL) {
502 			logit(LOG_ERR, "malloc");
503 			/* Fourth down, time to punt */
504 			ip = &punt;
505 		}
506 		ip->i_machine = machine;
507 		ip->i_team = team;
508 		ip->i_uid = uid;
509 		strlcpy(ip->i_name, name, sizeof ip->i_name);
510 		ip->i_kills = 0;
511 		ip->i_entries = 1;
512 		ip->i_score = 0;
513 		ip->i_absorbed = 0;
514 		ip->i_faced = 0;
515 		ip->i_shot = 0;
516 		ip->i_robbed = 0;
517 		ip->i_slime = 0;
518 		ip->i_missed = 0;
519 		ip->i_ducked = 0;
520 		ip->i_gkills = ip->i_bkills = ip->i_deaths = 0;
521 		ip->i_stillb = ip->i_saved = 0;
522 		ip->i_next = Scores;
523 		Scores = ip;
524 
525 		logx(LOG_INFO, "new player: %s%s%c%s",
526 			name,
527 			team == ' ' ? "" : " (team ",
528 			team,
529 			team == ' ' ? "" : ")");
530 	}
531 
532 	return ip;
533 }
534 
535 void
536 answer_info(fp)
537 	FILE *fp;
538 {
539 	struct spawn *sp;
540 	char buf[128];
541 	const char *bf;
542 	struct sockaddr_in *sa;
543 
544 	if (Spawn == NULL)
545 		return;
546 	fprintf(fp, "\nSpawning connections:\n");
547 	for (sp = Spawn; sp; sp = sp->next) {
548 		sa = (struct sockaddr_in *)&sp->source;
549 		bf = inet_ntop(AF_INET, &sa->sin_addr, buf, sizeof buf);
550 		if (!bf)  {
551 			logit(LOG_WARNING, "inet_ntop");
552 			bf = "?";
553 		}
554 		fprintf(fp, "fd %d: state %d, from %s:%d\n",
555 			sp->fd, sp->inlen + (sp->reading_msg ? sp->msglen : 0),
556 			bf, sa->sin_port);
557 	}
558 }
559