/*
 * Abstract: Replaces the old hostlookup system with a threaded version,
 *           thus removing the "lag" at new connections.
 *
 * This is the 2nd version of the threaded dns lookup howto, the first version
 * used a rather buggy system, which could accidently cause the mud to crash.
 * Even though this was rare, it's still anoying. This system has been implanted
 * in the latest releases of both version 1.0.x and 1.2.x of dystopia.
 *
 * This howto was written for godwars based muds, but it should be possible
 * to use it with any merc deriviative without to much work. It has been tested
 * on gw_96 downloaded from KaVirs homepage (www.kavir.org). It took roughly 5
 * minutes to add all the changes, so this is actually a minor modification.
 *
 * Code/Howto by Brian Graversen
 */

*******************************
*******************************
**** Initial Modifications ****
*******************************
*******************************

/*
 * File: Makefile
 */

** Add this to the L_FLAGS (if you use linux):
 -lpthread

** Add this to the L_FLAGS (if you use BSD and perhaps other unices):
 -pthread

/*
 * File: merc.h
 */

** Add this to the includes at the beginning of the file:
#include <pthread.h>

** Add a new set of defines somewhere:
#define STATUS_LOOKUP       0   // New Descriptor, in lookup by default.
#define STATUS_DONE         1   // The lookup is done.
#define STATUS_WAIT         2   // Closed while in thread.
#define STATUS_CLOSED       3   // Closed, ready to be recycled.

** Add a new structure type somewhere:
struct dummy_arg
{
  DUMMY_ARG        *next;
  DESCRIPTOR_DATA  *d;
  char             *buf;
  sh_int           status;
};

** Make sure to typedefine it somewhere
typedef struct  dummy_arg               DUMMY_ARG;


** Add these globel variables somewhere:
extern          DUMMY_ARG         *     dummy_free;
extern          DUMMY_ARG         *     dummy_list;
extern          pthread_mutex_t         memory_mutex;

** Edit the descriptor_data structure so it contains:
  sh_int              lookup_status;


**********************************
**********************************
**** Editing the lookup stuff ****
**********************************
**********************************

/*
 * File: comm.c
 */
** Initialize the global variables somewhere (above main() perhaps)
DUMMY_ARG        *     dummy_free;
DUMMY_ARG        *     dummy_list;
pthread_mutex_t        memory_mutex = PTHREAD_MUTEX_INITIALIZER;

** Write a new new_descroptor() function to replace the old code

/* This is just a prototype, you may have to compare this with
 * your own new_descriptor() function, so you don't forget anything.
 * This function can replace the one in gw_96 without any changes.
 *
 * It is important to notice that all banning has been removed from
 * this function, since the hostlookup has not been completed yet.
 */
void new_descriptor( int control )
{
  static DESCRIPTOR_DATA d_zero;
  char buf[MAX_STRING_LENGTH];
  DESCRIPTOR_DATA *dnew;
  struct sockaddr_in sock;
  int desc;
  int size;
  pthread_attr_t attr;
  pthread_t thread_lookup;
  DUMMY_ARG *dummyarg;

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


  /* New Dummy Argument */
  if (dummy_free == NULL)
  {
    dummyarg = alloc_perm(sizeof(*dummyarg));
  }
  else
  {
    dummyarg = dummy_free;
    dummy_free = dummy_free->next;
  }
  dummyarg->status = 1;   
  dummyarg->next = dummy_list;
  dummy_list = dummyarg;

  size = sizeof(sock);
  getsockname( control, (struct sockaddr *) &sock, &size );
  if (( desc = accept( control, (struct sockaddr *) &sock, &size) ) < 0)
  {
    perror( "New_descriptor: accept" );
    return;
  }
    
#if !defined(FNDELAY)
#define FNDELAY O_NDELAY
#endif
     
  if (fcntl( desc, F_SETFL, FNDELAY ) == -1)
  {
    perror( "New_descriptor: fcntl: FNDELAY");
    return;
  }

  /*
   * Cons a new descriptor.
   */
  if ( descriptor_free == NULL )
  {
    dnew = alloc_perm( sizeof(*dnew) );
  }
  else
  {
    dnew            = descriptor_free;
    descriptor_free = descriptor_free->next;
  }

  /* initialize descriptor data */
  *dnew               = d_zero;
  dnew->descriptor    = desc;
  dnew->connected     = CON_GET_NAME;
  dnew->lookup_status = STATUS_LOOKUP;  // important
  dnew->outsize       = 2000;
  dnew->outbuf        = alloc_mem( dnew->outsize );

  size = sizeof(sock);
  if ( getpeername( desc, (struct sockaddr *) &sock, &size ) < 0 )
  { 
    perror( "New_descriptor: getpeername" );
    dnew->host = str_dup( "(unknown)" );
  }
  else
  {
    int addr;

    addr = ntohl( sock.sin_addr.s_addr );
    sprintf( buf, "%d.%d.%d.%d",
           ( addr >> 24 ) & 0xFF, ( addr >> 16 ) & 0xFF,
           ( addr >>  8 ) & 0xFF, ( addr       ) & 0xFF);

    dnew->host = str_dup(buf);      // set the temporary ip as the host.
        
    /* Set up the dummyarg for use in lookup_address */
    dummyarg->buf      = str_dup((char *) &sock.sin_addr);
    dummyarg->d        = dnew;

    /* create a thread to do the lookup */
    pthread_create( &thread_lookup, &attr, (void*)&lookup_address, (void*) dummyarg);
  }

  /*
   * Init descriptor data.
   */
  dnew->next                  = descriptor_list;
  descriptor_list             = dnew;

  /* send gretting */
  {
    extern char * help_greeting;

    if ( help_greeting[0] == '.' )
      write_to_buffer( dnew, help_greeting+1, 0 );
    else
      write_to_buffer( dnew, help_greeting  , 0 );
  }
}

