dbm/
misc/
old-docs/
/* tcpio.c */

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

#include <stdio.h>
#include <sys/param.h>
/*
 * #ifndef BYTE_ORDER #include <machine/endian.h> #endif
 */
#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"

/* 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 **) ty_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);

      ty_free((char *) iob->outputprefix);
      iob->outputprefix = 0;
      ty_free((char *) iob->outputsuffix);
      iob->outputsuffix = 0;
      ty_free((char *) iob->hostname);
      iob->hostname = 0;
      ty_free((char *) iob->doing);
      iob->doing = 0;

      /*
       * 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) ty_free((char *) bp);
    bp = np;
  }
  (void) ty_free((char *) iobindex);
  (void) shutdown(serfd, 2);
  (void) close(serfd);
}


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

#ifdef HOSTNAMES
  struct hostent *he;

  he = gethostbyaddr(&a, sizeof(a), AF_INET);
  if (he) {
    p = (char *) ty_malloc(strlen(he->h_name) + 1, "addrout");
    (void) strcpy(p, he->h_name);
    return (p);
  }
#endif				/* HOSTNAMES */

  a = ntohl(a);
#if defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN
  sprintf(buf, "%d.%d.%d.%d", a & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff,
	  (a >> 24) & 0xff);
#else
  sprintf(buf, "%d.%d.%d.%d", (a >> 24) & 0xff, (a >> 16) & 0xff,
	  (a >> 8) & 0xff, a & 0xff);
#endif
  p = (char *) ty_malloc(strlen(buf) + 1, "addrout");
  (void) strcpy(p, buf);
  return (p);
}


/*
 * 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 *) ty_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 = 0;
      bp->outputsuffix = 0;
      bp->doing = 0;
      bp->site = ntohl(theaddr.sin_addr.s_addr);
      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) {
	char           *msg;

	iobindex[n] = bp;
#ifdef SUPPORT_LOCKOUT
	if (check_lockout_list(bp->site, LOCK_DISCONNECT, &msg)) {
	  if (msg && *msg) {
	    iobput(bp, msg);
	    iobflush(bp);
	  }
	  log_disconnect(bp);
	  iobdrop(bp);
	} else {
	  /* introduce ourselves */
	  greet(bp);
	}
#else
	greet(bp);
#endif
      } 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);
	if (fcf == INPUT_PLAY) {
	  disconnect_player(bp->player);
	}
	log_disconnect(bp);
      } 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;

      warning("ioloop", "connection dropped due to exception");
      if (fcf != INPUT_PLAY) {
	disconnect_player(iobindex[n]->player);
      }
      log_disconnect(iobindex[n]);
      iobdrop(iobindex[n]);
    }
  }
  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[] = "\r\n<Output flushed>\r\n";

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

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

    warning("iobflush", "dropped stalled iob");
    iobdrop(iob);

    if (iob->typ == INPUT_PLAY) {
      disconnect_player(iob->player);
    }
    log_disconnect(iob);
    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);

    if (fcf == INPUT_PLAY) {
      disconnect_player(iob->player);
    }
    log_disconnect(iob);
    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_PLAY)
      iobput(iobindex[x], str);
}

void            notify_wizz(str)
  char           *str;
{
  int             x;

  for (x = 0; x < iobindexsiz; x++)
    if ((iobindex[x] != (Iob *) 0) && iobindex[x]->typ == INPUT_PLAY &&
	iswiz(iobindex[x]->player))
      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 (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;
}

void            timeout_daemon()
{
  register int    i;
  register int    count;
  struct timeval  tm;

  (void) gettimeofday(&tm, (struct timezone *) 0);
  for (i = count = 0; i < iobindexsiz; i++) {
    if (iobindex[i] == (Iob *) 0)
      continue;
    if ((iobindex[i]->typ == INPUT_NEWCONN) &&
	(tm.tv_sec - iobindex[i]->lastcmd) > 300) {
      iobput(iobindex[i], "Idle timeout.\r\n");
      iobflush(iobindex[i]);
      log_disconnect(iobindex[i]);
      iobdrop(iobindex[i]);
      count++;
      continue;
    }
    if ((iobindex[i]->typ == INPUT_PLAY) &&
	(tm.tv_sec - iobindex[i]->lastcmd) > 3600) {
      iobput(iobindex[i], "Idle timeout.\r\n");
      iobflush(iobindex[i]);
      disconnect_player(iobindex[i]->player);
      log_disconnect(iobindex[i]);
      iobdrop(iobindex[i]);
      count++;
    }
  }

  if (count)
    log_status("STATUS: Idle Daemon booted %d connections.\n", count);
}