dbna/clans/
dbna/councils/
dbna/deity/
dbna/gods/
dbna/houses/
dbna/space/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |   \\._.//   *
 * -----------------------------------------------------------|   (0...0)   *
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998  by Derek Snider      |    ).:.(    *
 * -----------------------------------------------------------|    {o o}    *
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |   / ' ' \   *
 * Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek,      |~'~.VxvxV.~'~*
 * Tricops and Fireblade                                      |             *
 *------------------------------------------------------------------------  *
 *                 Ident                                                    *
****************************************************************************/

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <fcntl.h>

#ifdef WIN32
#include <io.h>
#define EWOULDBLOCK WSAEWOULDBLOCK
#else
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#define closesocket close
#endif

#include "mud.h"


#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif

typedef enum
{ AS_TOOPEN, AS_TOSEND, AS_TOREAD } AUTH_STATE;

typedef struct auth_data
{
  struct auth_data *next, *prev;
  DESCRIPTOR_DATA *d;
  int afd;
  int state;
  int times;
  struct sockaddr_in us, them;
} AUTH_DATA;

AUTH_DATA *first_auth, *last_auth;

bool auth_read( AUTH_DATA * a, CHAR_DATA * ch );
bool auth_write( AUTH_DATA * a, CHAR_DATA * ch );
bool auth_open( AUTH_DATA * a, CHAR_DATA * ch );

void auth_maxdesc( int *md, fd_set * ins, fd_set * outs, fd_set * excs )
{
  AUTH_DATA *a;

  for( a = first_auth; a; a = a->next )
    if( a->state != AS_TOOPEN )
    {
      *md = UMAX( *md, a->afd );
      FD_SET( a->afd, ins );
      FD_SET( a->afd, outs );
      FD_SET( a->afd, excs );
    }
  return;
}

void auth_check( fd_set * ins, fd_set * outs, fd_set * excs )
{
  AUTH_DATA *a, *a_next;
  CHAR_DATA *ch;
  bool ferr;

  for( a = first_auth; a; a = a_next )
  {
    a_next = a->next;
    ferr = FALSE;
    ch = ( a->d->original ? a->d->original : a->d->character );
    if( a->state == AS_TOOPEN )
    {
      if( !auth_open( a, ch ) )
        ferr = TRUE;
    }
    else if( FD_ISSET( a->afd, excs ) )
    {
      FD_CLR( a->afd, ins );
      FD_CLR( a->afd, outs );
      bug( "Auth_check: exception found for %s@%s.", ( ch ? ch->name : "(unknown)" ), a->d->host );
      STRFREE( a->d->user );
      a->d->user = STRALLOC( "Exception" );
      ferr = TRUE;
    }
    else if( FD_ISSET( a->afd, ins ) && a->state == AS_TOREAD )
    {
      if( !auth_read( a, ch ) )
      {
        FD_CLR( a->afd, outs );
        ferr = TRUE;
      }
    }
    else if( FD_ISSET( a->afd, outs ) && a->state == AS_TOSEND )
    {
      if( !auth_write( a, ch ) )
        ferr = TRUE;
    }
    if( ferr )
    {
      if( a->state != AS_TOOPEN )
        closesocket( a->afd );
      UNLINK( a, first_auth, last_auth, next, prev );
      DISPOSE( a );
    }
  }
  return;
}

char *break_arg( char **s, char end )
{
  char *ret, *ws;

  while( isspace( **s ) )
    ++ * s;
  ret = *s;
  while( **s && **s != end )
    ++ * s;
  for( ws = ( *s ) - 1; isspace( *ws ); --ws )
    *ws = '\0';
  if( **s != '\0' )
  {
    **s = '\0';
    ++*s;
  }
/*  bug("break_arg: broke '%s', left '%s'", ret, *s);*/
  return ret;
}

#define KILLRET(bs, us) \
do { \
  bug(bs, (ch ? ch->name : "(unknown)"), a->d->host); \
  STRFREE(a->d->user); \
  a->d->user = STRALLOC(us); \
  return FALSE; \
} while(0)

bool auth_read( AUTH_DATA * a, CHAR_DATA * ch )
{
  char readbuf[MAX_STRING_LENGTH];
  char system[MAX_INPUT_LENGTH], user[MAX_INPUT_LENGTH];
  char *s = readbuf;
  int n;

  n = recv( a->afd, readbuf, sizeof( readbuf ) - 10, 0 );
  if( n < 0 )
  {
    perror( "auth_read: read" );
    KILLRET( "Auth_read: Error on read for %s@%s.", "(Error_on_read)" );
  }
  if( !n )
    return TRUE;
/*    KILLRET("Auth_read: EOF on read for %s@%s.", "(EOF on read)");*/
  readbuf[n] = '\0';
  while( isspace( *s ) )
    ++s;
  if( !*s )
    KILLRET( "Auth_read: blank auth for %s@%s.", "(blank_auth)" );
  /*
   * That 1024 check should actually be against the port the user is
   * logged into, but in SMAUG theres like 4 possible, and I don't
   * feel like checking every single one.. -- Alty 
   */
/*  bug("ident = '%s'.", s);*/
  if( !atoi( break_arg( &s, ',' ) ) || atoi( break_arg( &s, ':' ) ) < 1024 )
    KILLRET( "Auth_read: Invalid ident reply for %s@%s.", "(invalid_ident)" );
  break_arg( &s, ':' );
  sprintf( system, "%.*s", ( int )sizeof( system ) - 1, break_arg( &s, ':' ) );
/*  if (!str_cmp(system, "OTHER"))
    KILLRET("Auth_read: invalid system for %s@%s.", "(invalid system)");*/
  sprintf( user, "%.*s", ( int )sizeof( user ) - 1, break_arg( &s, ' ' ) );
  if( !*user )
    KILLRET( "Auth_read: no username for %s@%s.", "(no_username)" );
  sprintf( log_buf, "Auth reply ok.  Incoming user [%s@%s] for %s.", user, a->d->host, ( ch ? ch->name : "(unknown)" ) );
  STRFREE( a->d->user );
  a->d->user = STRALLOC( user );
  return FALSE; /* FALSE actually removes the AUTH_DATA, which is good. */
}

bool auth_write( AUTH_DATA * a, CHAR_DATA * ch )
{
  char authbuf[32];
  int n;

  sprintf( authbuf, "%u , %u\r\n", ( unsigned int )ntohs( a->them.sin_port ), ( unsigned int )ntohs( a->us.sin_port ) );
  n = send( a->afd, authbuf, strlen( authbuf ), 0 );

  if( n != strlen( authbuf ) )
  {
    if( !--a->times )
    {
      /*
       * This used to be the KILLRET you see below, but too many imms complained
       * about log being spammed.. yeah.. like it aint spammed anyways..
       * -- Alty 
       */
      STRFREE( a->d->user );
      a->d->user = STRALLOC( "(broken_pipe)" );
      return FALSE;
    }
/*      KILLRET("Auth_write: broken pipe for %s@%s.", "(broken pipe)");*/
    closesocket( a->afd );
    a->state = AS_TOOPEN;
    if( a->times == sysdata.ident_retries - 1 )
    {
      STRFREE( a->d->user );
      a->d->user = STRALLOC( "(pipe_breaking)" );
    }
    return TRUE;  /* Dont kill AUTH_DATA */
  }
  a->state = AS_TOREAD;
  return TRUE;
}

bool auth_open( AUTH_DATA * a, CHAR_DATA * ch )
{
  struct sockaddr_in sock;
  struct servent *serv;
  int err;
#ifdef WIN32
  unsigned int arg = 1;
#endif

  a->afd = socket( AF_INET, SOCK_STREAM, 0 );
  err = errno;
  if( a->afd < 0 )
  {
    perror( "auth_open: socket" );
    if( err == EAGAIN )
    {
      bug( "Auth_open: can't allocate filedesc for %s@%s.", ( ch ? ch->name : "(unknown)" ), a->d->host );
      --a->times;
      return TRUE;
    }
    KILLRET( "Auth_open: unknown socket error", "(socket_error)" );
  }
#ifndef FNDELAY
#define FNDELAY O_NDELAY
#endif
#ifdef WIN32
  if( ioctlsocket( a->afd, FIONBIO, &arg ) == -1 )
#else
  if( fcntl( a->afd, F_SETFL, FNDELAY ) < 0 )
#endif
  {
    perror( "auth_open: fcntl" );
    KILLRET( "Auth_open: unknown fcntl error", "(fcntl_error)" );
  }
  sock = a->them;
  serv = getservbyname( "ident", "tcp" );
  if( !serv )
    sock.sin_port = htons( 113 );
  else
    sock.sin_port = serv->s_port;
  sock.sin_family = AF_INET;

  if( connect( a->afd, ( struct sockaddr * )&sock, sizeof( sock ) ) < 0
#ifdef WIN32
      && errno != WSAEINPROGRESS )
#else
      && errno != EINPROGRESS )
#endif
  {
#ifndef WIN32
    if( errno != ECONNREFUSED )
    {
      perror( "auth_open: connect" );
      KILLRET( "Auth_open: unknown connect error", "(connect_error)" );
    }
#endif
    KILLRET( "Auth_open: connection refused", "(connect_refused)" );
  }
  a->state = AS_TOSEND;
  return TRUE;
}

