untermud/DOC/
untermud/DOC/U/
untermud/DOC/U/U-examples/
untermud/DOC/internals/
untermud/DOC/wizard/
untermud/MISC/
untermud/MISC/dbchk/
untermud/RWHO/
untermud/RWHO/rwhod/
/*
        Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
*/

/* configure all options BEFORE including system stuff. */
#include        "config.h"
#include        "mud.h"
#include        "vars.h"
#include "RWHO/rwho.h"

#ifndef FD_SET
#define NBBY    8
#define NFDBITS (sizeof(long) * NBBY)
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      bzero((char *)(p), sizeof(*(p)))
#endif

/*
as always the network code comprises the lioness' share of the MUD.
this module supports connection maintenance and buffering for
berkeley-style tcp/ip sockets. each connection has a pair of
buffers allocated, and once it has been authenticated as a given
player's connection, an entry into a player-name hash table is
made for faster access.
the manner in which buffers are shutdown is bizarre, but it is
all handled in io_sync() to prevent writes into buffers that have
been freed. this way buffers *WILL* hang around 'till the end of
the play/run.
*/


/* you can change PHSIZ, but don't mess with PHMAG */
#define PHSIZ   31              /* width of internal name hash table */


/* Iob flags */
#define IOBOK   001             /* OK/logged in player connection */
#define IOBKILL 002             /* kill this IOB at sync time - it's dead */
#define IOBERR  004             /* ignore this IOB - it is f***ed up. */
#define IOBFL   010             /* Too many chars. Flush it. */

typedef struct {
  int flg;                      /* flags */
  char *host;                   /* name of player's host */
  time_t ltim;                  /* last input time */
  time_t ctim;                  /* connect time */
  char who[MAXOID];             /* player object ID */
  SOCKET fd;                    /* file desc */
  char *obuf;                   /* malloced output buffer */
  char *op;                     /* output buf ptr */
  char *ibuf;                   /* malloced input buffer */
  char *ip;                     /* input buf ptr */
  int ic;                       /* input byte cnt */
} Iob;




/*
player-name to Iob resolution map
an entry is made in this when the player is authenticated by the
login(), and is subsequently used to quickly map names to Iobs.
the mapping is destroyed when the iob is dropped by iobdrop().
don't mess with this code - it's icky.
*/
typedef struct pmap {
  Iob *iob;
  struct pmap *n;
} Pmap;
static Pmap *pmaptab[PHSIZ];



static Iob **iobtab;            /* active Iob table */
static Iob **WHOlist;           /* Ordered WHO list */
static int WHOlast = -1;        /* Last active iob in WHOlist */
static Iob *lastiob = (Iob *) 0;
static int onewrt = 0;          /* optimization */
static int iobtabsiz;           /* top bound of Iob table */
static int curriob;             /* highest Iob in use */
static fd_set liveset;          /* set of live Iobs for select() */
static struct timeval timo;     /* timeout value */
static SOCKET serfd;            /* inter server service port */
static SOCKET plyfd;            /* main player service port */
struct sockaddr_in addr;        /* address of newly connected */
static int sport = NET_SRVPRT;  /* service port # */
static int pport = NET_PLYPRT;  /* play port # */
static int net_initted = 0;

static int nxtwhoiobptr;        /* used for programmed Iob traverse */

static char *log_connect (SOCKET fd, char *desc);

