/
teeny/db/
teeny/dbm/
teeny/docs/
teeny/includes/
teeny/misc/
teeny/news/
teeny/text/
/* tcpio.c */

#include "copyright.h"
#include "config.h"

#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/errno.h>
extern int      errno;

#ifdef SYSV_TCPIP
#include <sys/file.h>
#endif                                /* SYSV_TCPIP */
#include <sys/socket.h>
#ifdef SELECT_H
#include <sys/select.h>
#endif				/* SELECT_H */
#ifdef SYSV_TCPIP
#include <sys/in.h>
#include <sys/inet.h>
#else
#include <netinet/in.h>
#endif                                /* SYSV_TCPIP */
#ifdef STDDEF_H
#include <stddef.h>
#else				/* STDDEF_H */
#define size_t unsigned
#endif				/* STDDEF_H */
#include <netdb.h>

#include "teeny.h"
#include "io.h"

extern char    *malloc();
extern void     free();

/* old iob handles are kept around, since we may go through these fast */
static Iob     *freeiob = (Iob *) 0;

/* pointer to active Iobs by file descriptor */
static Iob    **iobindex;
static int      iobindexsiz;

/* a real hack, but fun. - figure it out. */
static Iob     *lastiob = (Iob *) 0;
static int      onewrt = 0;


/* TCP specific stuff. */
static int      serfd;
struct sockaddr_in addr;



/*
 * set everything up for I/O. only call ONCE! Need I add 'or else' ? 
 */
