#include <sys/types.h> #include <ctype.h> #include <fcntl.h> #include <sys/time.h> #include <sys/errno.h> #include <sys/socket.h> #include <netinet/in.h> /* Hacked up by Andrew Molitor, somewhat severely, for use with TeenyMUD. */ /* 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 "teeny.h" #include "io.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. */ /* This will no longer work with UberMud, and rather badly not. It provides a couple more functions, and removes some. The additional function are dispatch(), which (here) handles player creation and connection, and also the QUIT and WHO commands. -- Andrew */ /* #define IODEBUG */ extern int errno; char *malloc(); time_t time(); /* 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))) { warning("ioinit","cannot bind socket"); return(1); } if(listen(serfd,5) == -1) { warning("ioinit","cannot listen at socket"); 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 (void)shutdown(x,2); (void)close(x); #ifdef IODEBUG printf("Closed connection\n"); #endif /* 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); } } /* 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); #ifdef IODEBUG printf("Closing serfd.\n"); #endif (void)shutdown(serfd,2); (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; struct timeval timo; int scnt; time_t currenttime; 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(); currenttime = time((time_t *)0); /* 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->whofwd; } else { bp = (Iob *)malloc(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 = (long) currenttime; bp->lastcmd = (long) currenttime; bp->blown = 0; *bp->outputbuf = '\0'; bp->outputcnt = 0; bp->outputtries = 0; *bp->inputbuf = '\0'; bp->inputcnt = 0; /* 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 { warning("ioloop", "could not set socket non-blocking"); 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_NEWCONN){ disconnect_player(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. */ if(dispatch(bp)) return(1); } } else if(iobindex[n] != (Iob *)0 && FD_ISSET(n,&xcpt)) { int fcf = iobindex[n]->typ; warning("ioloop","connection dropped due to exception"); iobdrop(iobindex[n]); if(fcf != INPUT_NEWCONN){ disconnect_player(iobindex[n]->player); } } } 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. */ void iobflush(iob) Iob *iob; { int rv; if(iob->state != IOSTATE_OK) return; if(iob->outputtries > 16){ /* This bloody socket is STALLED. */ iob->outputtries = 0; iob->outputcnt = 0; return; } rv = write(iob->fd,iob->outputbuf,iob->outputcnt); #ifdef IODEBUG printf("attempted write %d bytes, fd %d\n",iob->outputcnt,iob->fd); #endif if(rv < 0 && errno != EWOULDBLOCK) { int fcf = iob->typ; warning("iobflush","dropped fd due to bad write"); iobdrop(iob); if(fcf != INPUT_NEWCONN){ disconnect_player(iob->player); } return; } /* This is a little ookey. I should mess with pointers. Cope. */ /* -- Andrew */ if(rv < iob->outputcnt){ #ifdef IODEBUG printf("Partial write on fd %d, coping.\n",iob->fd); #endif 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) 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) { #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]->outputcnt != 0) { #ifdef IODEBUG printf("sync connection on fd %d\n",n); #endif iobflush(iobindex[n]); } } onewrt = 1; lastiob = (Iob *)0; }