** Write new close_socket() and close_socket2() functions.

/*
 * The following is a prototype of close_socket(), which
 * can replace the one used in gw_96 without any changes,
 * other codebases may need modifications of this function,
 * so remember to compare it with the one you already have.
 * close_socket2() should also be replaced with a similar
 * function, this is easy, so I'll leave that to you.
 */

/* Nothing much is done here, except preparing the socket
 * to get closed. The actual closing is done elsewhere.
 * This is because we do not want to close sockets that
 * are currently active in another thread.
 */
void close_socket( DESCRIPTOR_DATA *dclose )
{ 
  CHAR_DATA *ch;

  if (dclose->lookup_status > STATUS_DONE) return;
  dclose->lookup_status += 2; 

  if (dclose->outtop > 0) process_output( dclose, FALSE );

  if ( dclose->snoop_by != NULL )
    write_to_buffer( dclose->snoop_by, "Your victim has left the game.\n\r", 0 );

  if (dclose->character != NULL && dclose->connected == CON_PLAYING && 
      IS_NPC(dclose->character)) do_return(dclose->character,"");

  /*
   * Clear snoops
   */
  {
    DESCRIPTOR_DATA *d;

    for ( d = descriptor_list; d != NULL; d = d->next )
      if (d->snoop_by == dclose) d->snoop_by = NULL;
  }
  if ((ch = dclose->character) != NULL)
  {
    sprintf( log_buf, "Closing link to %s.", ch->name );
    log_string( log_buf );
    if (dclose->connected == CON_PLAYING)
    {
      if (IS_NPC(ch) || ch->pcdata->obj_vnum == 0)
        act( "$n has lost $s link.", ch, NULL, NULL, TO_ROOM );
      ch->desc = NULL;
    }
    else
    {
      free_char( dclose->character );
    }
  }

  if (d_next == dclose) d_next = d_next->next;
  dclose->connected = CON_NOT_PLAYING;
  return;
}

** Add four new functions, one to do the lookup,
** another function to check for bans,
** a function to recycle closed descriptors, and
** finally a function to recycle dummy_args.
** Remember to define them all in merc.h so they
** can be called from other files.

/* does the lookup, changes the hostname, and dies */
void lookup_address(DUMMY_ARG *darg)
{
  struct hostent *from = 0;
  struct hostent ent;
  char buf[16384]; // enough ??
  int err;

  gethostbyaddr_r( darg->buf, sizeof(darg->buf), AF_INET, &ent, buf, 16384, &from, &err);
  if (from && from->h_name)
  {
    free_string(darg->d->host);
    darg->d->host = str_dup(from->h_name);
  }
  darg->d->lookup_status++;
  free_string(darg->buf);
  darg->status = 0;
  pthread_exit(0);
}

/* returns true if the site is banned */
bool check_banned( DESCRIPTOR_DATA *dnew )
{
  BAN_DATA *pban;

  for (pban = ban_list; pban != NULL; pban = pban->next)
    if (!str_suffix(pban->name, dnew->host)) return TRUE;
  return FALSE;
}

/* recycles closed descriptors */
void recycle_descriptors()
{
  DESCRIPTOR_DATA *dclose;
  DESCRIPTOR_DATA *dclose_next;
         
  for (dclose = descriptor_list; dclose; dclose = dclose_next)
  {
    dclose_next = dclose->next;
    if (dclose->lookup_status != STATUS_CLOSED) continue;
       
    /*
     * First let's get it out of the descriptor list.
     */
    if ( dclose == descriptor_list )
    {
      descriptor_list = descriptor_list->next;
    }
    else
    {
      DESCRIPTOR_DATA *d;
  
      for (d = descriptor_list; d && d->next != dclose; d = d->next)
        ;
      if (d != NULL)
        d->next = dclose->next;
      else
      {
        bug( "Recycle_descriptors: dclose not found.", 0 );
        continue;
      }
    }

    /*
     * Clear out that memory
     */
    free_string( dclose->host );
    free_mem( dclose->outbuf, dclose->outsize );

    /* 
     * Bye bye mr. Descriptor.
     */
    close( dclose->descriptor );
       
    /*
     * And then we recycle
     */
    dclose->next        = descriptor_free;
    descriptor_free     = dclose;
  }
}

