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