/* 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(¤ttime, (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;
}