eventmud/doc/
eventmud/help/
/*
 * This file contains the socket code, used for accepting
 * new connections as well as reading and writing to
 * sockets, and closing down unused sockets.
 */

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/time.h>

#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>

/* including main header file */
#include "mud.h"

/* global variables */
struct event_base *io_base;
evutil_socket_t    io_sock;
struct event      *io_sock_event;

D_SOCKET *dsock_free   = NULL;  /* the socket free list              */
D_SOCKET *dsock_list   = NULL;  /* the linked list of active sockets */
D_MOBILE *dmobile_free = NULL;  /* the mobile free list              */
D_MOBILE *dmobile_list = NULL;  /* the mobile list of active mobiles */

/* mccp support */
#ifndef NOMCCP
const unsigned char compress_will   [] = { IAC, WILL, TELOPT_COMPRESS,  '\0' };
const unsigned char compress_will2  [] = { IAC, WILL, TELOPT_COMPRESS2, '\0' };
#endif
const unsigned char do_echo         [] = { IAC, WONT, TELOPT_ECHO,      '\0' };
const unsigned char dont_echo       [] = { IAC, WILL, TELOPT_ECHO,      '\0' };

#ifdef CYGWIN32
pthread_mutex_t lookup_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif


/*
 * This is where it all starts, nothing special.
 */
int main(int argc, char **argv)
{
  bool copyover = FALSE;

  /* note startup */
  log_string("Program starting.");

  if (argc > 2 && atoi(argv[2]) > 0)
  {
    copyover = TRUE;
    io_sock  = atoi(argv[2]);
  }

  /* init */
  init_io(copyover);

  /* load */
  load_muddata(copyover);

  /* run  */
  event_base_dispatch(io_base);

  /* kill */
  kill_io();

  /* note shutdown */
  log_string("Program terminated without errors.");

  return 0;
}


void prompt(D_SOCKET *dsock)
{
  /* bust a prompt */
  if (dsock->state == STATE_PLAYING && dsock->bust_prompt)
  {
    text_to_buffer(dsock, "EventMud:> ");

    dsock->bust_prompt = FALSE;
  }
}


/*
 * Set of callback routines to handle socket and timer events
 */
void cb_recycle(evutil_socket_t sockfd, short flags, void *arg)
{
  struct timeval tv;

  recycle_sockets();

  /* reschedule the recycle timer */
  tv.tv_sec  = 10;
  tv.tv_usec = 0;

  event_base_once(io_base, -1, EV_TIMEOUT, cb_recycle, NULL, &tv);
}


void cb_update(evutil_socket_t sockfd, short flags, void *arg)
{
  struct timeval tv;

  update_handler();

  /* reschedule the update timer */
  tv.tv_sec  = 0;
  tv.tv_usec = 1000000 / PULSES_PER_SECOND;

  event_base_once(io_base, -1, EV_TIMEOUT, cb_update, NULL, &tv);
}


void cb_command(evutil_socket_t sockfd, short flags, void *arg)
{
  D_SOCKET *dsock = arg;

  if (dsock->state == STATE_CLOSED)
  {
    return;
  }

  /* check for pending command */
  if (dsock->next_command[0] != '\0')
  {
    struct timeval tv;

    process_cmd(dsock);

    retrieve_cmd(dsock);

    /* reschedule command timer */
    tv.tv_sec  = 0;
    tv.tv_usec = 1000000 / PULSES_PER_SECOND;

    event_base_once(io_base, -1, EV_TIMEOUT, cb_command, (void*) dsock, &tv);
  }

  /* bust a prompt */
  prompt(dsock);
}


void cb_read(struct bufferevent *event, void *arg)
{
  D_SOCKET *dsock = arg;

  /* transfer to input buffer - could be optimized not to */
  struct evbuffer *buffer = bufferevent_get_input(event);

  int size = strlen(dsock->inbuf);
  int left = sizeof(dsock->inbuf) - size - 1;
  int read = evbuffer_remove(buffer, (void*) (dsock->inbuf + size), left);

  dsock->inbuf[size + read] = '\0';

  /* discard the remainder */
  evbuffer_drain(buffer, evbuffer_get_length(buffer));

  /* check for pending command */
  if (dsock->next_command[0] == '\0')
  {
    retrieve_cmd(dsock);

    cb_command(-1, 0, arg);
  }
}


