Bring hunt in from OpenBSD. The best multi-player terminal game ever!
[dragonfly.git] / games / hunt / huntd / answer.c
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.1 2008/09/02 21:50:21 dillon 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()
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(sp)
138         struct spawn *sp;
139 {
140         PLAYER                  *pp;
141         char                    *cp1, *cp2;
142         u_int32_t               version;
143         FILE                    *conn;
144         int                     len;
145         char                    teamstr[] = "[x]";
146
147         if (sp->reading_msg) {
148                 /* Receive a message from a player */
149                 len = read(sp->fd, sp->msg + sp->msglen, 
150                     sizeof sp->msg - sp->msglen);
151                 if (len < 0)
152                         goto error;
153                 sp->msglen += len;
154                 if (len && sp->msglen < (int)(sizeof sp->msg))
155                         return FALSE;
156
157                 teamstr[1] = sp->team;
158                 outyx(ALL_PLAYERS, HEIGHT, 0, "%s%s: %.*s",
159                         sp->name,
160                         sp->team == ' ' ? "": teamstr,
161                         sp->msglen,
162                         sp->msg);
163                 ce(ALL_PLAYERS);
164                 sendcom(ALL_PLAYERS, REFRESH);
165                 sendcom(ALL_PLAYERS, READY, 0);
166                 flush(ALL_PLAYERS);
167                 goto close_it;
168         }
169
170         /* Fill the buffer */
171         len = read(sp->fd, sp->inbuf + sp->inlen, 
172             sizeof sp->inbuf - sp->inlen);
173         if (len <= 0)
174                 goto error;
175         sp->inlen += len;
176         if (sp->inlen < (int)(sizeof sp->inbuf))
177                 return FALSE;
178
179         /* Extract values from the buffer */
180         cp1 = sp->inbuf;
181         memcpy(&sp->uid, cp1, sizeof (u_int32_t));
182         cp1+= sizeof(u_int32_t);
183         memcpy(sp->name, cp1, NAMELEN);
184         cp1+= NAMELEN;
185         memcpy(&sp->team, cp1, sizeof (u_int8_t));
186         cp1+= sizeof(u_int8_t);
187         memcpy(&sp->enter_status, cp1, sizeof (u_int32_t));
188         cp1+= sizeof(u_int32_t);
189         memcpy(sp->ttyname, cp1, NAMELEN);
190         cp1+= NAMELEN;
191         memcpy(&sp->mode, cp1, sizeof (u_int32_t));
192         cp1+= sizeof(u_int32_t);
193
194         /* Convert data from network byte order: */
195         sp->uid = ntohl(sp->uid);
196         sp->enter_status = ntohl(sp->enter_status);
197         sp->mode = ntohl(sp->mode);
198
199         /*
200          * Make sure the name contains only printable characters
201          * since we use control characters for cursor control
202          * between driver and player processes
203          */
204         sp->name[NAMELEN] = '\0';
205         for (cp1 = cp2 = sp->name; *cp1 != '\0'; cp1++)
206                 if (isprint(*cp1) || *cp1 == ' ')
207                         *cp2++ = *cp1;
208         *cp2 = '\0';
209
210         /* Make sure team name is valid */
211         if (sp->team < '1' || sp->team > '9')
212                 sp->team = ' ';
213
214         /* Tell the other end this server's hunt driver version: */
215         version = htonl((u_int32_t) HUNT_VERSION);
216         (void) write(sp->fd, &version, sizeof version);
217
218         if (sp->mode == C_MESSAGE) {
219                 /* The clients only wants to send a message: */
220                 sp->msglen = 0;
221                 sp->reading_msg = 1;
222                 return FALSE;
223         }
224
225         /* Use a stdio file descriptor from now on: */
226         conn = fdopen(sp->fd, "w");
227
228         /* The player is a monitor: */
229         if (sp->mode == C_MONITOR) {
230                 if (conf_monitor && End_monitor < &Monitor[MAXMON]) {
231                         pp = End_monitor++;
232                         if (sp->team == ' ')
233                                 sp->team = '*';
234                 } else {
235                         /* Too many monitors */
236                         fprintf(conn, "Too many monitors\n");
237                         fflush(conn);
238                         logx(LOG_NOTICE, "too many monitors");
239                         goto close_it;
240                 }
241
242         /* The player is a normal hunter: */
243         } else {
244                 if (End_player < &Player[MAXPL])
245                         pp = End_player++;
246                 else {
247                         fprintf(conn, "Too many players\n");
248                         fflush(conn);
249                         /* Too many players */
250                         logx(LOG_NOTICE, "too many players");
251                         goto close_it;
252                 }
253         }
254
255         /* Find the player's running scorecard */
256         pp->p_ident = get_ident(&sp->source, sp->sourcelen, sp->uid, 
257             sp->name, sp->team);
258         pp->p_output = conn;
259         pp->p_death[0] = '\0';
260         pp->p_fd = sp->fd;
261
262         /* No idea where the player starts: */
263         pp->p_y = 0;
264         pp->p_x = 0;
265
266         /* Mode-specific initialisation: */
267         if (sp->mode == C_MONITOR)
268                 stmonitor(pp);
269         else
270                 stplayer(pp, sp->enter_status);
271
272         /* And, they're off! Caller should remove and free sp. */
273         return TRUE;
274
275 error:
276         if (len < 0) 
277                 logit(LOG_WARNING, "read");
278         else
279                 logx(LOG_WARNING, "lost connection to new client");
280
281 close_it:
282         /* Destroy the spawn */
283         *sp->prevnext = sp->next;
284         if (sp->next) sp->next->prevnext = sp->prevnext;
285         FD_CLR(sp->fd, &Fds_mask);
286         close(sp->fd);
287         free(sp);
288         return FALSE;
289 }
290
291 /* Start a monitor: */
292 static void
293 stmonitor(pp)
294         PLAYER  *pp;
295 {
296
297         /* Monitors get to see the entire maze: */
298         memcpy(pp->p_maze, Maze, sizeof pp->p_maze);
299         drawmaze(pp);
300
301         /* Put the monitor's name near the bottom right on all screens: */
302         outyx(ALL_PLAYERS, 
303                 STAT_MON_ROW + 1 + (pp - Monitor), STAT_NAME_COL,
304                 "%5.5s%c%-10.10s %c", " ", 
305                 stat_char(pp), pp->p_ident->i_name, pp->p_ident->i_team);
306
307         /* Ready the monitor: */
308         sendcom(pp, REFRESH);
309         sendcom(pp, READY, 0);
310         flush(pp);
311 }
312
313 /* Start a player: */
314 static void
315 stplayer(newpp, enter_status)
316         PLAYER  *newpp;
317         int     enter_status;
318 {
319         int     x, y;
320         PLAYER  *pp;
321         int len;
322
323         Nplayer++;
324
325         for (y = 0; y < UBOUND; y++)
326                 for (x = 0; x < WIDTH; x++)
327                         newpp->p_maze[y][x] = Maze[y][x];
328         for (     ; y < DBOUND; y++) {
329                 for (x = 0; x < LBOUND; x++)
330                         newpp->p_maze[y][x] = Maze[y][x];
331                 for (     ; x < RBOUND; x++)
332                         newpp->p_maze[y][x] = SPACE;
333                 for (     ; x < WIDTH;  x++)
334                         newpp->p_maze[y][x] = Maze[y][x];
335         }
336         for (     ; y < HEIGHT; y++)
337                 for (x = 0; x < WIDTH; x++)
338                         newpp->p_maze[y][x] = Maze[y][x];
339
340         /* Drop the new player somewhere in the maze: */
341         do {
342                 x = rand_num(WIDTH - 1) + 1;
343                 y = rand_num(HEIGHT - 1) + 1;
344         } while (Maze[y][x] != SPACE);
345         newpp->p_over = SPACE;
346         newpp->p_x = x;
347         newpp->p_y = y;
348         newpp->p_undershot = FALSE;
349
350         /* Send them flying if needed */
351         if (enter_status == Q_FLY && conf_fly) {
352                 newpp->p_flying = rand_num(conf_flytime);
353                 newpp->p_flyx = 2 * rand_num(conf_flystep + 1) - conf_flystep;
354                 newpp->p_flyy = 2 * rand_num(conf_flystep + 1) - conf_flystep;
355                 newpp->p_face = FLYER;
356         } else {
357                 newpp->p_flying = -1;
358                 newpp->p_face = rand_dir();
359         }
360
361         /* Initialize the new player's attributes: */
362         newpp->p_damage = 0;
363         newpp->p_damcap = conf_maxdam;
364         newpp->p_nchar = 0;
365         newpp->p_ncount = 0;
366         newpp->p_nexec = 0;
367         newpp->p_ammo = conf_ishots;
368         newpp->p_nboots = 0;
369
370         /* Decide on what cloak/scan status to enter with */
371         if (enter_status == Q_SCAN && conf_scan) {
372                 newpp->p_scan = conf_scanlen * Nplayer;
373                 newpp->p_cloak = 0;
374         } else if (conf_cloak) {
375                 newpp->p_scan = 0;
376                 newpp->p_cloak = conf_cloaklen;
377         } else {
378                 newpp->p_scan = 0;
379                 newpp->p_cloak = 0;
380         }
381         newpp->p_ncshot = 0;
382
383         /*
384          * For each new player, place a large mine and
385          * a small mine somewhere in the maze:
386          */
387         do {
388                 x = rand_num(WIDTH - 1) + 1;
389                 y = rand_num(HEIGHT - 1) + 1;
390         } while (Maze[y][x] != SPACE);
391         Maze[y][x] = GMINE;
392         for (pp = Monitor; pp < End_monitor; pp++)
393                 check(pp, y, x);
394
395         do {
396                 x = rand_num(WIDTH - 1) + 1;
397                 y = rand_num(HEIGHT - 1) + 1;
398         } while (Maze[y][x] != SPACE);
399         Maze[y][x] = MINE;
400         for (pp = Monitor; pp < End_monitor; pp++)
401                 check(pp, y, x);
402
403         /* Create a score line for the new player: */
404         (void) snprintf(Buf, sizeof Buf, "%5.2f%c%-10.10s %c", 
405                 newpp->p_ident->i_score, stat_char(newpp), 
406                 newpp->p_ident->i_name, newpp->p_ident->i_team);
407         len = strlen(Buf);
408         y = STAT_PLAY_ROW + 1 + (newpp - Player);
409         for (pp = Player; pp < End_player; pp++) {
410                 if (pp != newpp) {
411                         /* Give everyone a few more shots: */
412                         pp->p_ammo += conf_nshots;
413                         newpp->p_ammo += conf_nshots;
414                         outyx(pp, y, STAT_NAME_COL, Buf, len);
415                         ammo_update(pp);
416                 }
417         }
418         for (pp = Monitor; pp < End_monitor; pp++)
419                 outyx(pp, y, STAT_NAME_COL, Buf, len);
420
421         /* Show the new player what they can see and where they are: */
422         drawmaze(newpp);
423         drawplayer(newpp, TRUE);
424         look(newpp);
425
426         /* Make sure that the position they enter in will be erased: */
427         if (enter_status == Q_FLY && conf_fly)
428                 showexpl(newpp->p_y, newpp->p_x, FLYER);
429
430         /* Ready the new player: */
431         sendcom(newpp, REFRESH);
432         sendcom(newpp, READY, 0);
433         flush(newpp);
434 }
435
436 /*
437  * rand_dir:
438  *      Return a random direction
439  */
440 int
441 rand_dir()
442 {
443         switch (rand_num(4)) {
444           case 0:
445                 return LEFTS;
446           case 1:
447                 return RIGHT;
448           case 2:
449                 return BELOW;
450           case 3:
451                 return ABOVE;
452         }
453         /* NOTREACHED */
454         return(-1);
455 }
456
457 /*
458  * get_ident:
459  *      Get the score structure of a player
460  */
461 static IDENT *
462 get_ident(sa, salen, uid, name, team)
463         struct sockaddr *sa;
464         int     salen __unused;
465         uid_t   uid;
466         char    *name;
467         char    team;
468 {
469         IDENT           *ip;
470         static IDENT    punt;
471         u_int32_t       machine;
472
473         if (sa->sa_family == AF_INET)
474                 machine = ntohl((u_long)((struct sockaddr_in *)sa)->sin_addr.s_addr);
475         else
476                 machine = 0;
477
478         for (ip = Scores; ip != NULL; ip = ip->i_next)
479                 if (ip->i_machine == machine
480                 &&  ip->i_uid == uid
481                 /* &&  ip->i_team == team */
482                 &&  strncmp(ip->i_name, name, NAMELEN) == 0)
483                         break;
484
485         if (ip != NULL) {
486                 if (ip->i_team != team) {
487                         logx(LOG_INFO, "player %s %s team %c",
488                                 name,
489                                 team == ' ' ? "left" : ip->i_team == ' ' ? 
490                                         "joined" : "changed to",
491                                 team == ' ' ? ip->i_team : team);
492                         ip->i_team = team;
493                 }
494                 if (ip->i_entries < conf_scoredecay)
495                         ip->i_entries++;
496                 else
497                         ip->i_kills = (ip->i_kills * (conf_scoredecay - 1))
498                                 / conf_scoredecay;
499                 ip->i_score = ip->i_kills / (double) ip->i_entries;
500         }
501         else {
502                 /* Alloc new entry -- it is released in clear_scores() */
503                 ip = (IDENT *) malloc(sizeof (IDENT));
504                 if (ip == NULL) {
505                         logit(LOG_ERR, "malloc");
506                         /* Fourth down, time to punt */
507                         ip = &punt;
508                 }
509                 ip->i_machine = machine;
510                 ip->i_team = team;
511                 ip->i_uid = uid;
512                 strlcpy(ip->i_name, name, sizeof ip->i_name);
513                 ip->i_kills = 0;
514                 ip->i_entries = 1;
515                 ip->i_score = 0;
516                 ip->i_absorbed = 0;
517                 ip->i_faced = 0;
518                 ip->i_shot = 0;
519                 ip->i_robbed = 0;
520                 ip->i_slime = 0;
521                 ip->i_missed = 0;
522                 ip->i_ducked = 0;
523                 ip->i_gkills = ip->i_bkills = ip->i_deaths = 0;
524                 ip->i_stillb = ip->i_saved = 0;
525                 ip->i_next = Scores;
526                 Scores = ip;
527
528                 logx(LOG_INFO, "new player: %s%s%c%s",
529                         name, 
530                         team == ' ' ? "" : " (team ",
531                         team,
532                         team == ' ' ? "" : ")");
533         }
534
535         return ip;
536 }
537
538 void
539 answer_info(fp)
540         FILE *fp;
541 {
542         struct spawn *sp;
543         char buf[128];
544         const char *bf;
545         struct sockaddr_in *sa;
546
547         if (Spawn == NULL)
548                 return;
549         fprintf(fp, "\nSpawning connections:\n");
550         for (sp = Spawn; sp; sp = sp->next) {
551                 sa = (struct sockaddr_in *)&sp->source;
552                 bf = inet_ntop(AF_INET, &sa->sin_addr, buf, sizeof buf);
553                 if (!bf)  {
554                         logit(LOG_WARNING, "inet_ntop");
555                         bf = "?";
556                 }
557                 fprintf(fp, "fd %d: state %d, from %s:%d\n",
558                         sp->fd, sp->inlen + (sp->reading_msg ? sp->msglen : 0),
559                         bf, sa->sin_port);
560         }
561 }