/* 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;
}
}