void cb_write(struct bufferevent *event, void *arg)
{
  D_SOCKET *dsock = arg;

  /* bust a prompt */
  prompt(dsock);
}


void cb_error(struct bufferevent *event, short flags, void *arg)
{
  D_SOCKET *dsock = arg;

  /* disconnect */
  close_socket(dsock, FALSE);
}


void cb_accept(evutil_socket_t sockfd, short flags, void *arg)
{
  struct sockaddr_storage ss;
  socklen_t slen = sizeof(ss);
  int fd;

  /* connect */
  fd = accept(sockfd, (struct sockaddr*) &ss, &slen);

  evutil_make_socket_nonblocking(fd);

  new_socket(fd);
}


/*
 * io startup and shutdown functions
 */
void init_io(bool copyover)
{
  struct timeval tv;

  /* initialize io */
  io_base = event_base_new();

  /* open game socket */
  if (copyover == FALSE)
  {
    struct sockaddr_in sin;

    io_sock = socket(AF_INET, SOCK_STREAM, 0);

    evutil_make_socket_nonblocking(io_sock);
    evutil_make_listen_socket_reuseable(io_sock);

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(MUDPORT);

    bind(io_sock, (struct sockaddr*) &sin, sizeof(struct sockaddr));

    listen(io_sock, 3);
  }

  /* register accept handler */
  io_sock_event = event_new(io_base, io_sock, (EV_READ | EV_PERSIST), cb_accept, NULL);

  event_add(io_sock_event, NULL);

  /* schedule recycle timer */
  tv.tv_sec  = 10;
  tv.tv_usec = 0;

  event_base_once(io_base, -1, EV_TIMEOUT, cb_recycle, NULL, &tv);

  /* schedule update timer */
  tv.tv_sec  = 0;
  tv.tv_usec = 1000000 / PULSES_PER_SECOND;

  event_base_once(io_base, -1, EV_TIMEOUT, cb_update, NULL, &tv);
}


void kill_io()
{
  D_SOCKET *dsock, *dsock_next;

  /* close user sockets */
  for (dsock = dsock_list; dsock; dsock = dsock_next)
  {
    dsock_next = dsock->next;

    if (dsock->lookup_status == STATE_CLOSED) continue;

    close_socket(dsock, FALSE);
  }

  /* deregister accept handler */
  event_del(io_sock_event);

  event_free(io_sock_event);

  /* close game socket */
  close(io_sock);

  /* release io */
  event_base_free(io_base);
}


/*
 * New_socket()
 *
 * Initializes a new socket, get's the hostname
 * and puts it in the active socket_list.
 */
void new_socket(int fd)
{
  D_SOCKET           * dsock;
  struct sockaddr_in   sock_addr;
  pthread_t            thread_lookup;
  LOOKUP_DATA        * lData;
  socklen_t            size;
  pthread_attr_t       attr;

  /* initialize threads */
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  /*
   * allocate some memory for a new socket if
   * there is no free socket in the free_list
   */
  if (dsock_free == NULL)
  {
    if ((dsock = (D_SOCKET *) malloc(sizeof(D_SOCKET))) == NULL)
    {
      bug("New_socket: Cannot allocate memory for socket.");
      abort();
    }
  }
  else
  {
    dsock      = dsock_free;
    dsock_free = dsock_free->next;
  }

  /* reset the socket */
  reset_socket(dsock, fd);

  /* update the linked list of sockets */
  dsock->next =  dsock_list;
  dsock_list  =  dsock;

  /* do a host lookup */
  size = sizeof(sock_addr);
  if (getpeername(fd, (struct sockaddr *) &sock_addr, &size) < 0)
  {
    perror("New_socket: getpeername");
    dsock->hostname = strdup("unknown");
  }
  else
  {
    /* set the IP number as the temporary hostname */
    dsock->hostname = strdup(inet_ntoa(sock_addr.sin_addr));

    if (!compares(dsock->hostname, "127.0.0.1"))
    {
      /* allocate some memory for the lookup data */
      if ((lData = malloc(sizeof(*lData))) == NULL)
      {
        bug("New_socket: Cannot allocate memory for lookup data.");
        abort();
      }

      /* Set the lookup_data for use in lookup_address() */
      lData->buf    =  strdup((char *) &sock_addr.sin_addr);
      lData->dsock  =  dsock;

      /* dispatch the lookup thread */
      pthread_create(&thread_lookup, &attr, &lookup_address, (void*) lData);
    }
    else dsock->lookup_status++;
  }

  /* negotiate compression */
#ifndef NOMCCP
  text_to_buffer(dsock, (char *) compress_will2);
  text_to_buffer(dsock, (char *) compress_will);
#endif

  /* send the greeting */
  text_to_buffer(dsock, greeting);
  text_to_buffer(dsock, "What is your name? ");
}


