/* Copyright 1989, 1990 by James Aspnes, David Applegate, and Bennet Yee */ /* See the file COPYING for distribution information */ /* Stripped-down TinyMUD interface */ #include "os.h" #include "db.h" #include "config.h" #include "interface.h" #define MAX_OUTPUT (16 * MAX_STRLEN) extern int errno; int shutdown_flag = 0; static const char *prompt_message = "TinyMUD login: "; static const char *connect_fail = "Invalid login.\n"; static const char *connect_success = "Connected.\n"; static const char *flushed_message = "<Output Flushed>\n"; static const char *shutdown_message = "Going down - Bye\n"; struct text_block { int nchars; struct text_block *nxt; char *start; char *buf; }; struct text_queue { struct text_block *head; struct text_block **tail; }; struct descriptor_data { SOCKET descriptor; int connected; datum player; int output_size; struct text_queue output; struct text_queue input; char *raw_input; char *raw_input_at; int quota; struct sockaddr_in address; /* added 3/6/90 SCG */ struct descriptor_data *next; struct descriptor_data **prev; } *descriptor_list = 0; static SOCKET sock; static int ndescriptors = 0; static void process_commands (void); static void shovechars (int port); static void shutdownsock (struct descriptor_data *d); static struct descriptor_data *initializesock (SOCKET s, struct sockaddr_in *a); static void make_nonblocking (SOCKET s); static void freeqs (struct descriptor_data *d); static void check_connect (struct descriptor_data *d, const char *msg); static void close_sockets (void); static void set_signals (void); static struct descriptor_data *new_connection (SOCKET sock); static void parse_connect (const char *msg, char *user, char *pass); static int do_command (struct descriptor_data *d, char *command); static int make_socket (int); static int queue_string (struct descriptor_data *, const char *); static int queue_write (struct descriptor_data *, const char *, int); static int process_output (struct descriptor_data *d); static int process_input (struct descriptor_data *d); static void bailout (int); static const char *addrout (struct in_addr a); #define MALLOC(result, type, number) do { \ if (!((result) = (type *) malloc ((number) * sizeof (type)))) \ panic("Out of memory"); \ } while (0) #define FREE(x) (free((void *) x)) int main (int argc, char **argv) { if (argc < 3) { fprintf (stderr, "Usage: %s infile dumpfile [port]\n", *argv); return (1); } if (init_game (argv[1], argv[2]) < 0) { fprintf (stderr, "Couldn't load %s!\n", argv[1]); return (2); } set_signals (); WIN32STARTUP /* go do it */ shovechars (argc >= 4 ? atoi (argv[3]) : TINYPORT); close_sockets (); dump_database (); WIN32CLEANUP return (0); } static void set_signals (void) { int dump_status (void); /* we don't care about SIGPIPE, we notice it in select() and write() */ #if !defined WIN32 signal (SIGPIPE, SIG_IGN); #endif /* standard termination signals */ signal (SIGINT, bailout); signal (SIGTERM, bailout); #if defined WIN32 signal (SIGBREAK, bailout); #endif /* catch these because we might as well */ #if !defined WIN32 signal (SIGQUIT, bailout); #endif signal (SIGILL, bailout); #if !defined __CYGWIN__ && !defined WIN32 signal (SIGIOT, bailout); #endif signal (SIGFPE, bailout); signal (SIGSEGV, bailout); #if !defined WIN32 #if !defined linux signal (SIGEMT, bailout); #endif signal (SIGBUS, bailout); signal (SIGSYS, bailout); signal (SIGXCPU, bailout); signal (SIGXFSZ, bailout); signal (SIGVTALRM, bailout); signal (SIGUSR2, bailout); #endif } void notify (datum player, const char *msg) { struct descriptor_data *d; for (d = descriptor_list; d; d = d->next) { if (d->connected && d->player == player) { queue_string (d, msg); queue_write (d, "\n", 1); } } } static struct timeval timeval_sub (struct timeval now, struct timeval then) { now.tv_sec -= then.tv_sec; now.tv_usec -= then.tv_usec; if (now.tv_usec < 0) { now.tv_usec += 1000000; now.tv_sec--; } return now; } static int msec_diff (struct timeval now, struct timeval then) { return ((now.tv_sec - then.tv_sec) * 1000 + (now.tv_usec - then.tv_usec) / 1000); } static struct timeval msec_add (struct timeval t, int x) { t.tv_sec += x / 1000; t.tv_usec += (x % 1000) * 1000; if (t.tv_usec >= 1000000) { t.tv_sec += t.tv_usec / 1000000; t.tv_usec = t.tv_usec % 1000000; } return t; } static struct timeval update_quotas (struct timeval last, struct timeval current) { int nslices; struct descriptor_data *d; nslices = msec_diff (current, last) / COMMAND_TIME_MSEC; if (nslices > 0) { for (d = descriptor_list; d; d = d->next) { d->quota += COMMANDS_PER_TIME * nslices; if (d->quota > COMMAND_BURST_SIZE) d->quota = COMMAND_BURST_SIZE; } } return msec_add (last, nslices * COMMAND_TIME_MSEC); } static void shovechars (int port) { fd_set input_set, output_set; long now; unsigned long next_tick; struct timeval last_slice, current_time; struct timeval next_slice; struct timeval timeout, slice_timeout; int maxd; struct descriptor_data *d, *dnext; struct descriptor_data *newd; int avail_descriptors; sock = make_socket (port); maxd = sock + 1; gettimeofday (&last_slice, (struct timezone *) 0); avail_descriptors = getdtablesize () - 4; while (shutdown_flag == 0) { gettimeofday (¤t_time, (struct timezone *) 0); last_slice = update_quotas (last_slice, current_time); process_commands (); if (shutdown_flag) break; /* compute timeout */ timeout.tv_sec = 1000; timeout.tv_usec = 0; next_slice = msec_add (last_slice, COMMAND_TIME_MSEC); slice_timeout = timeval_sub (next_slice, current_time); /* check for pending events */ next_tick = next_event_time (); if (next_tick != 0) { if (next_tick > (unsigned long)next_slice.tv_sec) { timeout.tv_sec = next_tick - current_time.tv_sec; } else { timeout = slice_timeout; } } FD_ZERO (&input_set); FD_ZERO (&output_set); if (ndescriptors < avail_descriptors) FD_SET (sock, &input_set); for (d = descriptor_list; d; d = d->next) { if (d->input.head) timeout = slice_timeout; else FD_SET (d->descriptor, &input_set); if (d->output.head) FD_SET (d->descriptor, &output_set); } if (select (maxd, &input_set, &output_set, (fd_set *) 0, &timeout) == SOCKET_ERROR) { if (GETERROR != EINTR) { perror ("select"); return; } } else { (void) time (&now); if (FD_ISSET (sock, &input_set)) { if (!(newd = new_connection (sock))) { if (GETERROR && GETERROR != EINTR && GETERROR != EMFILE) { perror ("new_connection"); return; } } else { if (newd->descriptor >= maxd) maxd = newd->descriptor + 1; } } for (d = descriptor_list; d; d = dnext) { dnext = d->next; if (FD_ISSET (d->descriptor, &input_set)) { if (!process_input (d)) { shutdownsock (d); continue; } } if (FD_ISSET (d->descriptor, &output_set)) { if (!process_output (d)) { shutdownsock (d); } } } } check_events (); } } static struct descriptor_data *new_connection (SOCKET sock) { SOCKET newsock; struct sockaddr_in addr; int addr_len; addr_len = sizeof (addr); newsock = accept (sock, (struct sockaddr *) &addr, &addr_len); if (newsock == INVALID_SOCKET) { return 0; } else { fprintf (stderr, "ACCEPT from %s(%d) on descriptor %d\n", addrout (addr.sin_addr), ntohs (addr.sin_port), newsock); return initializesock (newsock, &addr); } } static void shutdownsock (struct descriptor_data *d) { if (d->connected) { fprintf (stderr, "DISCONNECT descriptor %d player %ld\n", d->descriptor, d->player); disconnect_player (d->player); } else { fprintf (stderr, "DISCONNECT descriptor %d never connected\n", d->descriptor); } shutdown (d->descriptor, 2); close (d->descriptor); freeqs (d); *d->prev = d->next; if (d->next) d->next->prev = d->prev; FREE (d); ndescriptors--; } static struct descriptor_data *initializesock (SOCKET s, struct sockaddr_in *a) { struct descriptor_data *d; ndescriptors++; MALLOC (d, struct descriptor_data, 1); d->descriptor = s; d->connected = 0; make_nonblocking (s); d->output_size = 0; d->output.head = 0; d->output.tail = &d->output.head; d->input.head = 0; d->input.tail = &d->input.head; d->raw_input = 0; d->raw_input_at = 0; d->quota = COMMAND_BURST_SIZE; d->address = *a; /* added 5/3/90 SCG */ if (descriptor_list) descriptor_list->prev = &d->next; d->next = descriptor_list; d->prev = &descriptor_list; descriptor_list = d; queue_string (d, prompt_message); return d; } static const char *addrout (struct in_addr a) { #ifdef HOST_NAME struct hostent *he; // fix this up. JAL a = htonl (a.s_addr); he = gethostbyaddr ((const char *) &a, sizeof (a), AF_INET); if (he) return he->h_name; else return inet_ntoa (a); #else return inet_ntoa (a); #endif /* HOST_NAME */ } static int make_socket (int port) { SOCKET s; struct sockaddr_in server; int opt; s = socket (AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { perror ("creating stream socket"); WIN32CLEANUP exit (3); } opt = 1; if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof (opt)) == SOCKET_ERROR) { perror ("setsockopt"); WIN32CLEANUP exit (1); } make_nonblocking (s); server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons (port); if (bind (s, (struct sockaddr *) &server, sizeof (server))) { perror ("binding stream socket"); close (s); WIN32CLEANUP exit (4); } listen (s, 5); return s; } static struct text_block *make_text_block (const char *s, int n) { struct text_block *p; MALLOC (p, struct text_block, 1); MALLOC (p->buf, char, n); bcopy (s, p->buf, n); p->nchars = n; p->start = p->buf; p->nxt = 0; return p; } static void free_text_block (struct text_block *t) { FREE (t->buf); FREE ((char *) t); } static void add_to_queue (struct text_queue *q, const char *b, int n) { struct text_block *p; if (n == 0) return; p = make_text_block (b, n); p->nxt = 0; *q->tail = p; q->tail = &p->nxt; } static int flush_queue (struct text_queue *q, int n) { struct text_block *p; int really_flushed = 0; n += strlen (flushed_message); while (n > 0 && (p = q->head)) { n -= p->nchars; really_flushed += p->nchars; q->head = p->nxt; free_text_block (p); } p = make_text_block (flushed_message, strlen (flushed_message)); p->nxt = q->head; q->head = p; if (!p->nxt) q->tail = &p->nxt; really_flushed -= p->nchars; return really_flushed; } static int queue_write (struct descriptor_data *d, const char *b, int n) { int space; space = MAX_OUTPUT - d->output_size - n; if (space < 0) d->output_size -= flush_queue (&d->output, -space); add_to_queue (&d->output, b, n); d->output_size += n; return n; } static int queue_string (struct descriptor_data *d, const char *s) { return queue_write (d, s, strlen (s)); } static int process_output (struct descriptor_data *d) { struct text_block **qp, *cur; int cnt; for (qp = &d->output.head; cur = *qp;) { // :WARN: Assignment?! or Test - JAL cnt = send (d->descriptor, cur->start, cur->nchars, 0); if (cnt == SOCKET_ERROR) { if (GETERROR == EWOULDBLOCK) return 1; return 0; } d->output_size -= cnt; if (cnt == cur->nchars) { if (!cur->nxt) d->output.tail = qp; *qp = cur->nxt; free_text_block (cur); continue; /* do not adv ptr */ } cur->nchars -= cnt; cur->start += cnt; break; } return 1; } static void make_nonblocking (SOCKET s) { #ifdef WIN32 unsigned long flags = 1; if (ioctlsocket (s, FIONBIO, &flags) == SOCKET_ERROR) { #else if (fcntl (s, F_SETFL, FNDELAY) == SOCKET_ERROR) { #endif perror ("make_nonblocking: fcntl"); panic ("FNDELAY fcntl failed"); } } static void freeqs (struct descriptor_data *d) { struct text_block *cur, *next; cur = d->output.head; while (cur) { next = cur->nxt; free_text_block (cur); cur = next; } d->output.head = 0; d->output.tail = &d->output.head; cur = d->input.head; while (cur) { next = cur->nxt; free_text_block (cur); cur = next; } d->input.head = 0; d->input.tail = &d->input.head; if (d->raw_input) FREE (d->raw_input); d->raw_input = 0; d->raw_input_at = 0; } static void save_command (struct descriptor_data *d, const char *command) { add_to_queue (&d->input, command, strlen (command) + 1); } static int process_input (struct descriptor_data *d) { char buf[1024]; int got; char *p, *pend, *q, *qend; got = recv (d->descriptor, buf, sizeof buf, 0); if (got <= 0) return 0; if (!d->raw_input) { MALLOC (d->raw_input, char, MAX_COMMAND_LEN); d->raw_input_at = d->raw_input; } p = d->raw_input_at; pend = d->raw_input + MAX_COMMAND_LEN - 1; for (q = buf, qend = buf + got; q < qend; q++) { if (*q == '\n') { *p = '\0'; if (p > d->raw_input) save_command (d, d->raw_input); p = d->raw_input; } else if (p < pend && isascii (*q) && (isprint (*q) || *q == '\t')) { /* only allow printable ascii characters and TAB */ *p++ = *q; } } if (p > d->raw_input) { d->raw_input_at = p; } else { FREE (d->raw_input); d->raw_input = 0; d->raw_input_at = 0; } return 1; } static void process_commands (void) { int nprocessed; struct descriptor_data *d, *dnext; struct text_block *t; do { nprocessed = 0; for (d = descriptor_list; d; d = dnext) { dnext = d->next; if (d->quota > 0 && (t = d->input.head)) { d->quota--; nprocessed++; if (!do_command (d, t->start)) { shutdownsock (d); } else { d->input.head = t->nxt; if (!d->input.head) d->input.tail = &d->input.head; free_text_block (t); } } } } while (nprocessed > 0); } static int do_command (struct descriptor_data *d, char *command) { if (!strcmp (command, QUIT_COMMAND)) { return 0; } else { if (d->connected) { process_command (d->player, command); } else { check_connect (d, command); } } return 1; } static void check_connect (struct descriptor_data *d, const char *msg) { char user[MAX_COMMAND_LEN]; char password[MAX_COMMAND_LEN]; datum player; parse_connect (msg, user, password); player = connect_player (user, password); if (player == NOTHING) { queue_string (d, connect_fail); queue_string (d, prompt_message); fprintf (stderr, "FAILED CONNECT %s on descriptor %d\n", user, d->descriptor); } else { fprintf (stderr, "CONNECTED player %ld on descriptor %d\n", player, d->descriptor); d->connected = 1; d->player = player; queue_string (d, connect_success); } } static void parse_connect (const char *msg, char *user, char *pass) { char *p; while (*msg && isascii (*msg) && isspace (*msg)) msg++; p = user; while (*msg && isascii (*msg) && !isspace (*msg)) *p++ = *msg++; *p = '\0'; while (*msg && isascii (*msg) && isspace (*msg)) msg++; p = pass; while (*msg && isascii (*msg)) *p++ = *msg++; *p = '\0'; } static void close_sockets (void) { struct descriptor_data *d, *dnext; for (d = descriptor_list; d; d = dnext) { dnext = d->next; send (d->descriptor, shutdown_message, strlen (shutdown_message), 0); if (shutdown (d->descriptor, 2) == SOCKET_ERROR) perror ("shutdown"); close (d->descriptor); } close (sock); } void emergency_shutdown (void) { close_sockets (); WIN32CLEANUP } void force_disconnect (datum player) { struct descriptor_data *d; for (d = descriptor_list; d; d = d->next) { if (d->connected && d->player == player) { shutdownsock (d); return; } } } static void bailout (int sig) { char message[1024]; sprintf (message, "BAILOUT: caught signal %d", sig); panic (message); _exit (7); }