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