#undef KILLRET

#define ENDRET(bs, us) \
do { \
  bug(bs, (ch ? ch->name : "(unknown)"), d->host); \
  STRFREE(d->user); \
  d->user = STRALLOC(us); \
  return; \
} while(0)
void set_auth( DESCRIPTOR_DATA * d )
{
  CHAR_DATA *ch = ( d->original ? d->original : d->character );
  AUTH_DATA *a;
  struct sockaddr_in us, them;
  int ulen = sizeof( us ), tlen = sizeof( them );

  /*
   * To stop an uninitialized memory read --Shaddai 
   */
  us.sin_port = 0;
  them.sin_port = 0;

  if( sysdata.ident_retries <= 0 )
  {
    STRFREE( d->user );
    d->user = STRALLOC( "(ident-not-active)" );
    return;
  }

  if( getsockname( d->descriptor, ( struct sockaddr * )&us, &ulen ) < 0 )
  {
    perror( "set_auth: getsockname" );
    ENDRET( "Set_auth: getsockname error for %s@%s.", "(getsockname_error)" );
  }
  if( getpeername( d->descriptor, ( struct sockaddr * )&them, &tlen ) < 0 )
  {
    perror( "set_auth: getpeername" );
    ENDRET( "Set_auth: getpeername error for %s@%s.", "(getpeername_error)" );
  }
  CREATE( a, AUTH_DATA, 1 );
  a->d = d;
  a->state = AS_TOOPEN;
  a->times = sysdata.ident_retries;
  a->us = us;
  a->them = them;
  LINK( a, first_auth, last_auth, next, prev );
  STRFREE( d->user );
  d->user = STRALLOC( "(in_progress)" );
  return;
}

void kill_auth( DESCRIPTOR_DATA * d )
{
  AUTH_DATA *a;

  for( a = first_auth; a; a = a->next )
    if( a->d == d )
    {
      if( a->state != AS_TOOPEN )
        closesocket( a->afd );
      UNLINK( a, first_auth, last_auth, next, prev );
      DISPOSE( a );
      STRFREE( d->user );
      d->user = STRALLOC( "(killed)" );
      return;
    }
  return;
}