lpmud/mudlib/
lpmud/mudlib/doc/
lpmud/mudlib/doc/LPC/
lpmud/mudlib/log/
lpmud/mudlib/players/
lpmud/mudlib/room/maze1/
lpmud/mudlib/room/sub/
#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;
    }
  }
}