/*
set up the iob tables, iob maps, sockets, the whole bit
*/
int io_init ()
{
  int x;

#ifndef WIN32
  signal (SIGPIPE, SIG_IGN);
#endif
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  timo.tv_sec = cron_quantum;
  timo.tv_usec = 0;

  /*
     if you ain't got dtablesize(), fudge this with whatever
     your systems max file descriptor value is. erring on the
     high side will waste a little memory is all.
   */
  iobtabsiz = getdtablesize ();

  /* initialize the Iob hash table */
  iobtab = (Iob **) malloc ((unsigned) (sizeof (Iob *) * iobtabsiz));
  if (iobtab == (Iob **) 0)
    return (1);

  WHOlist = (Iob **) malloc ((unsigned) (sizeof (Iob *) * iobtabsiz));
  if (WHOlist == (Iob **) 0)
    return (1);

  /* zero iob table */
  for (x = 0; x < iobtabsiz; x++)
    iobtab[x] = (Iob *) 0;

  /* zero player to Iob hash table */
  for (x = 0; x < PHSIZ; x++)
    pmaptab[x] = (Pmap *) 0;



  /* OPEN INTER SERVER FILE DESCRIPTOR AND BIND IT */
  if ((serfd = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
    return (1);

  x = 1;
  setsockopt (serfd, SOL_SOCKET, SO_REUSEADDR, (char *) &x, sizeof (x));

  addr.sin_port = htons (sport);
  if (bind (serfd, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR) {
    log_printf ("cannot bind socket: ", (char *) -1, "\n", 0);
    return (1);
  }

  if (listen (serfd, 5) == SOCKET_ERROR) {
    log_printf ("cannot listen at socket: ", (char *) -1, "\n", 0);
    return (1);
  }



  /* OPEN PLAYER ACCESS FILE DESCRIPTOR AND BIND IT */
  if ((plyfd = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
    return (1);

  x = 1;
  setsockopt (plyfd, SOL_SOCKET, SO_REUSEADDR, (char *) &x, sizeof (x));

  addr.sin_port = htons (pport);
  if (bind (plyfd, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR) {
    log_printf ("cannot bind socket: ", (char *) -1, "\n", 0);
    return (1);
  }

  if (listen (plyfd, 5) == SOCKET_ERROR) {
    log_printf ("cannot listen at socket: ", (char *) -1, "\n", 0);
    return (1);
  }

  /* VITAL! */
  FD_SET (plyfd, &liveset);
  FD_SET (serfd, &liveset);
  curriob = plyfd;

  net_initted++;
  return (0);
}




/*
disconnect an Iob (low level)
*/
static void iobdrop (Iob * ip)
{
  int x;
  Pmap *pp;
  Pmap *pr;

  if (ip == (Iob *) 0)
    return;

  /* Blow it out of WHO list */

  for (x = 0; x <= WHOlast; x++) {
    if (WHOlist[x] == ip) {
      if (x < WHOlast)
        bcopy (&WHOlist[x + 1], &WHOlist[x], (WHOlast - x) * sizeof (Iob *));
      else
        WHOlist[x] = (Iob *) 0;
      WHOlast--;
      break;
    }
  }

  if (ip->who[0]) {
    plogf ("DISCONNECT %s on %d from %s\n", ip->who, ip->fd,
      ip->host ? ip->host : "unknown");
#ifdef  USE_RWHO
    rwhocli_userlogout (ip->who);
#endif
  } else
    plogf ("DISCONNECT %d from %s\n", ip->fd,
      ip->host ? ip->host : "unknown");

  for (x = 0; x < iobtabsiz; x++) {
    if (iobtab[x] == ip) {
      iobtab[x] = (Iob *) 0;
      break;
    }
  }

  /* unlink player map hash. this is somewhat convoluted. */
  for (x = 0; x < PHSIZ; x++) {
    pp = pmaptab[x];
    while (pp != (Pmap *) 0) {
      if (pp->iob == ip && pp == pmaptab[x]) {
        pr = pp;
        pmaptab[x] = pp->n;
        free ((mall_t) pr);
        break;
      } else {
        if (pp->n != (Pmap *) 0 && pp->n->iob == ip) {
          pr = pp->n;
          pp->n = pp->n->n;
          free ((mall_t) pr);
          break;
        }
      }
      pp = pp->n;
    }
  }


  /* adjust top iob in use count */
  if (ip->fd == curriob)
    curriob--;

  /* mark it dead in the live Iob set */
  FD_CLR (ip->fd, &liveset);

  /* final cleanup */
  shutdown (ip->fd, 2);
  closesocket (ip->fd);

  if (ip->host != (char *) 0)
    free ((mall_t) ip->host);

  free ((mall_t) ip->ibuf);
  free ((mall_t) ip->obuf);

  /*
     this is done to cause a coredump in case someone's code is
     so stupid as to write into an Iob that has been closed down
   */
  ip->ibuf = ip->obuf = (char *) 0;
  free ((mall_t) ip);
}




/*
tokenize out a single line of text (destructive)
this function handles a buffer of player input, returning a sequence of
lines - it may move data around in the buffer, and may alter the content
of the buffer as it does so. this handles cases where a line may be
broken into two packets by the network - it re-assembles them efficiently.
*/
static char *iobgl (Iob * i)
{
  char *op;
  int ic;

  while (i->ic > 0 && (*i->ip == '\n' || *i->ip == '\r')) {
    *i->ip++ = '\0';
    i->ic--;
  }

  if ((ic = i->ic) <= 0) {
    i->ip = i->ibuf;
    i->ic = 0;
    return ((char *) 0);
  }

  op = i->ip;

  while (1) {
    if (*i->ip == '\r' && *(i->ip + 1) == '\n' && ic > 1) {
      *i->ip++ = '\0';
      *i->ip++ = '\0';
      if ((i->ic = ic - 2) < 1)
        i->ip = i->ibuf;

      if (i->flg & IOBFL) {     /* If we're supposed to flush */
        i->flg &= ~IOBFL;
        return ((char *) 0);
      } else {
        return (op);
      }
    }

    if ((*i->ip == '\n' || *i->ip == '\r') && ic > 0) {
      *i->ip++ = '\0';
      if ((i->ic = --ic) < 1)
        i->ip = i->ibuf;
      if (i->flg & IOBFL) {     /* If we're supposed to flush */
        i->flg &= ~IOBFL;
        return ((char *) 0);
      } else {
        return (op);
      }
    }

    /* nothing left in buffer, but no newline */
    if (--ic <= 0) {
      if (i->ic >= MUDBUF - 1) {        /* Too Much! */
        i->ip = i->ibuf;
        i->ic = 0;

        /* Are we already flushing? */

        if (i->flg & IOBFL) {
          return ((char *) 0);
        } else {
          /* set up to flush until EOLN */

          i->flg |= IOBFL;
          *(i->ibuf + (MUDBUF - 1)) = '\0';
          return (i->ibuf);
        }
      }

      /* shift down */
      bcopy (op, i->ibuf, i->ic);

      /* resync */
      i->ip = i->ibuf;
      /* i->ic = 0; Why is this here? -- Andrew */
      return ((char *) 0);
    }

    if (!isprint (*i->ip))
      *i->ip = ' ';
    i->ip++;
  }
}



/*
flush a single Iob
doing the select there significantly increases the system call overhead
of the MUD, as opposed to doing nonblocking I/O. but that's life.
*/
static int iobflush (Iob * ip)
{
  static char buff_over[] = "\n<buffer overrun>\n";
  fd_set wfd;
  struct timeval wtimo;
  int xx = ip->op - ip->obuf;
  int wr;

  if (ip->flg & IOBERR)
    return (1);

  FD_ZERO (&wfd);
  FD_SET (ip->fd, &wfd);

  wtimo.tv_sec = 0;
  wtimo.tv_usec = 800;

  if (select (ip->fd + 1, (fd_set *) 0, &wfd, (fd_set *) 0, &wtimo) != 1 ||
    !FD_ISSET (ip->fd, &wfd)) {

    if (xx < MUDBUF)
      return (0);

    /* scrubba buffa */
    bcopy (buff_over, ip->obuf, sizeof (buff_over));
    ip->op = ip->obuf + sizeof (buff_over);

    return (0);
  }


  if ((wr = send (ip->fd, ip->obuf, xx, 0)) != xx) {
    /* partial write. god damn */
    if (wr > 0) {
      bcopy (ip->obuf + wr, ip->obuf, xx - wr);
      ip->op -= wr;
      return (0);
    }

    if (wr == SOCKET_ERROR && GETERROR == EWOULDBLOCK) {
      if (xx >= MUDBUF) {
        /* scrubba buffa */
        bcopy (buff_over, ip->obuf, sizeof (buff_over));
        ip->op = ip->obuf + sizeof (buff_over);
      }
      return (0);
    }

    /* fukit */
    ip->flg |= IOBERR;
    return (1);
  }
  ip->op = ip->obuf;
  return (0);
}




/* VARARGS1 */
void say (char *who, ...)
{
  Pmap *p;
  char *s;
  va_list ap;

  if (who == (char *) 0 || who[0] == '\0' || !net_initted)
    return;

  for (p = pmaptab[objid_hash (who, PHSIZ)]; p != (Pmap *) 0; p = p->n) {

    /* wrong guy */
    if ((p->iob->flg & IOBERR) || strcmp (p->iob->who, who))
      continue;

    if (lastiob != p->iob) {
      lastiob = p->iob;
      onewrt++;
    }

    va_start (ap, who);
    while ((s = va_arg (ap, char *)) != (char *) 0) {
      while (*s) {
        if ((p->iob->op - p->iob->obuf) > MUDBUF - 1) {
          if (iobflush (p->iob))
            goto dropthru;
        } else {
          *p->iob->op++ = *s++;
        }
      }
    }
  dropthru:
    va_end (ap);
  }
}




#ifdef CONNONLY
int playerconn (char *who)
{
  Pmap *p;

  if (who == (char *) 0 || who[0] == '\0' || !net_initted)
    return (0);

  for (p = pmaptab[objid_hash (who, PHSIZ)]; p != (Pmap *) 0; p = p->n) {
    if ((p->iob->flg & IOBERR) || strcmp (p->iob->who, who))
      /* wrong guy */
      continue;
    else
      return (1);
  }
  return (0);
}
#endif




void io_logoff (char *who)
{
  Pmap *p;

  if (who == (char *) 0 || who[0] == '\0' || !net_initted)
    return;

  p = pmaptab[objid_hash (who, PHSIZ)];
  while (p != (Pmap *) 0) {
    if (strcmp (p->iob->who, who)) {
      p = p->n;
      continue;
    }
    p->iob->flg |= IOBKILL;
    onewrt = 2;
    p = p->n;
  }
}




/* VARARGS1 */
void iobsay (Iob * ip, ...)
{
  char *s;
  va_list ap;

  if (ip->flg & IOBERR)
    return;

  if (lastiob != ip) {
    lastiob = ip;
    onewrt++;
  }

  va_start (ap, ip);
  while ((s = va_arg (ap, char *)) != (char *) 0) {
    while (*s) {
      if ((ip->op - ip->obuf) > MUDBUF - 1) {
        if (iobflush (ip))
          return;
      } else {
        *ip->op++ = *s++;
      }
    }
  }
  va_end (ap);
}




/*
flush the connected (valid) Iobs
*/
void io_sync ()
{
  int n;
  time_t now = (time_t) 0;

  if (!onewrt || !net_initted)
    return;

  /* WARNING!!! the calls to goodbye() actually may make more
     Iob writes as a result of a player hangup! Do NOT do much
     in goodbye!() or madness may result. */
  if (onewrt == 1 && lastiob != (Iob *) 0) {
    if (iobflush (lastiob)) {
      if (lastiob->flg & IOBOK)
        goodbye (lastiob->who);
      iobdrop (lastiob);
      return;
    }

    if (lastiob->flg & IOBERR || lastiob->flg & IOBKILL) {
      if (lastiob->flg & IOBOK)
        goodbye (lastiob->who);
      iobdrop (lastiob);
    }
    return;
  }

  for (n = 0; n < iobtabsiz; n++) {
    if (iobtab[n] != (Iob *) 0) {
      if (iobtab[n]->op > iobtab[n]->obuf) {
        if (iobflush (iobtab[n])) {
          if (iobtab[n]->flg & IOBOK)
            goodbye (iobtab[n]->who);
          iobdrop (iobtab[n]);
          continue;
        }
      }

      if (iobtab[n]->flg & IOBKILL || iobtab[n]->flg & IOBERR) {
        if (iobtab[n]->flg & IOBOK)
          goodbye (iobtab[n]->who);
        iobdrop (iobtab[n]);
        continue;
      }

      /* timeout logins */
      if (!(iobtab[n]->flg & IOBOK)) {
        if (now == (time_t) 0)
          time (&now);
        if (now - iobtab[n]->ctim > 80)
          iobdrop (iobtab[n]);
      }
    }
  }

  onewrt = 1;
  lastiob = (Iob *) 0;
}




/*
wrapper around the player authentication routine. said routine will
return a 1 if the login is valid, a 0 if it is not. if the login is
OK, we then fiddle some Iob values and make a hash-table map entry.
if login() returns -1, then we are to close the connection.
*/
static void io_dologin (Iob * bp, char *line)
{
  Pmap *pm;
  int hv;
  FILE *cfl;
  char cbuf[BUFSIZ];

  if (newusers) {
    /* Check for new user login */
    if (!strncmp ("new", line, 3) || !strncmp ("New", line, 3)) {
      hv = newlogin (line, bp->who);
    } else {
      hv = login (line, bp->who);
    }
  } else {
    if (!strncmp ("new", line, 3) || !strncmp ("New", line, 3)) {
      iobsay (bp, "New users are not enabled.\n", (char *) 0);
      return;
    }
    hv = login (line, bp->who);
  }

  if (hv == 2) {
    iobsay (bp, "Your new player ID is ", bp->who,
      ".  Don't forget it, you need it to log back in.\n", (char *) 0);

    /* Show the new user information file */
    if ((cfl = fopen ("NEWS/newuser.txt", "rb")) != (FILE *) 0) {
      while (fgets (cbuf, sizeof (cbuf), cfl) != (char *) 0)
        iobsay (bp, cbuf, (char *) 0);
      (void) fclose (cfl);
    }
  }

  if (hv == 0) {
    iobsay (bp,
      "Either that object does not exist, or the password was incorrect.\n",
      (char *) 0);
    /*log_printf("badlogin: ",line,"\n",(char *)0); */
    plogf ("badlogin: %s on %d from %s\n", line, bp->fd,
      bp->host ? bp->host : "unknown");
    return;
  }

  if (hv == -1) {
    plogf ("killconnection: '%s' on %d from %s\n", line, bp->fd,
      bp->host ? bp->host : "unknown");
    bp->flg |= IOBKILL;
    onewrt = 2;
    return;
  }

  bp->flg |= IOBOK;

  /* Stuff this Iob into the WHO list */

  WHOlist[++WHOlast] = bp;

  /* now add a pointer to the character's name in the Iob hash */
  pm = (Pmap *) malloc (sizeof (Pmap));
  if (pm == (Pmap *) 0)
    fatal ("out of memory building new connection\n", (char *) 0);

  pm->iob = bp;
  pm->n = pmaptab[(hv = objid_hash (bp->who, PHSIZ))];
  pmaptab[hv] = pm;

  plogf ("CONNECT %s on %d from %s\n", bp->who, bp->fd,
    bp->host ? bp->host : "unknown");
  welcome (bp->who);
#ifdef  USE_RWHO
  rwhocli_userlogin (bp->who, ut_name (bp->who), bp->ctim);
#endif
}



/*
main I/O loop - listens for new connections, accepts them, validates
them, reads input, and dispatches it.
*/
int io_loop ()
{
  fd_set redy;
  fd_set xcpt;
  Iob *bp;
  SOCKET n;
  int seld;
  int rd;
  char *lp;
  time_t now;
  char *host;

  if (!net_initted)
    return (-1);

  /* sleep quantum may have changed */
  timo.tv_sec = cron_quantum;

  bcopy (&liveset, &redy, sizeof (redy));
  bcopy (&liveset, &xcpt, sizeof (xcpt));

  if ((seld =
      select (curriob + 1, &redy, (fd_set *) 0, &xcpt,
        &timo)) == SOCKET_ERROR) {
    if (GETERROR != EINTR) {
      log_printf ("select in loop failed: ", (char *) -1, "\n", 0);
      return (1);
    }
  }

  /* yawn */
  if (seld <= 0)
    return (0);

  /* start the clock */
  (void) time (&now);

  /* new SERVER TO SERVER connection */
  if (FD_ISSET (serfd, &redy)) {

    n = accept (serfd, (struct sockaddr *) 0, (int *) 0);

    /* xact_in() handles *everything* including closing fd */
    if (n != INVALID_SOCKET) {
#ifdef  SO_LINGER
#ifdef        hpux
/*
*  HP-UX (release 7.0 B on a 9000/330, at least) claims to default to no-
*    lingering and, in any case, has a different syntax for passing the
*    linger value to setsockopt().  Don't use it.
*                                                    --- warlock
*/
#undef        SO_LINGER
#endif /*  hpux  */
/*
                        struct  linger  ling;

                        ling.l_onoff = 0;
                        ling.l_linger = 0;
                        setsockopt(n,SOL_SOCKET,SO_LINGER,&ling,sizeof(ling));
*/
#endif
      host = log_connect (n, "server");
      if (host != (char *) 0)   /* Toss the host in this case */
        free ((mall_t) host);
      xact_in (n);
    }
  }


  /* new PLAYER connection */
  if (FD_ISSET (plyfd, &redy)) {
    n = accept (plyfd, (struct sockaddr *) 0, (int *) 0);

    if (n != INVALID_SOCKET) {
#ifdef  SO_LINGER
/*
FIXME
                        struct  linger  ling;

                        ling.l_onoff = 0;
                        ling.l_linger = 0;
                        setsockopt(n,SOL_SOCKET,SO_LINGER,&ling,sizeof(ling));
*/
#endif
#ifdef WIN32
      unsigned long flags = 1;
#endif
      host = log_connect (n, "user");

#ifdef WIN32
      if (ioctlsocket (n, FIONBIO, &flags)) {
#else
      if (fcntl (n, F_SETFL, FNDELAY)) {
#endif
        log_printf ("cannot set NDELAY: ", (char *) -1, "\n", 0);
        goto mainloop;
      }

      bp = (Iob *) malloc (sizeof (Iob));
      if (bp == (Iob *) 0) {
        log_printf ("cannot alloc Iob: ", (char *) -1, "\n", 0);
        return (-1);
      }
      bp->host = host;
      bp->flg = 0;
      bp->who[0] = '\0';
      bp->fd = n;
      bp->ctim = now;

      if ((bp->ibuf = (char *) malloc (MUDBUF)) == (char *) 0)
        return (-1);
      bp->ip = bp->ibuf;
      bp->ic = 0;

      if ((bp->obuf = (char *) malloc (MUDBUF)) == (char *) 0)
        return (-1);
      bp->op = bp->obuf;

      if (n > curriob)
        curriob = n;

      /* enter it into our live set */
      FD_SET (n, &liveset);
#ifdef WIN32
      {
        int x;
        for (x = 0; x < iobtabsiz; x++) {
          if (iobtab[x] == NULL) {
            iobtab[x] = bp;
            break;
          }
        }
      }
#else
      iobtab[n] = bp;
#endif

      /* I added this next little block so I could display
         a file before the character logs in. Ed Hand 6/26/91 */
      {
        FILE *cfl;
        char cbuf[BUFSIZ];

        if ((cfl = fopen ("NEWS/connect.txt", "rb")) != (FILE *) 0) {
          while (fgets (cbuf, sizeof (cbuf), cfl) != (char *) 0)
            iobsay (bp, cbuf, (char *) 0);
          (void) fclose (cfl);
        }
      }
      iobsay (bp, "If you have a character already,\n",
        "type: c[onnect] objectid password\n", (char *) 0);
      if (newusers) {
        iobsay (bp, "To create a new character,\n", (char *) 0);
        iobsay (bp, "type: new charname password\n", (char *) 0);
      } else {
        iobsay (bp, "New users are not enabled.\n", (char *) 0);
      }
    }
  }

mainloop:
  /* check input on existing fds. */
#ifdef WIN32
  for (n = 0; n < iobtabsiz; n++) {
#else
  for (n = 0; n <= curriob; n++) {
#endif
    bp = iobtab[n];
    if (bp == NULL)
      continue;

    if (FD_ISSET (bp->fd, &redy)) {
      rd = recv (bp->fd, bp->ibuf + bp->ic, MUDBUF - bp->ic - 1, 0);
      if (rd <= 0) {
        bp->flg |= IOBERR | IOBKILL;
        onewrt = 2;
        continue;
      }

      /* increment count of bytes in buffer */
      bp->ic += rd;

      /* adjust last active time */
      bp->ltim = now;

      /* process input based on state of connection */
      while ((lp = iobgl (bp)) != (char *) 0) {
        if (bp->flg & IOBOK)
          run (bp->who, bp->who, lp, 0, (char **) 0, 1);
        else
          io_dologin (bp, lp);
      }
      continue;
    }

    /* lastly, check exceptions in case of no input */
    if (FD_ISSET (bp->fd, &xcpt)) {
      if (bp->fd == serfd)
        fatal ("server service fd died!!\n", (char *) 0);
      if (bp->fd == plyfd)
        fatal ("player service fd died!!\n", (char *) 0);

      /* default case */
      if (bp != (Iob *) 0) {
        bp->flg |= IOBERR | IOBKILL;
        onewrt = 2;
      }
      continue;
    }
  }

  /* e! */
  return (0);
}




/* ARGSUSED */
int cmd__netconfig (int argc, char *argv[], char *who, char *aswho)
{
  static char *nactm = "network layer is already active.\n";
  static char *badp = "invalid port number.\n";

  /* configure service port */
  if (!strcmp (argv[1], "playport")) {
    int tmpx;

    if (net_initted) {
      log_printf (nactm, (char *) 0);
      return (UERR_FATAL);
    }

    if (argc < 3 || (tmpx = atoi (argv[2])) <= 0) {
      log_printf (badp, (char *) 0);
      return (UERR_FATAL);
    }
    pport = tmpx;
    log_printf ("player port is #", argv[2], "\n", (char *) 0);
    return (UERR_NONE);
  }

  /* configure service port */
  if (!strcmp (argv[1], "servport")) {
    int tmpx;

    if (net_initted) {
      log_printf (nactm, (char *) 0);
      return (UERR_FATAL);
    }

    if (argc < 3 || (tmpx = atoi (argv[2])) <= 0) {
      log_printf (badp, (char *) 0);
      return (UERR_FATAL);
    }
    sport = tmpx;
    log_printf ("server port is #", argv[2], "\n", (char *) 0);
    return (UERR_NONE);
  }

  /* set time-out of select (to match quantum?) */
  if (!strcmp (argv[1], "looptime")) {
    int tmpx;

    if (argc < 3 || (tmpx = atoi (argv[2])) <= 0) {
      log_printf (badp, (char *) 0);
      return (UERR_BADPARM);
    }
    timo.tv_sec = tmpx;
    log_printf ("loop timeout is ", argv[2], " seconds\n", (char *) 0);
    return (UERR_NONE);
  }

  if (!strcmp (argv[1], "help")) {
    say (who, argv[0], " playport port-number\n", (char *) 0);
    say (who, argv[0], " servport port-number\n", (char *) 0);
    say (who, argv[0], " looptime seconds\n", (char *) 0);
    return (UERR_NONE);
  }

  log_printf ("_netconfig: I don't understand ", argv[1], "\n", (char *) 0);
  return (UERR_BADPARM);
}

static void cmd_WHO__format (char *buffer, int flg, char *name,
  time_t curtime, time_t ltime, char *objname, char *host)
{
  int ct_hours, ct_mins, ct_secs, lt_hours, lt_mins, lt_secs;
  char oidb[MAXOID + 3];

  ct_hours = curtime / 3600;
  ct_mins = (curtime % 3600) / 60;
  ct_secs = curtime % 60;

  lt_hours = ltime / 3600;
  lt_mins = (ltime % 3600) / 60;
  lt_secs = ltime % 60;

  if (host == (char *) 0)
    host = "unknown";

  if (flg) {                    /* Wiz WHO thang */
    oidb[0] = '(';
    strncpy (oidb + 1, objname, MAXOID);
    strcat (oidb, ")");
    sprintf (buffer,
      "%-17.17s  %3d:%02d:%02d %3d:%02d:%02d %-18.18s %-18.18s\n",
      name,
      ct_hours, ct_mins, ct_secs, lt_hours, lt_mins, lt_secs, oidb, host);

  } else {                      /* Regular WHO */
    sprintf (buffer, "%-17.17s  %3d:%02d:%02d %3d:%02d:%02d (%s)\n",
      name, ct_hours, ct_mins, ct_secs, lt_hours, lt_mins, lt_secs, objname);
  }
}


/* TCP version of WHO */
/* ARGSUSED */
int cmd_WHO (int argc, char *argv[], char *who, char *aswho)
{
  int x;
  int jj;
  int wiz;
  time_t now;
  char xuf[180];

  if (!net_initted)
    return (1);

  wiz = ut_flagged (aswho, var_wiz);
  (void) time (&now);
  say (who, "Player Name          On For    Idle\n", (char *) 0);

  if (argc < 2) {
    for (x = 0; x <= WHOlast; x++) {
      cmd_WHO__format (xuf, wiz,
        ut_name (WHOlist[x]->who),
        (now - WHOlist[x]->ctim),
        (now - WHOlist[x]->ltim), WHOlist[x]->who, WHOlist[x]->host);
      say (who, xuf, (char *) 0);
    }
    say (who, "There are ", itoa (WHOlast + 1, xuf, 10),
      " players connected.\n", (char *) 0);

    return (UERR_NONE);
  }

  for (jj = 1; jj < argc; jj++) {
    char *na;

    for (x = 0; x <= WHOlast; x++) {
      na = ut_name (WHOlist[x]->who);
      if (na != (char *) 0 && matchstr (na, argv[jj], 0) != 0) {
        cmd_WHO__format (xuf, wiz,
          ut_name (WHOlist[x]->who),
          (now - WHOlist[x]->ctim),
          (now - WHOlist[x]->ltim), WHOlist[x]->who, WHOlist[x]->host);
        say (who, xuf, (char *) 0);
      }
    }
    say (who, "There are ", itoa (WHOlast + 1, xuf, 10),
      " players connected.\n", (char *) 0);
  }
  return (UERR_NONE);
}




/* reset pointer to live Iobs for programmed traverse */
void io_rstnxtwho (void)
{
  nxtwhoiobptr = 0;
}




/* programmed traverse of object-IDs of logged-in players */
char *io_nxtwho (time_t * timp)
{
#ifdef WIN32
  if (nxtwhoiobptr > iobtabsiz || !net_initted)
#else
  if (nxtwhoiobptr > curriob || !net_initted)
#endif
    return ((char *) 0);

#ifdef WIN32
  while (nxtwhoiobptr < iobtabsiz) {
#else
  while (nxtwhoiobptr <= curriob) {
#endif
    if (iobtab[nxtwhoiobptr] != (Iob *) 0 &&
      (iobtab[nxtwhoiobptr]->flg & IOBOK) &&
      (iobtab[nxtwhoiobptr]->flg & (IOBKILL | IOBERR)) == 0 &&
      iobtab[nxtwhoiobptr]->who[0] != '\0') {
      if (timp != (time_t *) 0)
        *timp = iobtab[nxtwhoiobptr]->ctim;
      return (iobtab[nxtwhoiobptr++]->who);
    }

    nxtwhoiobptr++;
  }
  return ((char *) 0);
}

/* Log (via logf) the essential statistics of a new connection. */
static char *log_connect (SOCKET fd, char *desc)
{
  struct sockaddr_in sin;
  char *t;
  char *name;
  int sin_len;
#ifndef NO_HUGE_RESOLVER_CODE
  struct hostent *hp;
#endif

  sin_len = sizeof (sin);
  if (getpeername (fd, (struct sockaddr *) &sin, &sin_len) == SOCKET_ERROR) {
    plogf ("ACCEPT [%s] getpeername failed! on %d\n", desc, fd);
    return ((char *) 0);
  }
#ifndef NO_HUGE_RESOLVER_CODE
  hp = gethostbyaddr ((char *) &sin.sin_addr, sizeof (struct in_addr),
    AF_INET);
  if (hp != (struct hostent *) 0) {
    plogf ("ACCEPT [%s] from %s on %d\n", desc, hp->h_name, fd);
    if ((name = (char *) malloc (strlen (hp->h_name) + 1)) == (char *) 0)
      return ((char *) 0);
    strcpy (name, hp->h_name);
    return (name);
  }
#endif

  t = inet_ntoa (sin.sin_addr);
  if (t == (char *) 0) {
    plogf ("ACCEPT [%s] inet_ntoa failed! on %d\n", fd);
    return ((char *) 0);
  }


  plogf ("ACCEPT [%s] from %s on %d\n", desc, t, fd);
  if ((name = (char *) malloc (strlen (t) + 1)) == (char *) 0)
    return ((char *) 0);
  strcpy (name, t);
  return (name);
}