/* Copyright (c) 1993 Stephen F. White */ #include "cool.h" #include "string.h" #include "netio.h" #include "buf.h" #include "netio_private.h" #include "servers.h" int server_running; Player *players = 0; short yo_port; short player_port = PLAYER_PORT; SOCKET yo_sock; char *progfname; char *dumpfname; int max_age = MAX_AGE; int max_ticks = MAX_TICKS; static SOCKET player_sock; static int nfds; static int compiler = 0; /* flag: command line compiler mode */ static int dumper = 0; /* flag: dump flatfile mode */ static int do_init = 0; /* flag: call init() in new objects */ static void init_fdsets (fd_set * inputfds, fd_set * outputfds, struct timeval *timeout); static void process_fdsets (fd_set * inputfds, fd_set * outputfds); static void init_signals (void); static void close_sockets (void); static SOCKET server_socket (int port, int type); static void run_server (void); static void server_input (void); static int player_input (Player * p); static Player *new_player (void); static Player *init_player (SOCKET psock); static void set_nonblock (SOCKET fd); static int do_options (int argc, char *argv[]); static long safe_gethostbyname(const char *hostname, int timeout); static const char *shutdown_message = "*** The server has been shut down. ***\r\n"; typedef void (*PFVS) (int); void sighandler (int sig); static int do_options (int argc, char *argv[]) { int i, j, done_option, newargc = 0; for (i = 0; i < argc; i++) { if (argv[i][0] == '-') { done_option = 0; for (j = 1; j < (int)strlen (argv[i]) && !done_option; j++) { switch (argv[i][j]) { case 'p': promiscuous = 1; break; case 'c': corefile = 1; break; case 'i': do_init = 1; break; case 'f': compiler = 1; if (i >= argc - 1) { return 0; } progfname = argv[++i]; done_option = 1; break; case 'd': dumper = 1; if (i >= argc - 1) { return 0; } dumpfname = argv[++i]; done_option = 1; break; default: fprintf (stderr, "Unknown option: -%c\n", argv[i][j]); return 0; } /* for i */ } /* if */ } else { argv[newargc++] = argv[i]; } /* if */ } /* for j */ return newargc; } /* do_options() */ int main (int argc, char *argv[]) { argc = do_options (argc, argv); if (argc != 2) { fprintf (stderr, "Usage: %s [-pc -f progfname -d dumpfname] dbfile\n", argv[0]); return (1); } WIN32STARTUP if (compiler) { cmdline_compile (argv[1], progfname, do_init); WIN32CLEANUP return (0); } else if (dumper) { write_flatfile (argv[1], dumpfname); WIN32CLEANUP return (0); } if (init (argv[1], 1, 1)) { WIN32CLEANUP return (2); } writelog (); fprintf (stderr, "Initializing sockets.\n"); player_sock = server_socket (player_port, SOCK_STREAM); yo_sock = server_socket (yo_port, SOCK_DGRAM); if (player_sock == INVALID_SOCKET || yo_sock == INVALID_SOCKET) { WIN32CLEANUP return (3); } nfds = yo_sock + 1; writelog (); fprintf (stderr, "Connecting to remote servers.\n"); connect_to_servers (); init_signals (); /* * do it */ writelog (); fprintf (stderr, "Server initialized.\n"); run_server (); /* there 'tis */ close_sockets (); shutdown_server (); writelog (); fprintf (stderr, "Server exiting.\n"); WIN32CLEANUP return (0); } struct timeval cur_time; /* grabbed at start of loop */ static int totfds; static void run_server (void) { fd_set inputfds, outputfds; struct timeval timeout; /* reserve 9 fd's: 3 for stdio, 2 for server sockets, 3 for db, 1 for misc */ totfds = getdtablesize () - 9; server_running = 1; while (server_running) { gettimeofday (&cur_time, 0); timeout.tv_sec = 60; timeout.tv_usec = 0; queue_player_commands (cur_time, &timeout); process_queues (cur_time, &timeout); if (!server_running) { break; } init_fdsets (&inputfds, &outputfds, &timeout); if (select (nfds, &inputfds, &outputfds, (fd_set *) 0, &timeout) == SOCKET_ERROR) { if (GETERROR != EINTR) { writelog (); perror ("select failed"); server_running = 0; } } else { gettimeofday (&cur_time, 0); process_fdsets (&inputfds, &outputfds); } } /* while */ } static void init_signals (void) { signal (SIGINT, (PFVS) sighandler); signal (SIGTERM, (PFVS) sighandler); signal (SIGFPE, (PFVS) sighandler); signal (SIGSEGV, (PFVS) sighandler); #if !defined linux && !defined WIN32 signal (SIGEMT, (PFVS) sighandler); #endif #if !defined __CYGWIN__ && !defined WIN32 signal (SIGIOT, (PFVS) sighandler); #endif #ifndef WIN32 signal (SIGQUIT, (PFVS) sighandler); signal (SIGILL, (PFVS) sighandler); signal (SIGTRAP, (PFVS) sighandler); signal (SIGBUS, (PFVS) sighandler); signal (SIGSYS, (PFVS) sighandler); signal (SIGPIPE, SIG_IGN); /* ignore this */ signal (SIGURG, SIG_IGN); /* and this */ signal (SIGTSTP, SIG_IGN); signal (SIGCONT, SIG_IGN); signal (SIGALRM, SIG_IGN); signal (SIGWINCH, SIG_IGN); #endif } void sighandler (int sig) { char message[80]; int i; /* * restore all signals to default handling */ for (i = 1; i < NSIG; i++) { signal (i, SIG_DFL); } sprintf (message, "Caught signal %d", sig); panic (message); close_sockets (); if (corefile) { writelog (); fprintf (stderr, "Dumping corefile.\n"); WIN32CLEANUP (void) abort (); } writelog (); fprintf (stderr, "Server aborting.\n"); WIN32CLEANUP _exit (10); } static void init_fdsets (fd_set * inputfds, fd_set * outputfds, struct timeval *timeout) { Player *p; FD_ZERO (inputfds); FD_ZERO (outputfds); /* only wait for new players if there are free descriptors */ #ifndef WIN32 if (nfds < totfds) #endif { FD_SET (player_sock, inputfds); } FD_SET (yo_sock, inputfds); /* always wait for UDP input */ /* * always wait for input from players; wait for output if there's some * output to send */ for (p = players; p; p = p->next) { FD_SET (p->fd, inputfds); if (p->input.head && p->quota >= 0) { timeout->tv_sec = timeout->tv_usec = 0; } if (p->output.head || (p->outbound && !p->connected)) { FD_SET (p->fd, outputfds); } } } static void process_fdsets (fd_set * inputfds, fd_set * outputfds) { Player *p, *pnext; if (FD_ISSET (yo_sock, inputfds)) { /* if there's UDP yo input */ server_input (); /* handle it */ } if (FD_ISSET (player_sock, inputfds)) { /* new player wants connect */ if ((p = new_player ()) #ifndef WIN32 && p->fd >= nfds #endif ) { nfds = p->fd + 1; } } for (p = players; p; p = pnext) { /* for all players */ pnext = p->next; if (FD_ISSET(p->fd, outputfds)) { /* socket ready for writing */ if (p->outbound && !p->connected) { /* if it's outbound connect, */ p->connected = 1; /* it just connected */ fprintf(stderr, "Outbound connect #%d, fd %d to %s(%d) eventually succeeded\n", p->id, p->fd, addr_htoa(p->addr), p->port); new_connect(p->id, p->fd); } else { send_output(p->fd, &p->output); /* player output, send it */ } } if (FD_ISSET (p->fd, inputfds)) { /* player has input pending */ if (player_input(p) && !p->input.head) { /* handle it; if conn dies, */ if (p->connected) { disconnect_player (p->id); } remove_player (p); /* remove player */ } } } } static void server_input (void) { char buf[UDP_BLOCK_SIZE + 1]; int got; struct sockaddr_in from; int fromlen; fromlen = (int)sizeof (from); got = recvfrom (yo_sock, buf, UDP_BLOCK_SIZE, 0, (struct sockaddr *) &from, &fromlen); if (got <= 0) { return; } buf[got] = '\0'; server_command (&from, buf); } static Player *new_player (void) { SOCKET psock; struct sockaddr_in from; int fromlen; Player *p; fromlen = (int)sizeof (from); psock = accept (player_sock, (struct sockaddr *) &from, &fromlen); if (psock == INVALID_SOCKET) { return 0; } else { p = init_player (psock); p->connected = 0; p->addr = ntohl (from.sin_addr.s_addr); p->port = ntohs (from.sin_port); writelog (); fprintf (stderr, "Accepted player from %s(%d)\n", addr_htoa (p->addr), p->port); new_connect(p->id, p->fd); return p; } } void remove_player (Player * p) { if (p->connected) { writelog (); fprintf (stderr, "Player #%d disconnected from fd %d\n", p->id, p->fd); } else { writelog(); fprintf(stderr, "Player disconnected from fd %d, never connected\n", p->fd); } buf_free (&p->input); buf_free (&p->output); shutdown (p->fd, 2); close (p->fd); if (p->prev) { p->prev->next = p->next; } else { players = p->next; } if (p->next) { p->next->prev = p->prev; } FREE (p); } static Player *init_player (SOCKET psock) { Player *newp = MALLOC (Player, 1); set_nonblock (psock); newp->fd = psock; newp->connected = 0; newp->id = SYS_OBJ; newp->outbound = 0; buf_init (&newp->input); buf_init (&newp->output); newp->dangling_input = 0; newp->quota = MAX_CMDS * MSEC_PER_CMD; if (players) { players->prev = newp; } newp->next = players; newp->prev = 0; players = newp; return newp; } static SOCKET server_socket (int port, int type) { struct sockaddr_in to; SOCKET servsock; int opt; servsock = socket (AF_INET, type, 0); if (servsock == INVALID_SOCKET) { writelog (); perror ("Couldn't make server socket"); return -1; } set_nonblock (servsock); opt = 1; if (setsockopt (servsock, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof (opt)) == SOCKET_ERROR) { writelog (); perror ("Couldn't set socket options"); return -2; } to.sin_family = AF_INET; to.sin_addr.s_addr = INADDR_ANY; to.sin_port = htons (port); if (bind (servsock, (struct sockaddr *) &to, sizeof (to)) == SOCKET_ERROR) { writelog (); fprintf (stderr, "%s port %d: ", (type == SOCK_STREAM ? "TCP" : "UDP"), port); perror ("couldn't bind()"); close (servsock); return -3; } if (type == SOCK_STREAM) { if (listen (servsock, 5) == SOCKET_ERROR) { perror ("Couldn't listen()"); return -4; } } return servsock; } void send_output (SOCKET fd, Buf * output) { Line *line, *next; int len, written; for (line = output->head; line; line = next) { next = line->next; len = line->str->len - (line->ptr - line->str->str); written = send (fd, line->ptr, len, 0); if (written < 0) { /* error, including EWOULDBLOCK */ break; } else if (written < len) { line->ptr += written; break; } else { buf_delhead (output); } } } static void set_nonblock (SOCKET fd) { char buf[128]; #ifdef WIN32 unsigned long flags = 1; ioctlsocket (fd, FIONBIO, &flags); #else int flags; flags = fcntl (fd, F_GETFL); if (flags < 0 || fcntl (fd, F_SETFL, flags | O_NONBLOCK) == -1) { sprintf (buf, "Couldn't set descriptor %d nonblocking", fd); writelog (); perror (buf); } #endif /* WIN32 */ } static String *grab_input (SOCKET fd, char **dangling_input) { char buf[BLOCK_SIZE + 1]; int got; String *str; got = recv (fd, buf, BLOCK_SIZE, 0); if (got <= 0) { return 0; } str = string_new (0); if (*dangling_input) { /* some input left from last time */ str = string_cat (str, *dangling_input); /* prepend it */ FREE (*dangling_input); *dangling_input = 0; } do { buf[got] = '\0'; str = string_cat (str, buf); got = recv (fd, buf, BLOCK_SIZE, 0); } while (got > 0); return str; } static int player_input (Player * p) { String *str, *cmd; char *q, *r; if (!(str = grab_input (p->fd, &p->dangling_input))) { return -1; } for (q = r = str->str; r < str->str + str->len; r++) { if (*r == '\n') { /* save command */ *q = '\0'; cmd = string_new (0); cmd = string_cat (cmd, str->str); buf_add (&p->input, cmd); q = str->str; /* restart at beginning of buf */ } else if (*r == '\t') { /* change tabs to spaces */ *q++ = ' '; } else if (*r == '\b') { /* Handle visible ^h's */ q--; } else if (isascii (*r) && isprint ((int)*r)) { /* store printable */ *q++ = *r; } } if (q > str->str) { /* there's half a command here */ *q = '\0'; /* terminate it */ p->dangling_input = str_dup (str->str); /* copy it */ } string_free (str); return 0; } static void close_sockets (void) { Player *p, *pnext; for (p = players; p; p = pnext) { pnext = p->next; send (p->fd, shutdown_message, strlen (shutdown_message), 0); (void) shutdown (p->fd, 2); close (p->fd); } close (player_sock); writelog (); fprintf (stderr, "Disconnecting from remote servers.\n"); disconnect_from_servers (); close (yo_sock); } /* * safe_gethostbyname(hostname, timeout) * * Do a gethostbyname(), with a timer set to expire in _timeout_ seconds */ long safe_gethostbyname(const char *hostname, int timeout) { struct hostent *h = 0; #ifndef WIN32 alarm(timeout); #endif h = gethostbyname(hostname); if (h) { return ntohl(*( (unsigned long *) h->h_addr_list[0])); } else { return 0; } } /* * net_open_connect(id, hostname, port) */ SOCKET net_open_connect(Playerid id, const char *hostname, short port) { SOCKET newsock; struct sockaddr_in dest; Player *p, *pnext; int err; long addr = safe_gethostbyname(hostname, OUTBOUND_TIMEOUT); if (!addr) { return -1; } for (p = players; p; p = pnext) { pnext = p->next; if (p->id == id) { disconnect_player(p->id); remove_player(p); } } newsock = socket(AF_INET, SOCK_STREAM, 0); if (newsock == INVALID_SOCKET) { return -2; } if (newsock >= nfds) { nfds = newsock + 1; } p = init_player(newsock); p->addr = addr; p->port = port; p->id = id; p->outbound = 1; dest.sin_family = AF_INET; dest.sin_addr.s_addr = htonl(addr); dest.sin_port = htons(port); writelog(); fprintf(stderr, "Outbound connect #%d, fd %d to %s/%s(%d)", id, newsock, hostname, addr_htoa(p->addr), p->port); err = connect(newsock, (struct sockaddr *) &dest, sizeof(dest)); if (err == SOCKET_ERROR && GETERROR != EINPROGRESS && GETERROR != EWOULDBLOCK) { perror("failed: "); return -3; } else if (err == SOCKET_ERROR && (GETERROR == EINPROGRESS || GETERROR == EWOULDBLOCK)) { p->connected = 0; fprintf(stderr, " in progress\n"); return -4; } else { p->connected = 1; fprintf(stderr, " succeeded\n"); new_connect(p->id, p->fd); return newsock; } }