/* Copyright (c) 1993 Stephen F. White */ #include <signal.h> #include <stdio.h> #ifdef SYSV #include <string.h> #else #include <strings.h> #endif #include <sys/types.h> #include <sys/uio.h> #include <sys/file.h> #include <sys/time.h> #include <sys/resource.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <fcntl.h> #include <sys/errno.h> #include <ctype.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include "config.h" #include "string.h" #include "netio.h" #include "buf.h" #include "netio_private.h" #include "sys_proto.h" #include "socket_proto.h" #include "servers.h" int server_running; Player *players = 0; short yo_port; short player_port = PLAYER_PORT; int yo_sock; char *progfname; char *dumpfname; int max_age = MAX_AGE; int max_ticks = MAX_TICKS; static int 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 int server_socket(int port, int type); static void run_server(void); static void server_input(void); static int player_input(Player *p); static void send_output(int fd, Buf *output); static Player *new_player(void); static Player *init_player(int psock); static void set_nonblock(int s); static int do_options(int argc, char *argv[]); static const char *shutdown_message = "*** The server has been shut down. ***\r\n"; char *welcome; typedef void (*PFV)(); void sighandler(int sig, int code); 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 < 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() */ void 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]); exit (1); } if (compiler) { cmdline_compile(argv[1], progfname, do_init); exit(0); } else if (dumper) { write_flatfile(argv[1], dumpfname); exit(0); } if (init(argv[1], 1, 1)) { exit(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 < 0 || yo_sock < 0) { exit(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"); exit (0); } #ifdef PROTO extern void gettimeofday(struct timeval *, struct timezone *); #endif /* PROTO */ 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) < 0) { if (errno != 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, (PFV) sighandler); signal(SIGQUIT, (PFV) sighandler); signal(SIGILL, (PFV) sighandler); signal(SIGTRAP, (PFV) sighandler); signal(SIGIOT, (PFV) sighandler); signal(SIGFPE, (PFV) sighandler); #ifndef linux signal(SIGBUS, (PFV) sighandler); signal(SIGEMT, (PFV) sighandler); signal(SIGSYS, (PFV) sighandler); #endif /* !linux */ signal(SIGSEGV, (PFV) sighandler); signal(SIGPIPE, SIG_IGN); /* ignore this */ signal(SIGTERM, (PFV) sighandler); signal(SIGURG, SIG_IGN); /* and this */ signal(SIGTSTP, SIG_IGN); signal(SIGCONT, SIG_IGN); signal(SIGWINCH, (PFV) SIG_IGN); } void sighandler(int sig, int code) { char message[80]; int i; /* * restore all signals to default handling */ for (i = 0; i < NSIG; i++) { signal(i, SIG_DFL); } sprintf (message, "Caught signal %d, code %d", sig, code); panic(message); close_sockets(); if (corefile) { writelog(); fprintf(stderr, "Dumping corefile.\n"); (void) abort(); } writelog(); fprintf(stderr, "Server aborting.\n"); _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 */ if (nfds < totfds) { 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->parsing && p->quota >= 0) { timeout->tv_sec = timeout->tv_usec = 0; } if (p->output.head) { 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()) && p->fd >= nfds) { nfds = p->fd + 1; } } for (p = players; p; p = pnext) { /* for all players */ pnext = p->next; if (FD_ISSET(p->fd, outputfds)) { /* player has output pending */ send_output(p->fd, &p->output); /* send it */ } if (FD_ISSET(p->fd, inputfds)) { /* player has input pending */ if (player_input(p)) { /* 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 = 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) { int psock; struct sockaddr_in from; int fromlen; Player *p; fromlen = sizeof(from); psock = accept(player_sock, (struct sockaddr *) &from, &fromlen); if (psock < 0) { 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); write(psock, welcome, strlen(welcome)); return p; } } void remove_player(Player *p) { if (p->connected) { if (p->isprogramming) { fclose(p->progfile); unlink(p->progfilename); FREE(p->progwhat); } writelog(); fprintf(stderr, "Player #%d disconnected from fd %d\n", p->id, 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(int psock) { Player *newp = MALLOC(Player, 1); set_nonblock(psock); newp->fd = psock; newp->connected = 0; newp->isprogramming = 0; newp->parsing = 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 int server_socket(int port, int type) { struct sockaddr_in to; int servsock, opt; servsock = socket(AF_INET, type, 0); if (servsock < 0) { 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)) < 0) { 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)) < 0) { 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) < 0) { perror ("Couldn't listen()"); return -4; } } return servsock; } static void send_output(int 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 = write(fd, line->ptr, len); if (written < 0) { /* error, including EWOULDBLOCK */ break; } else if (written < len) { line->ptr += written; break; } else { buf_delhead(output); } } } static void set_nonblock(int s) { char buf[128]; #ifndef linux if (fcntl(s, F_SETFL, FNDELAY) == -1) { #else /* linux */ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { #endif sprintf(buf, "Couldn't set descriptor %d nonblocking", s); writelog(); perror(buf); } } static String * grab_input(int fd, char **dangling_input) { char buf[BLOCK_SIZE + 1]; int got; String *str; got = read (fd, buf, BLOCK_SIZE); 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 = read (fd, buf, BLOCK_SIZE); } 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 (isascii (*r) && isprint (*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; write(p->fd, shutdown_message, strlen(shutdown_message)); (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); }