/* network.c * */ #include "stdinclude.h" #include #include #include #include #include #include "common.h" #include "config.h" #include "ficsmain.h" #include "maxxes-utils.h" #include "network.h" #include "playerdb.h" #include "rmalloc.h" #ifdef TIMESEAL #include "timeseal.h" #endif #include "utils.h" /* Sparse array */ PUBLIC connection con[512]; PUBLIC int no_file; PUBLIC int max_connections; PRIVATE int sockfd = 0; PRIVATE int numConnections = 0; PUBLIC int findConnection(int fd) { if (con[fd].status == NETSTAT_EMPTY) return -1; return fd; } PUBLIC int net_addConnection(int fd, unsigned int fromHost) { int noblock = 1; if (findConnection(fd) >= 0) { fprintf(stderr, "FICS: FD already in connection table!\n"); return -1; } if (numConnections >= max_connections) return -1; if (ioctl(fd, FIONBIO, &noblock) == -1) { fprintf(stderr, "Error setting nonblocking mode errno=%d\n", errno); } con[fd].fd = fd; if (fd != 0) con[fd].outFd = fd; else con[fd].outFd = 1; con[fd].fromHost = fromHost; con[fd].status = NETSTAT_CONNECTED; #ifdef TIMESEAL con[fd].sys[0] = '\0'; con[fd].time = 0; con[fd].timeseal = 0; con[fd].user[0] = '\0'; #endif con[fd].numPending = 0; con[fd].outPos = 0; con[fd].processed = 0; if (con[fd].sndbuf == NULL) { #ifdef DEBUG fprintf(stderr, "FICS: nac(%d) allocating sndbuf.\n", fd); #endif con[fd].sndbufpos = 0; con[fd].sndbufsize = MAX_STRING_LENGTH; con[fd].sndbuf = rmalloc(MAX_STRING_LENGTH); } else { #ifdef DEBUG fprintf(stderr, "FICS: nac(%d) reusing old sndbuf " "size %d pos %d.\n", fd, con[fd].sndbufsize, con[fd].sndbufpos); #else /* empty */; #endif } con[fd].state = 0; numConnections++; #ifdef DEBUG fprintf(stderr, "FICS: fd: %d connections: %d descriptors: %d \n", fd, numConnections, getdtablesize()); /* sparky 3/13/95 */ #endif return 0; } PRIVATE int remConnection(int fd) { if (findConnection(fd) < 0) return -1; numConnections--; con[fd].status = NETSTAT_EMPTY; if (con[fd].sndbuf == NULL) { fprintf(stderr, "FICS: remcon(%d) SNAFU, " "this shouldn't happen.\n", fd); } else { if (con[fd].sndbufsize > MAX_STRING_LENGTH) { con[fd].sndbufsize = MAX_STRING_LENGTH; con[fd].sndbuf = rrealloc(con[fd].sndbuf, MAX_STRING_LENGTH); } if (con[fd].sndbufpos) // didn't send everything, bummer con[fd].sndbufpos = 0; } return 0; } PRIVATE void net_flushme(int which) { int sent; if ((sent = send(con[which].outFd, con[which].sndbuf, con[which].sndbufpos, 0)) == -1) { if (errno != EPIPE) { // EPIPE = they've disconnected fprintf(stderr, "FICS: net_flushme(%d) couldn't send, " "errno=%d.\n", which, errno); } con[which].sndbufpos = 0; } else { con[which].sndbufpos -= sent; if (con[which].sndbufpos) { memmove(con[which].sndbuf, con[which].sndbuf + sent, con[which].sndbufpos); } } if (con[which].sndbufsize > MAX_STRING_LENGTH && con[which].sndbufpos < MAX_STRING_LENGTH) { // time to shrink the buffer... con[which].sndbuf = rrealloc(con[which].sndbuf, MAX_STRING_LENGTH); con[which].sndbufsize = MAX_STRING_LENGTH; } } PRIVATE void net_flush_all_connections(void) { fd_set writefds; int which; struct timeval to; FD_ZERO(&writefds); for (which = 0; which < MAX_PLAYER; which++) { if (con[which].status == NETSTAT_CONNECTED && con[which].sndbufpos) FD_SET(con[which].outFd, &writefds); } to.tv_usec = 0; to.tv_sec = 0; select(no_file, NULL, &writefds, NULL, &to); for (which = 0; which < MAX_PLAYER; which++) { if (FD_ISSET(con[which].outFd, &writefds)) net_flushme(which); } } PRIVATE void net_flush_connection(int fd) { fd_set writefds; int which; struct timeval to; if ((which = findConnection(fd)) >= 0 && con[which].sndbufpos) { FD_ZERO(&writefds); FD_SET(con[which].outFd, &writefds); to.tv_usec = 0; to.tv_sec = 0; select(no_file, NULL, &writefds, NULL, &to); if (FD_ISSET(con[which].outFd, &writefds)) net_flushme(which); } } PRIVATE int sendme(int which, char *str, int len) { fd_set writefds; int i, count; struct timeval to; count = len; while ((i = ((con[which].sndbufsize - con[which].sndbufpos) < len) ? (con[which].sndbufsize - con[which].sndbufpos) : len) > 0) { memmove(con[which].sndbuf + con[which].sndbufpos, str, i); con[which].sndbufpos += i; if (con[which].sndbufpos == con[which].sndbufsize) { FD_ZERO(&writefds); FD_SET(con[which].outFd, &writefds); to.tv_usec = 0; to.tv_sec = 0; select(no_file, NULL, &writefds, NULL, &to); if (FD_ISSET(con[which].outFd, &writefds)) { net_flushme(which); } else { // time to grow the buffer con[which].sndbufsize += MAX_STRING_LENGTH; con[which].sndbuf = rrealloc(con[which].sndbuf, con[which].sndbufsize); } } str += i; len -= i; } return count; } /* * Put LF after every CR and put '\' at the end of overlength lines. * * Doesn't send anything unless the buffer fills and output waits * until flushed. * * '-1' for an error other than 'EWOULDBLOCK'. */ PUBLIC int net_send_string(int fd, char *str, int format) { int which, i, j; if ((which = findConnection(fd)) < 0) return -1; while (*str) { for (i = 0; str[i] >= ' '; i++) { /* null */; } if (i) { if (format && (i >= (j = LINE_WIDTH - con[which].outPos))) { // word wrap i = j; while (i > 0 && str[i - 1] != ' ') i--; while (i > 0 && str[i - 1] == ' ') i--; if (i == 0) i = j - 1; sendme(which, str, i); sendme(which, "\n\r\\ ", 6); con[which].outPos = 4; while (str[i] == ' ') { // eat the leading // spaces after we wrap i++; } } else { sendme(which, str, i); con[which].outPos += i; } str += i; } else { // non-printable stuff handled here switch (*str) { case '\t': sendme(which, " ", 8 - (con[which].outPos & 7)); con[which].outPos &= ~7; if ((con[which].outPos += 8) >= LINE_WIDTH) con[which].outPos = 0; break; case '\n': sendme(which, "\n\r", 2); con[which].outPos = 0; break; case '\033': con[which].outPos -= 3; default: sendme(which, str, 1); } str++; } } return 0; } /* * A) if we get a complete line (something terminated by '\n'), copy it * to com and return 1. * * B) if we don't get a complete line, but there is no error, return 0. * * C) if some error, return -1. */ PUBLIC int readline2(comstr_t *cs, int who) { int howmany, state, fd, pending; unsigned char *start, *s, *d; static unsigned char ayt[] = "[Responding to AYT: Yes, I'm here.]\n"; static unsigned char will_sga[] = { IAC, WILL, TELOPT_SGA, '\0' }; static unsigned char will_tm[] = { IAC, WILL, TELOPT_TM, '\0' }; state = con[who].state; if (state == 2 || state > 4) { fprintf(stderr, "FICS: state screwed for con[%d], " "this is a bug.\n", who); state = 0; } s = start = con[who].inBuf; pending = con[who].numPending; fd = con[who].fd; if ((howmany = recv(fd, start + pending, MAX_STRING_LENGTH - 1 - pending, 0)) == 0) { // error: they've disconnected return -1; } else if (howmany == -1) { if (errno != EWOULDBLOCK) { // some other error return -1; } else if (con[who].processed) { // nothing new and nothing old return 0; } else { // nothing new // but some unprocessed old howmany = 0; } } if (con[who].processed) s += pending; else howmany += pending; d = s; for (; howmany-- > 0; s++) { switch (state) { case 0: // haven't skipped over any control chars or // telnet commands if (*s == IAC) { d = s; state = 1; } else if (*s == '\n') { *s = '\0'; msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s", start); if (howmany) memmove(start, s + 1, howmany); con[who].state = 0; con[who].numPending = howmany; con[who].processed = 0; con[who].outPos = 0; return 1; } else if (*s > (0xff - 0x20) || *s < 0x20) { d = s; state = 2; } break; case 1: // got telnet IAC if (*s == IP) { return -1; // ^C = logout } else if (*s == DO) { state = 4; } else if (*s == WILL || *s == DONT || *s == WONT) { state = 3; // this is cheesy // but we aren't using em } else if (*s == AYT) { send(fd, (char *)ayt, strlen((char *)ayt), 0); state = 2; } else if (*s == EL) { // erase line d = start; state = 2; } else { // dunno what it is // so ignore it state = 2; } break; case 2: // we've skipped over something // need to shuffle processed chars down if (*s == IAC) state = 1; else if (*s == '\n') { *d = '\0'; msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s", start); if (howmany) memmove(start, s + 1, howmany); con[who].state = 0; con[who].numPending = howmany; con[who].processed = 0; con[who].outPos = 0; return 1; } else if (*s >= ' ') *(d++) = *s; break; case 3: // some telnet junk we're ignoring state = 2; break; case 4: // got IAC DO if (*s == TELOPT_TM) { send(fd, (char *)will_tm, strlen((char *)will_tm), 0); } else if (*s == TELOPT_SGA) { send(fd, (char *)will_sga, strlen((char *)will_sga), 0); } state = 2; break; } } if (state == 0) d = s; else if (state == 2) state = 0; con[who].state = state; con[who].numPending = d - start; con[who].processed = 1; if (con[who].numPending == MAX_STRING_LENGTH - 1) { // buffer full *d = '\0'; msnprintf(cs->com, ARRAY_SIZE(cs->com), "%s", start); con[who].state = 0; con[who].numPending = 0; con[who].processed = 0; return 1; } return 0; } PUBLIC int net_init(int port) { int opt; struct linger lingeropt; struct sockaddr_in serv_addr; /* * Although we have 256 descriptors to work with for opening * files, we can only use 126 for sockets under SunOS 4.x.x * socket libs. Using glibc can get you up to 256 again. Many * OS's can do more than that! * Sparky 9/20/95 */ if ((no_file = getdtablesize()) > MAX_PLAYER + 6) no_file = MAX_PLAYER + 6; max_connections = no_file - 6; for (int i = 0; i < no_file; i++) { con[i].status = NETSTAT_EMPTY; con[i].sndbuf = NULL; con[i].sndbufsize = con[i].sndbufpos = 0; } /* Open a TCP socket (an Internet stream socket) */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "FICS: can't open stream socket\n"); return -1; } /* Bind our local address so that the client can send to us */ memset(&serv_addr, 0, sizeof serv_addr); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(port); /* * Attempt to allow rebinding to the port... */ opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof opt); opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof opt); lingeropt.l_onoff = 0; lingeropt.l_linger = 0; setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *) &lingeropt, sizeof(lingeropt)); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof serv_addr) < 0) { fprintf(stderr, "FICS: can't bind local address. errno=%d\n", errno); return -1; } opt = 1; ioctl(sockfd, FIONBIO, &opt); listen(sockfd, 5); return 0; } PUBLIC void net_close(void) { for (int i = 0; i < no_file; i++) { if (con[i].status != NETSTAT_EMPTY) net_close_connection(con[i].fd); } } PUBLIC void net_close_connection(int fd) { if (con[fd].status == NETSTAT_CONNECTED) net_flush_connection(fd); if (!remConnection(fd)) { if (fd > 2) close(fd); } } PUBLIC void turn_echo_on(int fd) { static unsigned char wont_echo[] = { IAC, WONT, TELOPT_ECHO, '\0' }; send(fd, (char *) wont_echo, strlen((char *) wont_echo), 0); } PUBLIC void turn_echo_off(int fd) { static unsigned char will_echo[] = { IAC, WILL, TELOPT_ECHO, '\0' }; send(fd, (char *) will_echo, strlen((char *) will_echo), 0); } PUBLIC unsigned int net_connected_host(int fd) { int which; if ((which = findConnection(fd)) < 0) { fprintf(stderr, "FICS: FD not in connection table!\n"); return -1; } return con[which].fromHost; } PUBLIC void ngc2(comstr_t *cs, int timeout) { fd_set readfds; int fd, loop, nfound, lineComplete; socklen_t cli_len = sizeof(struct sockaddr_in); struct sockaddr_in cli_addr; struct timeval to; while ((fd = accept(sockfd, (struct sockaddr *) &cli_addr, &cli_len)) != -1) { if (net_addConnection(fd, cli_addr.sin_addr.s_addr)) { fprintf(stderr, "FICS is full. fd = %d.\n", fd); psend_raw_file(fd, mess_dir, MESS_FULL); close(fd); } else { process_new_connection(fd, net_connected_host(fd)); } } if (errno != EWOULDBLOCK) { fprintf(stderr, "FICS: Problem with accept(). errno=%d\n", errno); } net_flush_all_connections(); FD_ZERO(&readfds); for (loop = 0; loop < no_file; loop++) { if (con[loop].status != NETSTAT_EMPTY) FD_SET(con[loop].fd, &readfds); } to.tv_usec = 0; to.tv_sec = timeout; nfound = select(no_file, &readfds, NULL, NULL, &to); /* XXX: unused */ (void) nfound; for (loop = 0; loop < no_file; loop++) { if (con[loop].status != NETSTAT_EMPTY) { fd = con[loop].fd; if ((lineComplete = readline2(cs, fd)) == 0) { // partial line: do nothing continue; } if (lineComplete > 0) { // complete line: process it #ifdef TIMESEAL if (!parseInput(cs->com, &con[loop])) continue; #endif if (process_input(fd, cs->com) != COM_LOGOUT) { net_flush_connection(fd); continue; } } /* * Disconnect anyone who gets here */ process_disconnection(fd); net_close_connection(fd); } } } PUBLIC int net_consize(void) { int total = sizeof con; for (int i = 0; i < no_file; i++) total += con[i].sndbufsize; return total; }