/*
 * Close_socket()
 *
 * Will close one socket directly, freeing all
 * resources and making the socket availably on
 * the socket free_list.
 */
void close_socket(D_SOCKET *dsock, bool reconnect)
{
  if (dsock->lookup_status > TSTATE_DONE) return;
  dsock->lookup_status += 2;

  /* remove the socket */
  if (dsock->state == STATE_PLAYING)
  {
    if (reconnect)
      text_to_socket(dsock, "This connection has been taken over.\n\r");
    else if (dsock->player)
    {
      dsock->player->socket = NULL;
      log_string("Closing link to %s", dsock->player->name);
    }
  }
  else if (dsock->player)
    free_mobile(dsock->player);

  /* set the closed state */
  close(dsock->control);

  dsock->state = STATE_CLOSED;

  /* release user context */
  bufferevent_free(dsock->context);

  dsock->context = NULL;
}


void reset_socket(D_SOCKET *sock_new, int fd)
{
  struct bufferevent *event;

  /* reset the structure */
  bzero(sock_new, sizeof(*sock_new));

  sock_new->control       = fd;
  sock_new->state         = STATE_NEW_NAME;
  sock_new->lookup_status = TSTATE_LOOKUP;

  /* allocate user context */
  event = (void*) bufferevent_socket_new(io_base, fd, BEV_OPT_CLOSE_ON_FREE);

  bufferevent_setcb(event, cb_read, cb_write, cb_error, (void*) sock_new);
  bufferevent_setwatermark(event, EV_READ, 0, 4096);
  bufferevent_enable(event, (EV_READ | EV_WRITE));

  sock_new->context = event;
}


/*
 * Text_to_buffer()
 *
 * Stores outbound text in a buffer, where it will
 * stay untill it is flushed in the gameloop.
 *
 * Will also parse ANSI colors and other tags.
 */
