toc/
toc/account/a/
toc/area/backup/
toc/area/imc/
toc/caste/
toc/caste/backup/
toc/clans/
toc/classes/
toc/crash/
toc/gods/
toc/guilds/
toc/lname/s/
toc/maps/backup/
toc/player/a/
toc/src/
toc/system/backup/
toc/tableprog/
/****************************************************************************
 * [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[MSL];
   char system[MIL], 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 != WSAINPROGRESS)
#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, (socklen_t *) &ulen) < 0)
   {
      perror("set_auth: getsockname");
      ENDRET("Set_auth: getsockname error for %s@%s.", "(getsockname error)");
   }
   if (getpeername(d->descriptor, (struct sockaddr *) &them, (socklen_t *) &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;
}