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