void text_to_buffer(D_SOCKET *dsock, const char *txt)
{
  static char output[8 * MAX_BUFFER];
  bool underline = FALSE, bold = FALSE;
  int iPtr = 0, last = -1, j, k;
  int length = strlen(txt);

  /* the color struct */
  struct sAnsiColor
  {
    const char    cTag;
    const char  * cString;
    int           aFlag;
  };

  /* the color table... */
  const struct sAnsiColor ansiTable[] =
  {
    { 'd',  "30",  eTHIN },
    { 'D',  "30",  eBOLD },
    { 'r',  "31",  eTHIN },
    { 'R',  "31",  eBOLD },
    { 'g',  "32",  eTHIN },
    { 'G',  "32",  eBOLD },
    { 'y',  "33",  eTHIN },
    { 'Y',  "33",  eBOLD },
    { 'b',  "34",  eTHIN },
    { 'B',  "34",  eBOLD },
    { 'p',  "35",  eTHIN },
    { 'P',  "35",  eBOLD },
    { 'c',  "36",  eTHIN },
    { 'C',  "36",  eBOLD },
    { 'w',  "37",  eTHIN },
    { 'W',  "37",  eBOLD },

    /* the end tag */
    { '\0',  "",   eTHIN }
  };

  if (length >= MAX_BUFFER)
  {
    log_string("text_to_buffer: buffer overflow.");
    return;
  }

  /* always start with a leading space */
  if (dsock->top_output == 0)
  {
    dsock->outbuf[0] = '\n';
    dsock->outbuf[1] = '\r';
    dsock->top_output = 2;
  }

  while (*txt != '\0')
  {
    /* simple bound checking */
    if (iPtr > (8 * MAX_BUFFER - 15))
      break;

    switch(*txt)
    {
      default:
        output[iPtr++] = *txt++;
        break;
      case '#':
        txt++;

        /* toggle underline on/off with #u */
        if (*txt == 'u')
        {
          txt++;
          if (underline)
          {
            underline = FALSE;
            output[iPtr++] =  27; output[iPtr++] = '['; output[iPtr++] = '0';
            if (bold)
            {
              output[iPtr++] = ';'; output[iPtr++] = '1';
            }
            if (last != -1)
            {
              output[iPtr++] = ';';
              for (j = 0; ansiTable[last].cString[j] != '\0'; j++)
              {
                output[iPtr++] = ansiTable[last].cString[j];
              }
            }
            output[iPtr++] = 'm';
          }
          else
          {
            underline = TRUE;
            output[iPtr++] =  27; output[iPtr++] = '[';
            output[iPtr++] = '4'; output[iPtr++] = 'm';
          }
        }

        /* parse ## to # */
        else if (*txt == '#')
        {
          txt++;
          output[iPtr++] = '#';
        }

        /* #n should clear all tags */
        else if (*txt == 'n')
        {
          txt++;
          if (last != -1 || underline || bold)
          {
            underline = FALSE;
            bold = FALSE;
            output[iPtr++] =  27; output[iPtr++] = '[';
            output[iPtr++] = '0'; output[iPtr++] = 'm';
          }

          last = -1;
        }

        /* check for valid color tag and parse */
        else
        {
          bool validTag = FALSE;

          for (j = 0; ansiTable[j].cString[0] != '\0'; j++)
          {
            if (*txt == ansiTable[j].cTag)
            {
              validTag = TRUE;

              /* we only add the color sequence if it's needed */
              if (last != j)
              {
                bool cSequence = FALSE;

                /* escape sequence */
                output[iPtr++] = 27; output[iPtr++] = '[';

                /* remember if a color change is needed */
                if (last == -1 || last / 2 != j / 2)
                  cSequence = TRUE;

                /* handle font boldness */
                if (bold && ansiTable[j].aFlag == eTHIN)
                {
                  output[iPtr++] = '0';
                  bold = FALSE;

                  if (underline)
                  {
                    output[iPtr++] = ';'; output[iPtr++] = '4';
                  }

                  /* changing to eTHIN wipes the old color */
                  output[iPtr++] = ';';
                  cSequence = TRUE;
                }
                else if (!bold && ansiTable[j].aFlag == eBOLD)
                {
                  output[iPtr++] = '1';
                  bold = TRUE;

                  if (cSequence)
                    output[iPtr++] = ';';
                }

                /* add color sequence if needed */
                if (cSequence)
                {
                  for (k = 0; ansiTable[j].cString[k] != '\0'; k++)
                  {
                    output[iPtr++] = ansiTable[j].cString[k];
                  }
                }

                output[iPtr++] = 'm';
              }

              /* remember the last color */
              last = j;
            }
          }

          /* it wasn't a valid color tag */
          if (!validTag)
            output[iPtr++] = '#';
          else
            txt++;
        }
        break;
    }
  }

  /* and terminate it with the standard color */
  if (last != -1 || underline || bold)
  {
    output[iPtr++] =  27; output[iPtr++] = '[';
    output[iPtr++] = '0'; output[iPtr++] = 'm';
  }
  output[iPtr] = '\0';

  /* check to see if the socket can accept that much data */
  if (dsock->top_output + iPtr >= MAX_OUTPUT)
  {
    bug("Text_to_buffer: ouput overflow on %s.", dsock->hostname);
    return;
  }

  /* add data to buffer */
  strcpy(dsock->outbuf + dsock->top_output, output);
  dsock->top_output += iPtr;

  /* reset the top pointer */
  dsock->top_output = 0;

  /* queue onto socket */
  text_to_socket(dsock, dsock->outbuf);
}


/*
 * Text_to_socket()
 *
 * Sends text directly to the socket,
 * will compress the data if needed.
 */