ioinit()
{
  int             x, temp;

  /*
   * 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. 
   */
  iobindexsiz = getdtablesize();


  /* prep index */
  iobindex = (Iob **) malloc((size_t) (sizeof(Iob) * iobindexsiz));
  if (iobindex == (Iob **) 0)
    return (1);

  for (x = 0; x < iobindexsiz; x++)
    iobindex[x] = (Iob *) 0;

  if ((serfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    return (1);

  temp = 1;
  if (setsockopt(serfd, SOL_SOCKET, SO_REUSEADDR, (char *) &temp,
		 sizeof(temp)) < 0)
  {
    warning("ioinit", "setsockopt fail");
    return (1);
  }
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(mud_port);

  if (bind(serfd, (struct sockaddr *) & addr, sizeof(addr)))
  {
    warning("ioinit", "cannot bind socket");
    return (1);
  }
  if (listen(serfd, 5) == -1)
  {
    warning("ioinit", "cannot listen at socket");
    return (1);
  }
  return (0);
}



/*
 * drop an Iob from the active list, stack it on the free list, and make sure
 * everything is cleaned. if we reach here, we are DAMN sure not to flush it
 * - who knows what state the connection is in. 
 */
void
iobdrop(iob)
  Iob            *iob;
{
  int             x;

  for (x = 0; x < iobindexsiz; x++)
  {
    if (iobindex[x] == iob)
    {
      (void) shutdown(x, 2);
      (void) close(x);

      /* mark it as dead */
      iob->state = IOSTATE_HUNGUP;

      /* de-index (very important) */
      iobindex[x] = (Iob *) 0;

      /* Do this now, 'cause isalive() needs  */
      /* this index entry set to zero to work */

      if (iob->typ == INPUT_PLAY)
      {
	dropwho(iob);
	if (!isalive(iob->player))
	{
	  deanimate(iob->player);
	}
      }
      if (iob->outputprefix)
      {
	(void) free((char *) iob->outputprefix);
	iob->outputprefix = NULL;
      }
      if (iob->outputsuffix)
      {
	(void) free((char *) iob->outputsuffix);
	iob->outputsuffix = NULL;
      }

      /*
       * put the Iob on free chain pressing the buf-ptr as a list ptr Do this
       * AFTER the dropwho, else the dropwho() will spam the freelist
       * (freelist uses who list ptrs) 
       */
      iob->whofwd = freeiob;
      freeiob = iob;

      break;
    }
  }
}

/*
 * returns true if the specified player is still connected somewhere or
 * other. 
 *
 */

isalive(player)
  int             player;
{
  int             x;

  if (player < 0)
    return (0);

  for (x = 0; x < iobindexsiz; x++)
  {
    if (iobindex[x] != (Iob *) 0 && iobindex[x]->player == player)
      return (1);
  }
  return (0);
}

/*
 * iowrap is responsible for shutting down and cleaning everything up. 
 */
void
iowrap()
{
  int             x;
  Iob            *bp;

  /* shut down bound socket */
  (void) shutdown(serfd, 2);
  (void) close(serfd);

  /* shut down any I/O ports open */
  for (x = 0; x < iobindexsiz; x++)
    if (iobindex[x] != (Iob *) 0)
    {
      iobdrop(iobindex[x]);
    }
  for (bp = freeiob; bp != (Iob *) 0;)
  {
    Iob            *np = (Iob *) bp->whofwd;

    (void) free((char *) bp);
    bp = np;
  }
  (void) free((char *) iobindex);
  (void) shutdown(serfd, 2);
  (void) close(serfd);
}


static char    *
addrout(a)
  long            a;
{
  static char     buf[32];

#ifdef HOSTNAMES
  struct hostent *he;

  he = gethostbyaddr(&a, sizeof(a), AF_INET);
  if (he)
    return he->h_name;
#endif				/* HOSTNAMES */

#ifndef BACKWARDS_IP
  sprintf(buf, "%d.%d.%d.%d", (a >> 24) & 0xff, (a >> 16) & 0xff,
	  (a >> 8) & 0xff, a & 0xff);
#else
  sprintf(buf, "%d.%d.%d.%d", a & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff,
	  (a >> 24) & 0xff);
#endif				/* BACKWARDS_IP */
  return buf;
}


/*
 * main loop. run through all the active FDs once, handle new connections,
 * and return. 
 */
ioloop()
{
  fd_set          ready;
  fd_set          xcpt;
  Iob            *bp;
  register int    n;
  int             bcnt;
  struct timeval  timo;
  int             scnt;
  struct timeval  currenttime;
  struct sockaddr_in theaddr;
  int             thelen;
  extern char    *ctime();

  timo.tv_sec = 60;
  timo.tv_usec = 5;

  /* check for new connections */
  FD_ZERO(&ready);
  FD_ZERO(&xcpt);

  /* set the fds to check */
  FD_SET(serfd, &ready);
  for (n = 0; n < iobindexsiz; n++)
  {
    if (iobindex[n] != (Iob *) 0)
    {
      FD_SET(n, &ready);
      FD_SET(n, &xcpt);
    }
  }

  if ((scnt = select(iobindexsiz, &ready, (fd_set *) 0, &xcpt, &timo)) < 0)
  {
    if (errno != EINTR)
    {
      warning("ioloop", "select in loop failed");
      return (1);
    }
  }
  /* Cope with resetting quotas, and running timed events and stuff */

  timers();
  (void) gettimeofday(&currenttime, (struct timezone *) 0);

  /* not a very interesting run, eh ? */
  if (scnt <= 0)
    return (0);

  /* check new connections */
  if (FD_ISSET(serfd, &ready))
  {
    thelen = sizeof(theaddr);
    n = accept(serfd, &theaddr, &thelen);
    if (n != -1)
    {

      /* allocate a buffer */
      if (freeiob != (Iob *) 0)
      {
	bp = freeiob;
	freeiob = (Iob *) freeiob->whofwd;
      } else
      {
	bp = (Iob *) malloc((size_t) sizeof(Iob));
	if (bp == (Iob *) 0)
	{
	  warning("ioloop"
		  ,"cannot malloc a new Iob");
	  return (1);
	}
      }

      /* initialize it */
      bp->typ = INPUT_NEWCONN;
      bp->player = -1;
      bp->state = IOSTATE_OK;
      bp->fd = n;
      bp->quota = SLICE_QUOTA;
      bp->connect = currenttime.tv_sec;
      bp->lastcmd = currenttime.tv_sec;
      bp->blown = 0;
      *bp->outputbuf = '\0';
      bp->outputcnt = 0;
      bp->outputtries = 0;
      *bp->inputbuf = '\0';
      bp->inputcnt = 0;
      bp->outputprefix = NULL;
      bp->outputsuffix = NULL;
      (void) strcpy(bp->doing, "");
      bp->site = ntohl(theaddr.sin_addr.s_addr);
      (void) strcpy(bp->hostname,
		    addrout(ntohl(theaddr.sin_addr.s_addr)));
      log_connect(bp);

      /* refuse to talk if we cannot set non-blocking */
      if (fcntl(bp->fd, F_SETFL, FNDELAY) == 0)
      {
	iobindex[n] = bp;

#ifdef SITE_REGISTRATION
        if (check_lockout_list('L',bp->site))
        {
          if (iob_spit_file(bp, SITELOCKOUT_FILE) == -1)
          {
            iobput(bp, SITE_REGISTER_MSG);
          }
          iobflush(bp);
          log_disconnect(bp);
          iobdrop(bp);
        }
#endif /* SITE_REGISTRATION */

	/* introduce ourselves */
	greet(bp);
      } else
      {
	warning("ioloop",
		"couldn't set new connection non-blocking");
	log_disconnect(bp);
	iobdrop(bp);
      }
    }
  }
  /* check input on existing fds. */
  for (n = 0; n < iobindexsiz; n++)
  {

    /* first check needed to keep from checking serfd */
    if (iobindex[n] != (Iob *) 0 && FD_ISSET(n, &ready))
    {
      bp = iobindex[n];

      bcnt = read(n, bp->inputbuf + bp->inputcnt
		  ,(MUDBUFSIZ - 1) - bp->inputcnt);

      /* check hangups */
      if (bcnt <= 0)
      {
	int             fcf = bp->typ;

	iobdrop(bp);
	log_disconnect(bp);
	if (fcf != INPUT_NEWCONN)
	{
	  disconnect_player(bp->player);
          autotoadcheck(bp->player);
	}
      } else
      {
	bp->inputcnt += bcnt;
	bp->inputbuf[bp->inputcnt] = '\0';

	/*
	 * dispatch branches on the type of buffer we are dealing with. the
	 * Iob pointer is passed, too, since it contains uid information and
	 * whatnot. 
	 *
	 * for those of you using this outside of Ubermud, the iob->typ flag can
	 * be used in dispatch() to provide a simple state machine for a
	 * login sequence. that is all completely irrelevant to this code. 
	 */
	dispatch(bp);
      }
    } else
    if (iobindex[n] != (Iob *) 0 && FD_ISSET(n, &xcpt))
    {
      int             fcf = iobindex[n]->typ;
      int             deadplayer;

      if (fcf != INPUT_NEWCONN)
      {
	deadplayer = iobindex[n]->player;
        if (deadplayer < 0)
	  log_error("Bad deadplayer from exception: %d\n", deadplayer);
      }
      warning("ioloop", "connection dropped due to exception");
      iobdrop(iobindex[n]);
      log_disconnect(iobindex[n]);
      if (fcf != INPUT_NEWCONN && deadplayer >= 0)
      {
        log_error("Dropped due to exception: %d\n",deadplayer);
	disconnect_player(deadplayer);
        autotoadcheck(deadplayer);
      }
    }
  }
  return (0);
}



/*
 * the super-duper teenymud iobflush. it does it all. first off, we run a
 * quick select() to give the connection a moment to catch up, and then we
 * try to shove our buffer down the socket. should it fuck up, it gets either
 * dropped or let alone 'til later. should it block, either a nice little
 * message gets copied into the buffer (if the buffer is full), or it gets
 * put aside 'til later. if we try to write and have to wait more than
 * sixteen times, we drop the sucker. 
 */
void
iobflush(iob)
  Iob            *iob;
{
  int             rv;
  fd_set          wfd;
  struct timeval  wtimo;
  static char     flushed_msg[] = "<Output flushed>\r\n";

  if (iob->state != IOSTATE_OK)
    return;

  if (iob->outputtries > 16)
  {				/* This bloody socket is STALLED. */
    int deadplayer;
    /* drop it */

    warning("iobflush", "dropped stalled iob");
    iobdrop(iob);
    log_disconnect(iob);
    if (iob->typ != INPUT_NEWCONN)
    {
      disconnect_player(iob->player);
      autotoadcheck(iob->player);
    }
    return;
  }
  FD_ZERO(&wfd);
  FD_SET(iob->fd, &wfd);

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

  if (select(iob->fd + 1, (fd_set *) 0, &wfd, (fd_set *) 0, &wtimo) != 1
      || !FD_ISSET(iob->fd, &wfd))
  {
    if (iob->outputcnt < MUDBUFSIZ)
      return;

    bcopy(flushed_msg, iob->outputbuf, sizeof(flushed_msg));
    iob->outputcnt = strlen(flushed_msg);
    return;
  }
  rv = write(iob->fd, iob->outputbuf, iob->outputcnt);

  if (rv < 0 && errno != EWOULDBLOCK)
  {
    int             fcf = iob->typ;

    warning("iobflush", "dropped fd due to bad write");
    iobdrop(iob);
    log_disconnect(iob);
    if (fcf != INPUT_NEWCONN)
    {
      disconnect_player(iob->player);
      autotoadcheck(iob->player);
    }
    return;
  } else
  if (rv < 0 && errno == EWOULDBLOCK)
  {
    if (iob->outputcnt >= MUDBUFSIZ)
    {				/* shit */
      bcopy(flushed_msg, iob->outputbuf, sizeof(flushed_msg));
      iob->outputcnt = strlen(flushed_msg);
      return;
    }
    iob->outputtries++;		/* hehe */
    return;
  }
  if (rv < iob->outputcnt)
  {
    if (rv == 0)
    {				/* No bytes written at all! */
      iob->outputtries++;
    } else
    {
      iob->outputtries = 0;
      iob->outputcnt -= rv;
      bcopy(iob->outputbuf + rv, iob->outputbuf, iob->outputcnt);
    }
  } else
  {
    iob->outputcnt = 0;
  }

}



/*
 * toast connects belonging to #XX 
 */
void
iobdisconnect(player)
  int             player;
{
  int             x;

  for (x = 0; x < iobindexsiz; x++)
    if (iobindex[x] != (Iob *) 0 && iobindex[x]->player == player)
    {
      iobflush(iobindex[x]);
      iobdrop(iobindex[x]);
    }
}

/*
 * wall a message to all the Iobs and then flush them (Apparently not --
 * Andrew) 
 */
/* VARARGS *//* Not any more. */
void
notify_wall(str)		/* Was iobwall */
  char           *str;
{
  int             x;

  for (x = 0; x < iobindexsiz; x++)
    if ((iobindex[x] != (Iob *) 0) &&
	iobindex[x]->typ != INPUT_NEWCONN)
      iobput(iobindex[x], str);
}


/*
 * put text into an Iob. if it overflows, make a half-hearted attempt to
 * flush it, but don't get carried away, or anything. all arguments to iobput
 * must be strings, and the list must be terminated with a (char *)0. warning
 * - some machines have limited lengths of arg lists that can be passed with
 * varargs, so some sense is required. 
 */
/* VARARGS1 *//* Not any more. One \0 terminated string, please. */
void
iobput(iob, str)
  Iob            *iob;
  char           *str;
{
  register char  *s;

  if (str == NULL || strlen(str) < 1)		/* gunky fix */
    return;

  if (iob == (Iob *) 0 || iob->state != IOSTATE_OK)
    return;

  if (lastiob != iob)
  {
    lastiob = iob;
    onewrt++;
  }
  s = str;
  while (*s && iob != (Iob *) 0)
  {
    if (iob->outputcnt < MUDBUFSIZ)
    {
      iob->outputbuf[iob->outputcnt++] = *s++;
    } else
    {
      iobflush(iob);
    }
  }
}




/*
 * put text into an Iob by user-id. it the user-id is connected more than
 * once, they get more than one copy of the output. as with iobput, all
 * arguments must be strings, and the list must be terminated with (char *)0. 
 */
/* VARARGS1 *//* Not any more. Single \0 terminated string. -- Andrew */
void
notify_player(player, str)	/* Formerly iobtell() */
  int             player;
  char           *str;
{
  int             x;
  register char  *s;

  for (x = 0; x < iobindexsiz; x++)
  {
    if (iobindex[x] != (Iob *) 0 && iobindex[x]->player == player &&
	iobindex[x]->state == IOSTATE_OK)
    {

      if (lastiob != iobindex[x])
      {
	lastiob = iobindex[x];
	onewrt++;
      }
      s = str;
      while (*s && iobindex[x] != (Iob *) 0)
      {
	if ((iobindex[x]->outputcnt) < MUDBUFSIZ)
	  iobindex[x]->outputbuf
	      [iobindex[x]->outputcnt++]
	      = *s++;
	else
	  iobflush(iobindex[x]);
      }
    }
  }
}


/*
 * flush all the Iobs. 
 */
void
iosync()
{
  int             n;

  if (!onewrt)
    return;

  if (onewrt == 1 && lastiob != (Iob *) 0 && lastiob->outputcnt != 0)
  {
    iobflush(lastiob);
    return;
  }
  for (n = 0; n < iobindexsiz; n++)
  {
    if (iobindex[n] != (Iob *) 0 && iobindex[n]->outputcnt != 0)
    {
      iobflush(iobindex[n]);
    }
  }

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