/* recycles used dummy arguments */
void recycle_dummys()
{
  DUMMY_ARG *dummy;
  DUMMY_ARG *dummy_next;
   
  for (dummy = dummy_list; dummy; dummy = dummy_next)
  {
    dummy_next = dummy->next;
    if (dummy->status == 1) continue;  // being used

    if (dummy == dummy_list)
    {
      dummy_list = dummy_list->next;
    }
    else
    {
      DUMMY_ARG *prev;
     
      /* we find the prev dummy arg */
      for (prev = dummy_list; prev && prev->next != dummy; prev = prev->next)
        ;
      if (prev != NULL) 
        prev->next = dummy->next;
      else
      {
        bug( "Recycle_dymmys: dummy not found.", 0 );
        continue;
      } 
      /* recycle */
      dummy->next = dummy_free;
      dummy_free = dummy;
    }   
  }  
}

**********************************
**********************************
**** Handling New Connections ****
****      And recycling       ****
**********************************
**********************************

/*
 * File: comm.c
 */
** In nanny() we should edit case CON_GET_NAME, so only
** descriptors that have passed the lookup can enter the
** game, as well as check for banned sites.

** after this check

  if ( argument[0] == '\0' )
  {
    close_socket( d );
    return;
  }

** Add these two checks:

  if (d->lookup_status != STATUS_DONE)
  {
    write_to_buffer( d, "Making a dns lookup, please have patience.\n\rWhat be thy name?", 0 );
    return;
  }
  if (check_banned(d))
  {
    write_to_buffer( d, "Your site has been banned from this mud\n\r", 0 );
    close_socket( d );
    return;
  }

/*
 * update.c
 */
** Edit update_handler() so it calls this function every pass
** (put it somewhere in the top, but make sure it is called
** every time update_handler is called, so do NOT put it in
** one of the pulse's.

  recycle_descriptors();

** Now somewhere in update_handler() add this call,
** where you put it doesn't matter, perhaps in pulse_area
** since it doesn't need to be called that often.

  recycle_dummys();


*******************************
*******************************
**** Locking Memory Access ****
*******************************
*******************************

/*
 * File: db.c
 */

** Edit the functions alloc_mem(), free_mem() and alloc_perm().
** in each of the functions you must begin with:

    pthread_mutex_lock(&memory_mutex);

** and make sure to end the function with:

    pthread_mutex_unlock(&memory_mutex);

** This will lock and unlock access to the memory list,
** so only one thread can use it at a given time.
** Also ***IMPORTANT** since alloc_mem() calls alloc_perm()
** if it doesn't have any free memory of the needed size,
** you **MUST** unlock and lock the mutex before and after
** the call to alloc_perm(). All in all, you should add
** 8 mutex calls in db.c (4 locks and 4 unlocks).

*****************
*****************
**** Roundup ****
*****************
*****************

Let's take a quick look at possible crashes in the code,
the most important thing to remember is that d->host might
not be allocated, so we have to worry when accessing this.

In comm.c we should make the following changes :

First make sure nanny() can handle CON_NOT_PLAYING
(just break on those)

Then in read_from_descriptor() make these changes :

(after)

  if ( d->incomm[0] != '\0' )
    return TRUE;

(add this)

  if (d->connected == CON_NOT_PLAYING)
    return TRUE;

(change these lines)

  else
    sprintf( log_buf, "%s input overflow!", d->host );

(to these)

  else if (d->lookup_status == STATUS_DONE) {
    sprintf( log_buf, "%s input overflow!", d->host );
    log_string( log_buf );
  }

And in the function read_from_buffer() we should do the same,
to avoid crashes if we try to access d->host when it's not allocated.

(change)

  else
    sprintf( log_buf, "%s input spamming!", d->host );

(to)

  else if (d->lookup_status == STATUS_DONE) {
    sprintf( log_buf, "%s input overflow!", d->host );
    log_string( log_buf );
  }

also, in interp.c the function interpret() should have a little
extra check to avoid crashes when after freeing a character.

(add this at the very beginning of interpret() )

  if (!ch) return;


Now look through your code, whenever theres a function that
does something to d->host (d being a descriptor), then you
need to make sure that d->lookup_status == STATUS_DONE.

One example would be the immortal command 'users' which
lists all active connections to the mud.