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