#include <sys/types.h>
#include <ctype.h>
#include <varargs.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
/*
Copyright(C) 1990, Marcus J. Ranum, All Rights Reserved.
This software may be freely used, modified, and redistributed,
as long as this copyright message is left intact, and this
software is not used to develop any commercial product, or used
in any product that is provided on a pay-for-use basis.
*/
/*
network layer library for Ubermud. this comprises a completely
'hidden' network interface for TCP/IP. All I/O function calls
are contained in this file.
*/
#include "ubermud.h"
#include "io.h"
#include "externs.h"
/*
this arguably should be in ubermud.h, but is not, because it is
network-implementation-specific.
*/
#define MUDPORT 6666
/*
Routines to handle multiplexing I/O. You can plug anything you like into
the I/O routines, if you're not on a BSD machine, but follow this
interface. All you need to know is the following:
1) you will get called with ioinit() once at startup.
2) you will get called with ioloop() as long as ioloop() returns 0. each
call to ioloop() is expected to handle calling dispatch() where
appropriate.
3) after ioloop() returns 0, iosync() is called.
4) when ioloop() returns 1, iowrap() is called.
*/
/*
#define IODEBUG
*/
/* 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;
/*
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((unsigned)(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);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(MUDPORT);
if(bind(serfd,(struct sockaddr *)&addr,sizeof(addr))) {
logf("cannot bind socket: ",(char *)-1,"\n",0);
return(1);
}
if(listen(serfd,5) == -1) {
logf("cannot listen at socket: ",(char *)-1,"\n",0);
return(1);
}
#ifdef IODEBUG
printf("I/O system ready\n");
#endif
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) {
#ifdef IODEBUG
printf("close connection on fd %d\n",x);
#endif
logf("close connect @",itoa(x),"\n",0);
(void)close(x);
/*
put the Iob on free chain
pressing the buf-ptr as a list ptr
*/
iob->bufp = (char *)freeiob;
freeiob = iob;
/* mark it as dead */
iob->state = IOSTATE_HUNGUP;
/* de-index (very important) */
iobindex[x] = (Iob *)0;
}
}
}
/*
iowrap is responsible for shutting down and cleaning everything up.
*/
void
iowrap()
{
int x;
Iob *bp;
/* shut down bound socket */
(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->bufp;
(void)free((char *)bp);
bp = np;
}
(void)free((char *)iobindex);
(void)close(serfd);
}
/*
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;
char buf[MUDBUFSIZ];
struct timeval timo;
int scnt;
timo.tv_sec = 666;
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,0,&xcpt,&timo)) < 0) {
if(errno != EINTR) {
logf("select in loop failed: ",(char *)-1,"\n",0);
return(1);
}
}
/* not a very interesting run, eh ? */
if(scnt <= 0)
return(0);
/* check new connections */
if(FD_ISSET(serfd,&ready)) {
n = accept(serfd,(struct sockaddr *)0,(int *)0);
if(n != -1) {
/* allocate a buffer */
if(freeiob != (Iob *)0) {
bp = freeiob;
freeiob = (Iob *)freeiob->bufp;
} else {
bp = (Iob *)malloc(sizeof(Iob));
if(bp == (Iob *)0) {
logf("cannot malloc a new Iob: ",
(char *)-1,"\n",0);
return(1);
}
}
/* initialize it */
bp->typ = INPUT_NEWCONN;
bp->uid = bp->euid = (long)-1;
bp->flg = INPUT_DEFAULT;
bp->state = IOSTATE_OK;
bp->fd = n;
*bp->inbuf = '\0';
bp->bufp = bp->inbuf;
bp->bufcnt = sizeof(bp->inbuf);
/* refuse to talk if we cannot set no blocking */
if(fcntl(bp->fd,F_SETFL,FNDELAY) == 0) {
#ifdef IODEBUG
printf("new connection fd on %d\n",n);
#endif
iobindex[n] = bp;
/* introduce ourselves */
greet(bp);
} else {
logf("could not set non-blocking:",(char *)-1,"\n",0);
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)) {
bcnt = read(n,buf,sizeof(buf) - 1);
/* check hangups */
if(bcnt <= 0) {
long oid = iobindex[n]->uid;
int fcf = iobindex[n]->typ;
iobdrop(iobindex[n]);
if(fcf != INPUT_NEWCONN)
call_sysfunc("_quit",oid,(long)0);
} else {
buf[bcnt] = '\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.
*/
if(dispatch(buf,iobindex[n]))
return(1);
}
} else
if(iobindex[n] != (Iob *)0 && FD_ISSET(n,&xcpt)) {
long oid = iobindex[n]->uid;
int fcf = iobindex[n]->typ;
logf("dropped due to exception: ",(char *)-1,"\n",0);
iobdrop(iobindex[n]);
if(fcf != INPUT_NEWCONN)
call_sysfunc("_quit",oid,(long)0);
}
}
return(0);
}
/*
try to flush an Iob. if the IOB would block, avoid the flush and
leave the data in it for later, if we can.
*/
static void
iobflush(iob)
Iob *iob;
{
int rv;
if(iob->state != IOSTATE_OK)
return;
rv = write(iob->fd,iob->inbuf,iob->bufp - iob->inbuf);
#ifdef IODEBUG
printf("attempted write %d bytes, fd %d\n",iob->bufp - iob->inbuf,iob->fd);
#endif
if(rv < 0 && errno != EWOULDBLOCK) {
long oid = iob->uid;
int fcf = iob->typ;
logf("dropped fd due to bad write: ",(char *)-1,"\n",0);
iobdrop(iob);
if(fcf != INPUT_NEWCONN)
call_sysfunc("_quit",oid,(long)0);
return;
}
/* ignore partial writes and whatnot if we can. <shrug> */
if(rv > 0 && rv != iob->bufp - iob->inbuf && iob->bufcnt > 0)
return;
iob->bufp = iob->inbuf;
iob->bufcnt = sizeof(iob->inbuf);
}
/*
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 */
void
iobput(iob,va_alist)
Iob *iob;
va_dcl
{
register char *s;
va_list ap;
if(iob == (Iob *)0 || iob->state != IOSTATE_OK)
return;
if(lastiob != iob) {
lastiob = iob;
onewrt++;
}
va_start(ap);
while((s = va_arg(ap,char *)) != (char *)0) {
while(*s && *s != MATCH_CHAR && iob != (Iob *)0) {
if(--iob->bufcnt)
*iob->bufp++ = *s++;
else
iobflush(iob);
}
}
va_end(ap);
}
/*
wall a message to all the Iobs and then flush them
*/
/* VARARGS */
void
iobwall(va_alist)
va_dcl
{
char *s;
int x;
va_list ap;
va_start(ap);
while((s = va_arg(ap,char *)) != (char *)0) {
for(x = 0; x < iobindexsiz; x++)
if(iobindex[x] != (Iob *)0)
iobput(iobindex[x],s,0);
}
va_end(ap);
}
/*
toast connects belonging to #XX
*/
void
iobdisconnect(uid)
long uid;
{
int x;
for(x = 0; x < iobindexsiz; x++)
if(iobindex[x] != (Iob *)0 && iobindex[x]->uid == uid) {
iobflush(iobindex[x]);
iobdrop(iobindex[x]);
}
}
/*
let us know if user #XX is connected.
*/
int
iobconnected(uid)
long uid;
{
int x;
if(uid == 0L)
return(0);
for(x = 0; x < iobindexsiz; x++)
if(iobindex[x] != (Iob *)0 && iobindex[x]->uid == uid)
return(1);
return(0);
}
/*
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 */
void
iobtell(uid,va_alist)
long uid;
va_dcl
{
register char *s;
int x;
va_list ap;
for(x = 0; x < iobindexsiz; x++) {
if(iobindex[x] != (Iob *)0 && iobindex[x]->uid == uid &&
iobindex[x]->state == IOSTATE_OK) {
if(lastiob != iobindex[x]) {
lastiob = iobindex[x];
onewrt++;
}
va_start(ap);
while((s = va_arg(ap,char *)) != (char *)0) {
while(*s && *s != MATCH_CHAR && iobindex[x] != (Iob *)0) {
if(--iobindex[x]->bufcnt)
*iobindex[x]->bufp++ = *s++;
else
iobflush(iobindex[x]);
}
}
va_end(ap);
}
}
}
/*
flush all the Iobs.
*/
void
iosync()
{
int n;
if(!onewrt)
return;
if(onewrt == 1 && lastiob != (Iob *)0 && lastiob->bufp != lastiob->inbuf) {
#ifdef IODEBUG
printf("only one iob to flush\n");
#endif
iobflush(lastiob);
return;
}
for(n = 0; n < iobindexsiz; n++) {
if(iobindex[n] != (Iob *)0 && iobindex[n]->bufp != iobindex[n]->inbuf) {
#ifdef IODEBUG
printf("sync connection on fd %d\n",n);
#endif
iobflush(iobindex[n]);
}
}
onewrt = 1;
lastiob = (Iob *)0;
}