#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;
}
}
}