bool text_to_socket(D_SOCKET *dsock, const char *txt)
{
  int iBlck, iPtr, iWrt = 0, length;

  length = strlen(txt);

  /* write compressed */
#ifndef NOMCCP
  if (dsock && dsock->out_compress)
  {
    dsock->out_compress->next_in  = (unsigned char *) txt;
    dsock->out_compress->avail_in = length;

    while (dsock->out_compress->avail_in)
    {
      dsock->out_compress->avail_out = COMPRESS_BUF_SIZE - (dsock->out_compress->next_out - dsock->out_compress_buf);

      if (dsock->out_compress->avail_out)
      {
        int status = deflate(dsock->out_compress, Z_SYNC_FLUSH);

        if (status != Z_OK)
        return FALSE;
      }

      length = dsock->out_compress->next_out - dsock->out_compress_buf;
      if (length > 0)
      {
        for (iPtr = 0; iPtr < length; iPtr += iWrt)
        {
          iBlck = UMIN(length - iPtr, 4096);

          if (bufferevent_write((struct bufferevent*) dsock->context, (void*) (dsock->out_compress_buf + iPtr), iBlck) < 0)
          {
            bug("Text_to_socket (compressed): bufferevent_write() failed");
            return FALSE;
          }

          iWrt = iBlck;
        }

        if (iWrt <= 0)
          break;

        if (iPtr > 0)
        {
          if (iPtr < length)
            memmove(dsock->out_compress_buf, dsock->out_compress_buf + iPtr, length - iPtr);

          dsock->out_compress->next_out = dsock->out_compress_buf + length - iPtr;
        }
      }
    }
    return TRUE;
  }
#endif

  /* write uncompressed */
  for (iPtr = 0; iPtr < length; iPtr += iWrt)
  {
    iBlck = UMIN(length - iPtr, 4096);

    if (bufferevent_write((struct bufferevent*) dsock->context, (void*) (txt + iPtr), iBlck) < 0)
    {
      bug("Text_to_socket: bufferevent_write() failed");
      return FALSE;
    }

    iWrt = iBlck;
  }

  return TRUE;
}


/*
 * Text_to_mobile()
 *
 * If the mobile has a socket, then the data will
 * be send to text_to_buffer().
 */
void text_to_mobile(D_MOBILE *dMob, const char *txt)
{
  if (dMob->socket)
  {
    text_to_buffer(dMob->socket, txt);

    dMob->socket->bust_prompt = TRUE;
  }
}


void retrieve_cmd(D_SOCKET *dsock)
{
  int size = 0, i = 0, j = 0;
#ifndef NOMCCP
  int telopt = 0;
#endif

  /* if theres already a command ready, we return */
  if (dsock->next_command[0] != '\0')
    return;

  /* if there is nothing pending, then return */
  if (dsock->inbuf[0] == '\0')
    return;

  /* check how long the next command is */
  while (dsock->inbuf[size] != '\0' && dsock->inbuf[size] != '\n' && dsock->inbuf[size] != '\r')
    size++;

  /* we only deal with real commands - but treat a full buffer as a command */
  if (size < (sizeof(dsock->inbuf) - 1) && dsock->inbuf[size] == '\0')
    return;

  /* copy the next command into next_command */
  for ( ; i < size; i++)
  {
#ifndef NOMCCP
    if (dsock->inbuf[i] == (signed char) IAC)
    {
      telopt = 1;
    }
    else if (telopt == 1 && (dsock->inbuf[i] == (signed char) DO || dsock->inbuf[i] == (signed char) DONT))
    {
      telopt = 2;
    }
    else if (telopt == 2)
    {
      telopt = 0;

      if (dsock->inbuf[i] == (signed char) TELOPT_COMPRESS)         /* check for version 1 */
      {
        if (dsock->inbuf[i-1] == (signed char) DO)                  /* start compressing   */
          compressStart(dsock, TELOPT_COMPRESS);
        else if (dsock->inbuf[i-1] == (signed char) DONT)           /* stop compressing    */
          compressEnd(dsock, TELOPT_COMPRESS, FALSE);
      }
      else if (dsock->inbuf[i] == (signed char) TELOPT_COMPRESS2)   /* check for version 2 */
      {
        if (dsock->inbuf[i-1] == (signed char) DO)                  /* start compressing   */
          compressStart(dsock, TELOPT_COMPRESS2);
        else if (dsock->inbuf[i-1] == (signed char) DONT)           /* stop compressing    */
          compressEnd(dsock, TELOPT_COMPRESS2, FALSE);
      }
    }
    else
#endif
    if (isprint((int) dsock->inbuf[i]) && isascii((int) dsock->inbuf[i]))
    {
      dsock->next_command[j++] = dsock->inbuf[i];
    }
  }

  dsock->next_command[j] = '\0';

  /* skip forward to the next line */
  while (dsock->inbuf[size] == '\n' || dsock->inbuf[size] == '\r')
  {
    dsock->bust_prompt = TRUE;   /* seems like a good place to check */
    size++;
  }

  /* use i as a static pointer */
  i = size;

  /* move the context of inbuf down */
  while (dsock->inbuf[size] != '\0')
  {
    dsock->inbuf[size - i] = dsock->inbuf[size];
    size++;
  }

  dsock->inbuf[size - i] = '\0';
}


