#include "os.h" #define TELOPTS #ifdef WIN32 #include "telnet.h" #else #include <arpa/telnet.h> #endif #include "interpret.h" #include "comm.h" #include "object.h" #include "config.h" #include "sent.h" /* main.c */ extern int d_flag; extern int port_number; extern void *xalloc(int size); extern char *string_copy(char *str); extern void debug_message(char *a, ...); /* * Interprocess communication interface to the backend. */ /* backend.c */ extern void logon(struct object *ob); /* simulate.c */ extern struct value *clone_object (char *str1); extern void free_sentence(struct sentence *p); extern void fatal(char *fmt, ...); /* interpret.c */ extern struct value *alloc_value(void); extern struct value *apply(char *fun, struct object *ob, struct value *arg); /* object.c */ extern int free_object(struct object *ob, char *from); extern void add_ref(struct object *ob, char *from); #define MAX_PLAYERS 40 static struct interactive *all_players[MAX_PLAYERS]; static fd_set readfds; static int nfds = 0; static SOCKET s; int num_player; /* comm1.c */ void prepare_ipc(void); void ipc_remove(void); void add_message(char *fmt, ...); int get_message(char *buff, int size); void remove_interactive(struct object *ob); int call_function_interactive(struct interactive *i, char *str); int set_call(struct object *ob, struct sentence *sent); void show_info_about(char *str, char *room, struct interactive *i); void remove_all_players(void); void set_prompt(char *str); void print_prompt(void); void set_snoop(struct object *me, struct object *you); static void new_player(int new_socket, struct sockaddr_in *addr, int len); static int telnet_neg(char *to, char *from); void prepare_ipc (void) { struct sockaddr_in sin; struct hostent *hp; int tmp; char host_name[100]; #ifdef WIN32 unsigned long flags = 1; #endif if (gethostname (host_name, sizeof host_name) == SOCKET_ERROR) { debug_message ("%s: Error %d\n", "gethostname", GETERROR); fatal ("Error in gethostname()\n"); } hp = gethostbyname (host_name); if (hp == 0) { (void) fprintf (stderr, "gethostbyname: unknown host.\n"); WIN32CLEANUP exit (1); } memset ((char *) &sin, '\0', sizeof sin); memcpy ((char *) &sin.sin_addr, hp->h_addr, hp->h_length); sin.sin_port = htons (port_number); sin.sin_family = hp->h_addrtype; sin.sin_addr.s_addr = INADDR_ANY; s = socket (hp->h_addrtype, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { debug_message ("%s: Error %d\n", "socket", GETERROR); WIN32CLEANUP abort (); } tmp = 1; if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *) &tmp, sizeof (tmp)) == SOCKET_ERROR) { debug_message ("%s: Error %d\n", "setsockopt", GETERROR); WIN32CLEANUP exit (1); } if (bind (s, (struct sockaddr *) &sin, sizeof sin) == SOCKET_ERROR) { if (GETERROR == EADDRINUSE) debug_message ("Socket already bound!\n"); else { debug_message ("%s: Error %d\n", "bind", GETERROR); WIN32CLEANUP abort (); } } if (listen (s, 5) == SOCKET_ERROR) { debug_message ("%s: Error %d\n", "listen", GETERROR); WIN32CLEANUP abort (); } tmp = 1; #ifdef WIN32 if (ioctlsocket (s, FIONBIO, &flags)) { #else if (fcntl (s, F_SETFL, FNDELAY) == -1) { #endif debug_message ("%s: Error %d\n", "ioctl socket FIONBIO", GETERROR); WIN32CLEANUP abort (); } #ifndef WIN32 signal (SIGPIPE, SIG_IGN); #endif } /* * This one is called when shutting down the MUD. */ void ipc_remove (void) { (void) printf ("Shutting down ipc...\n"); close (s); } /*VARARGS1*/ void add_message (char *fmt, ...) { va_list args; char buff[10000]; /* Kludgy! Hope this is enough ! */ char buff2[sizeof buff]; struct interactive *ip; int n, offset, chunk, length; int from, to; if (command_giver == 0) { debug_message ("command_giver == 0. Message:\n"); va_start (args, fmt); debug_message (fmt, args); va_end (args); return; } ip = command_giver->interactive; if (ip == 0) return; if (ip->out_portal) { buff[0] = ']'; va_start (args, fmt); vsprintf (buff + 1, fmt, args); va_end (args); } else { va_start (args, fmt); vsprintf (buff, fmt, args); va_end (args); } length = (int) strlen (buff); /* * Always check that your arrays are big enough ! :-) */ if (length > (int) sizeof buff) fatal ("To long message!\n"); if (ip->snoop_by) { struct object *save = command_giver; command_giver = ip->snoop_by->ob; add_message ("%%%s", buff); command_giver = save; } if (d_flag) debug_message ("[%s(%d)]: %s", command_giver->name, length, buff); /* * Insert CR after all NL. */ for (from = 0, to = 0; to < (int) sizeof buff2 && buff[from] != '\0';) { if (buff[from] == '\n') buff2[to++] = '\r'; buff2[to++] = buff[from++]; } buff2[to++] = '\0'; length = to - 1; /* * We split up the message into something smaller than the max size. */ for (offset = 0; length > 0; offset += chunk, length -= chunk) { chunk = length; if (chunk > MAX_SOCKET_PACKET_SIZE) chunk = MAX_SOCKET_PACKET_SIZE; if ((n = send (ip->socket, buff2 + offset, chunk, 0)) == SOCKET_ERROR) { if (GETERROR == EMSGSIZE) { debug_message ("comm1: write EMSGSIZE.\n"); return; } if (GETERROR == EINVAL) { debug_message ("comm1: write EINVAL.\n"); if (!ip->closing) remove_interactive (ip->ob); return; } if (GETERROR == ENETUNREACH) { debug_message ("comm1: write ENETUNREACH.\n"); if (!ip->closing) remove_interactive (ip->ob); return; } if (GETERROR == EHOSTUNREACH) { debug_message ("comm1: write EHOSTUNREACH.\n"); if (!ip->closing) remove_interactive (ip->ob); return; } if (GETERROR == EPIPE) { debug_message ("comm1: write EPIPE detected\n"); if (!ip->closing) remove_interactive (ip->ob); return; } if (GETERROR == EWOULDBLOCK) { debug_message ("comm1: write EWOULDBLOCK. Message discarded.\n"); return; } if (GETERROR == ECONNREFUSED) { debug_message ("comm1: write ECONNREFUSED.\n"); if (!ip->closing) remove_interactive (ip->ob); return; } debug_message ("%s: Error %d\n", "write", GETERROR); #ifdef DO_ABORT WIN32CLEANUP abort (); #endif return; } if (n != chunk) debug_message ("write socket: Size %d(%d) is too big !\n", chunk, n); return; } } /* * Get a message from any player. * If we get a message, set command_giver to the players object and * return true. * If we are interrupted, return false (no message yet). */ int get_message (char *buff, int size) { int i, res; static int last = -1; /* Last player number */ /* * Stay in this loop until we have a message from a player. */ while (1) { SOCKET new_socket; struct sockaddr_in addr; int length; struct timeval timeout; /* First, try to get a new player... */ length = sizeof addr; new_socket = accept (s, (struct sockaddr *)&addr, &length); if (new_socket != INVALID_SOCKET) { #ifdef WIN32 unsigned long flags = 1; if (ioctlsocket (new_socket, FIONBIO, &flags)) { #else if (fcntl (new_socket, F_SETFL, FNDELAY) == -1) { #endif debug_message ("%s: Error %d\n", "ioctl socket FIONBIO", GETERROR); #ifdef DO_ABORT WIN32CLEANUP abort (); #endif } new_player (new_socket, &addr, length); } else if (new_socket == INVALID_SOCKET && GETERROR != EWOULDBLOCK && GETERROR != EINTR) { debug_message ("%s: Error %d\n", "accept", GETERROR); #ifdef DO_ABORT WIN32CLEANUP abort (); #endif } nfds = 0; FD_ZERO (&readfds); FD_SET (s, &readfds); for (i = 0; i < MAX_PLAYERS; i++) { struct interactive *ip = all_players[i]; if (ip) { FD_SET (ip->socket, &readfds); #ifndef WIN32 if (ip->socket >= nfds) nfds = ip->socket + 1; #endif if (ip->out_portal) { FD_SET (ip->portal_socket, &readfds); #ifndef WIN32 if (ip->portal_socket >= nfds) nfds = ip->portal_socket + 1; #endif } } } timeout.tv_sec = 1; timeout.tv_usec = 0; res = select (nfds, &readfds, 0, 0, &timeout); if (res == SOCKET_ERROR) { if (GETERROR == EINTR) return 0; debug_message ("%s: Error %d\n", "select", GETERROR); WIN32CLEANUP abort (); } if (res == 0) return 0; i = last + 1; if (i > MAX_PLAYERS) i = 0; for (i = 0; i < MAX_PLAYERS; i++) { struct interactive *ip = all_players[i]; if (ip == 0) continue; if (ip->out_portal && FD_ISSET (ip->portal_socket, &readfds)) { int l; l = recv (ip->portal_socket, buff, size, 0); if (l == SOCKET_ERROR) continue; send (ip->socket, buff, l, 0); continue; } if (FD_ISSET (ip->socket, &readfds)) { char *p; int l, newline = 0; last = i; if ((l = recv (ip->socket, buff, size, 0)) == SOCKET_ERROR) { if (GETERROR == ENETUNREACH) { debug_message ("Net unreachable detected.\n"); remove_interactive (ip->ob); return 0; } if (GETERROR == EHOSTUNREACH) { debug_message ("Host unreachable detected.\n"); remove_interactive (ip->ob); return 0; } if (GETERROR == ETIMEDOUT) { debug_message ("Connection timed out detected.\n"); remove_interactive (ip->ob); return 0; } if (GETERROR == ECONNRESET) { debug_message ("Connection reset by peer detected.\n"); remove_interactive (ip->ob); return 0; } if (GETERROR == EWOULDBLOCK) { debug_message ("read would block socket %d!\n", ip->socket); continue; } if (GETERROR == EMSGSIZE) { debug_message ("read EMSGSIZE !\n"); continue; } debug_message ("%s: Error %d\n", "read", GETERROR); #ifdef DO_ABORT WIN32CLEANUP abort (); #endif } /* * If the data goes through a portal, send it, * but don't return any data. */ if (ip->out_portal) { send (ip->portal_socket, buff, l, 0); continue; } if (l == 0) { if (ip->closing) fatal ("Tried to read from closing socket.\n"); remove_interactive (ip->ob); return 0; } buff[l] = '\0'; if (p = strchr (buff, '\n')) { newline = 1; *p = '\0'; } if (p = strchr (buff, '\r')) *p = '\0'; strncat (ip->text, buff, sizeof ip->text); ip->text[sizeof ip->text - 1] = '\0'; if (ip->snoop_by && newline) { command_giver = ip->snoop_by->ob; add_message ("%% %s\n", ip->text); } command_giver = ip->ob; if (newline) { telnet_neg (buff, ip->text); ip->text[0] = '\0'; return 1; } last = -1; return 0; } } } } /* * Remove an interactive player immediately. */ void remove_interactive (struct object *ob) { struct object *save = command_giver; int i; for (i = 0; i < MAX_PLAYERS; i++) { if (all_players[i] != ob->interactive) continue; if (ob->interactive->closing) fatal ("Double call to remove_interactive()\n"); ob->interactive->closing = 1; if (ob->interactive->snoop_by) { ob->interactive->snoop_by->snoop_on = 0; ob->interactive->snoop_by = 0; } if (ob->interactive->snoop_on) { ob->interactive->snoop_on->snoop_by = 0; ob->interactive->snoop_on = 0; } command_giver = ob; add_message ("Closing down.\n"); if (shutdown (ob->interactive->socket, 2) == SOCKET_ERROR) debug_message ("%s: Error %d\n", "shutdown", GETERROR); close (ob->interactive->socket); num_player--; if (ob->interactive->input_to) { free_sentence (ob->interactive->input_to); ob->interactive->input_to = 0; } free (ob->interactive); ob->interactive = 0; all_players[i] = 0; free_object (ob, "remove_interactive"); command_giver = save; return; } (void) fprintf (stderr, "Could not find and remove player %s\n", ob->name); #ifdef DO_ABORT WIN32CLEANUP abort (); #endif } static void new_player (int new_socket, struct sockaddr_in *addr, int len) { int i; char *p; if (d_flag) debug_message ("New player at socket %d.\n", new_socket); for (i = 0; i < MAX_PLAYERS; i++) { struct object *ob; if (all_players[i] != 0) continue; current_object = 0; ob = (clone_object ("obj/player"))->u.ob; add_ref (ob, "new_player"); if (ob == 0) fatal ("Could not load 'obj/player'\n"); ob->interactive = (struct interactive *) xalloc (sizeof (struct interactive)); all_players[i] = ob->interactive; command_giver = ob; ob->interactive->ob = ob; ob->interactive->text[0] = '\0'; ob->interactive->input_to = 0; ob->interactive->closing = 0; ob->interactive->snoop_on = 0; ob->interactive->snoop_by = 0; ob->interactive->out_portal = 0; ob->interactive->portal_socket = 0; ob->interactive->from_portal = 0; set_prompt ("> "); all_players[i]->socket = new_socket; /* memcpy(&all_players[i]->addr, addr, len); */ getpeername (new_socket, (struct sockaddr *) &all_players[i]->addr, &len); current_object = 0; num_player++; logon (ob); return; } p = "Lpmud is full. Come back later.\r\n"; send (new_socket, p, strlen (p), 0); close (new_socket); } int call_function_interactive (struct interactive *i, char *str) { char *function; struct object *ob; struct value *val; if (!i->input_to) return 0; /* * Special feature: input_to() has been called to setup * a call to a function. */ function = string_copy (command_giver->interactive->input_to->function); ob = command_giver->interactive->input_to->ob; val = alloc_value (); val->type = T_STRING; val->u.string = string_copy (str); free_sentence (command_giver->interactive->input_to); command_giver->interactive->input_to = 0; /* * We must clear this reference before the call to apply(), because someone * might want to set up a new input_to(). */ (void) apply (function, ob, val); free (function); return 1; } int set_call (struct object *ob, struct sentence *sent) { if (ob->interactive == 0 || ob->interactive->input_to) return 0; ob->interactive->input_to = sent; return 1; } void show_info_about (char *str, char *room, struct interactive *i) { struct hostent *hp = 0; #if 0 hp = gethostbyaddr (&i->addr.sin_addr.s_addr, 4, AF_INET); #endif add_message ("%-15s %-25s %s\n", hp ? hp->h_name : inet_ntoa (i->addr.sin_addr), str, room); } void remove_all_players (void) { int i; for (i = 0; i < MAX_PLAYERS; i++) { if (all_players[i] == 0) continue; (void) apply ("quit", all_players[i]->ob, 0); } /* * All players should be out now. However, lets try again. */ for (i = 0; i < MAX_PLAYERS; i++) { if (all_players[i] == 0) continue; fprintf (stderr, "Still players.\n"); remove_interactive (all_players[i]->ob); } } void set_prompt (char *str) { command_giver->interactive->prompt = str; } /* * Print the prompt, but only if input_to not is enabled. */ void print_prompt (void) { if (command_giver == 0) fatal ("command_giver == 0.\n"); if (command_giver->interactive->input_to == 0) add_message (command_giver->interactive->prompt); } void set_snoop (struct object *me, struct object *you) { struct interactive *on = 0, *by = 0; int i; for (i = 0; i < MAX_PLAYERS && (on == 0 || by == 0); i++) { if (all_players[i] == 0) continue; if (all_players[i]->ob == me) by = all_players[i]; else if (all_players[i]->ob == you) on = all_players[i]; } if (you == 0) { if (by == 0) fatal ("Could not find myself to stop snoop.\n"); add_message ("Ok.\n"); if (by->snoop_on == 0) return; by->snoop_on->snoop_by = 0; by->snoop_on = 0; return; } if (on == 0 || by == 0) { add_message ("Failed.\n"); return; } if (by->snoop_on) by->snoop_on->snoop_by = 0; if (on->snoop_by) { add_message ("Busy.\n"); return; } on->snoop_by = by; by->snoop_on = on; add_message ("Ok.\n"); } #define TS_DATA 0 #define TS_IAC 1 #define TS_WILL 2 #define TS_WONT 3 #define TS_DO 4 #define TS_DONT 5 static int telnet_neg (char *to, char *from) { int state = TS_DATA; int ch; char *first = to; while (1) { ch = (*from++ & 0xff); switch (state) { case TS_DATA: switch (ch) { case IAC: state = TS_IAC; continue; case '\b': /* Backspace */ case 0x7f: /* Delete */ if (to <= first) continue; to -= 1; continue; default: if (ch & 0x80) { debug_message ("Tel_neg: 0x%x\n", ch); continue; } *to++ = ch; if (ch == 0) return 0; continue; } case TS_IAC: switch (ch) { case WILL: state = TS_WILL; continue; case WONT: state = TS_WONT; continue; case DO: state = TS_DO; continue; case DONT: state = TS_DONT; continue; case DM: break; case NOP: case GA: default: break; } state = TS_DATA; continue; case TS_WILL: debug_message ("Will %s\n", telopts[ch]); state = TS_DATA; continue; case TS_WONT: debug_message ("Wont %s\n", telopts[ch]); state = TS_DATA; continue; case TS_DO: debug_message ("Do %s\n", telopts[ch]); state = TS_DATA; continue; case TS_DONT: debug_message ("Dont %s\n", telopts[ch]); state = TS_DATA; continue; default: debug_message ("Bad state: 0x%x\n", state); state = TS_DATA; continue; } } }