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