void process_cmd(D_SOCKET *dsock)
{
  /* figure out how to deal with the incoming command */
  switch(dsock->state)
  {
    default:
      break;
    case STATE_NEW_NAME:
    case STATE_NEW_PASSWORD:
    case STATE_VERIFY_PASSWORD:
    case STATE_ASK_PASSWORD:
      handle_new_connections(dsock, dsock->next_command);
      break;
    case STATE_PLAYING:
      handle_cmd_input(dsock, dsock->next_command);
      break;
  }

  dsock->next_command[0] = '\0';
}


void handle_new_connections(D_SOCKET *dsock, char *arg)
{
  D_MOBILE *p_new;
  int i;

  switch(dsock->state)
  {
    default:
      bug("Handle_new_connections: Bad state.");
      break;
    case STATE_NEW_NAME:
      if (dsock->lookup_status != TSTATE_DONE)
      {
        text_to_buffer(dsock, "Making a dns lookup, please have patience.\n\rWhat is your name? ");
        return;
      }
      if (!check_name(arg)) /* check for a legal name */
      {
        text_to_buffer(dsock, "Sorry, that's not a legal name, please pick another.\n\rWhat is your name? ");
        break;
      }
      arg[0] = toupper((int) arg[0]);
      log_string("%s is trying to connect.", arg);

      /* Check for a new Player */
      if ((p_new = load_profile(arg)) == NULL)
      {
        if (dmobile_free == NULL)
        {
          if ((p_new = malloc(sizeof(*p_new))) == NULL)
          {
            bug("Handle_new_connection: Cannot allocate memory.");
            abort();
          }
        }
        else
        {
          p_new        = dmobile_free;
          dmobile_free = dmobile_free->next;
        }
        clear_mobile(p_new);

        /* give the player it's name */
        p_new->name = strdup(arg);

        /* prepare for next step */
        text_to_buffer(dsock, "Please enter a new password: ");
        dsock->state = STATE_NEW_PASSWORD;
      }
      else /* old player */
      {
        /* prepare for next step */
        text_to_buffer(dsock, "What is your password? ");
        dsock->state = STATE_ASK_PASSWORD;
      }

      /* socket <-> player */
      p_new->socket = dsock;
      dsock->player = p_new;
      break;
    case STATE_NEW_PASSWORD:
      if (strlen(arg) < 5 || strlen(arg) > 12)
      {
        text_to_buffer(dsock, "Between 5 and 12 chars please!\n\rPlease enter a new password: ");
        return;
      }
      dsock->player->password = strdup(crypt(arg, dsock->player->name));

      for (i = 0; dsock->player->password[i] != '\0'; i++)
      {
	      if (dsock->player->password[i] == '~')
	      {
	        text_to_buffer(dsock, "Illegal password!\n\rPlease enter a new password: ");
	        return;
	      }
      }

      text_to_buffer(dsock, "Please verify the password: ");
      dsock->state = STATE_VERIFY_PASSWORD;
      break;
    case STATE_VERIFY_PASSWORD:
      if (compares(crypt(arg, dsock->player->name), dsock->player->password))
      {
        /* put him in the list */
        dsock->player->next = dmobile_list;
        dmobile_list        = dsock->player;

        log_string("New player: %s has entered the game.", dsock->player->name);

        /* and into the game */
        dsock->state = STATE_PLAYING;
        text_to_buffer(dsock, motd);
      }
      else
      {
        free(dsock->player->password);
        text_to_buffer(dsock, "Password mismatch!\n\rPlease enter a new password: ");
        dsock->state = STATE_NEW_PASSWORD;
      }
      break;
    case STATE_ASK_PASSWORD:
      if (compares(crypt(arg, dsock->player->name), dsock->player->password))
      {
        if ((p_new = check_reconnect(dsock->player->name)) != NULL)
        {
          /* attach the new player */
          ex_free_mob(dsock->player);
          dsock->player = p_new;
          p_new->socket = dsock;

          log_string("%s has reconnected.", dsock->player->name);

          /* and let him enter the game */
          dsock->state = STATE_PLAYING;
          text_to_buffer(dsock, "You take over a body already in use.\n\r");
        }
        else if ((p_new = load_player(dsock->player->name)) == NULL)
        {
          text_to_socket(dsock, "ERROR: Your pfile is missing!\n\r");
          ex_free_mob(dsock->player);
          dsock->player = NULL;
          close_socket(dsock, FALSE);
          return;
        }
        else
        {
          /* attach the new player */
          ex_free_mob(dsock->player);
          dsock->player = p_new;
          p_new->socket = dsock;

          /* put him in the active list */
          p_new->next   =  dmobile_list;
          dmobile_list  =  p_new;

          log_string("%s has entered the game.", dsock->player->name);

          /* and let him enter the game */
          dsock->state = STATE_PLAYING;
          text_to_buffer(dsock, motd);
        }
      }
      else
      {
        text_to_socket(dsock, "Bad password!\n\r");
        ex_free_mob(dsock->player);
        dsock->player = NULL;
        close_socket(dsock, FALSE);
      }
      break;
  }
}


/* does the lookup, changes the hostname, and dies */
void *lookup_address(void *arg)
{
  LOOKUP_DATA *lData = (LOOKUP_DATA *) arg;
  struct hostent *from = 0;
#ifndef CYGWIN32
  struct hostent ent;
  char buf[16384];
  int err;
#endif

#ifdef CYGWIN32

  pthread_mutex_lock(&lookup_mutex);
  from = gethostbyaddr(lData->buf, sizeof(lData->buf), AF_INET);
  if (from && from->h_name)
  {
    free(lData->dsock->hostname);
    lData->dsock->hostname = strdup(from->h_name);
  }
  pthread_mutex_unlock(&lookup_mutex);

#else

  /* do the lookup and store the result at &from */
  gethostbyaddr_r(lData->buf, sizeof(lData->buf), AF_INET, &ent, buf, 16384, &from, &err);

  /* did we get anything ? */
  if (from && from->h_name)
  {
    free(lData->dsock->hostname);
    lData->dsock->hostname = strdup(from->h_name);
  }

#endif

  /* set it ready to be closed or used */
  lData->dsock->lookup_status++;

  /* free the lookup data */
  free(lData->buf);
  free(lData);

  /* and kill the thread */
  pthread_exit(0);

#ifdef CYGWIN32
  return NULL;
#endif
}


void recycle_sockets()
{
  D_SOCKET *dsock, *dsock_next;

  for (dsock = dsock_list; dsock; dsock = dsock_next)
  {
    dsock_next = dsock->next;

    if (dsock->lookup_status != TSTATE_CLOSED) continue;

    /* remove the socket from the socket list */
    if (dsock == dsock_list)
      dsock_list = dsock->next;
    else
    {
      D_SOCKET *prev;

      for (prev = dsock_list; prev && prev->next != dsock; prev = prev->next)
        ;
      if (prev)
        prev->next = dsock->next;
      else
        bug("Recycle_sockets: Closed socket not in list");
    }

    /* free the memory */
    free(dsock->hostname);

    /* stop compression */
#ifndef NOMCCP
    compressEnd(dsock, dsock->compressing, TRUE);
#endif

    /* put the socket in the free_list */
    dsock->next = dsock_free;
    dsock_free  = dsock;
  }
}