diff options
Diffstat (limited to 'FICS/network.c')
-rw-r--r-- | FICS/network.c | 622 |
1 files changed, 622 insertions, 0 deletions
diff --git a/FICS/network.c b/FICS/network.c new file mode 100644 index 0000000..2480a69 --- /dev/null +++ b/FICS/network.c @@ -0,0 +1,622 @@ +/* network.c + * + */ + +#include "stdinclude.h" + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/telnet.h> + +#include "ficsmain.h" +#include "common.h" +#include "utils.h" +#include "playerdb.h" +#include "network.h" +#include "rmalloc.h" +#include "config.h" +#ifdef TIMESEAL +#include "timeseal.h" +#endif + +extern int errno; + +PRIVATE int sockfd = 0; /* The socket */ +PRIVATE int numConnections = 0; +/* Sparse array */ +PUBLIC connection con[512]; + +PUBLIC int no_file; +PUBLIC int max_connections; + +/* Index == fd, for sparse array, quick lookups! wasted memory :( */ +PUBLIC int findConnection(int fd) +{ + if (con[fd].status == NETSTAT_EMPTY) + return -1; + else + 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].user[0]='\0'; + con[fd].sys[0]='\0'; + con[fd].timeseal = 0; + con[fd].time = 0; +#endif + con[fd].numPending = 0; + con[fd].processed = 0; + con[fd].outPos = 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); +#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) +{ + int which; + if ((which = 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; + + sent = send(con[which].outFd, con[which].sndbuf, con[which].sndbufpos, 0); + if (sent == -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) +{ + int which; + fd_set writefds; + 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) +{ + int which; + fd_set writefds; + 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); + } + } + return; +} + +PRIVATE int sendme(int which, char *str, int len) +{ + int i, count; + fd_set writefds; + 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; +} + +/* + * -1 for an error other than EWOULDBLOCK. + * Put <lf> after every <cr> and put \ at the end of overlength lines. + * Doesn't send anything unless the buffer fills, output waits until + * flushed +*/ +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++); + 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; +} + +/* if we get a complete line (something terminated by \n), copy it to com + and return 1. + if we don't get a complete line, but there is no error, return 0. + if some error, return -1. + */ +PUBLIC int readline2(char *com, int who) +{ + unsigned char *start, *s, *d; + int howmany, state, fd, pending; + + static unsigned char will_tm[] = {IAC, WILL, TELOPT_TM, '\0'}; + static unsigned char will_sga[] = {IAC, WILL, TELOPT_SGA, '\0'}; + static unsigned char ayt[] = "[Responding to AYT: Yes, I'm here.]\n"; + + 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; + + howmany = recv(fd, start + pending, MAX_STRING_LENGTH - 1 - pending, 0); + if (howmany == 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'; + strcpy(com, start); + if (howmany) + bcopy(s + 1, start, 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'; + strcpy(com, 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'; + strcpy(com, start); + con[who].state = 0; + con[who].numPending = 0; + con[who].processed = 0; + return (1); + } + return (0); +} + +/* +PRIVATE int readline(int who) +{ + int e, fd = con[who].fd; + char *t = con[who].inBuf; + int recvCount; + int totalCount = 0; + unsigned char c; + static unsigned char will_tm[] = {IAC, WILL, TELOPT_TM, '\0'}; + static unsigned char will_sga[] = {IAC, WILL, TELOPT_SGA, '\0'}; + + t += con[who].numPending; + + while ((recvCount = recv(fd, (char *) &c, 1, 0)) == 1) { + totalCount += recvCount; + if (c == IAC) { + recvCount = recv(fd, (char *) &c, 1, 0); + if (recvCount == 1) { + totalCount += recvCount; + switch (c) { + case IP: + c = '\3'; + break; + case DO: + recvCount = recv(fd, (char *) &c, 1, 0); + if (recvCount == 1) { + totalCount += recvCount; + if (c == TELOPT_TM) { + send(fd, (char *) will_tm, strlen((char *) will_tm), 0); + } else if (c == TELOPT_SGA) { + send(fd, (char *) will_sga, strlen((char *) will_sga), 0); + } + } + c = '\0'; + break; + case DONT: + recvCount = recv(fd, (char *) &c, 1, 0); + break; + c = '\0'; + default: + recvCount = recv(fd, (char *) &c, 1, 0); + c = '\0'; + break; + } + } + } + if (c != '\r' && c > 2) { + if (isprint(c) || (c == '\n')) { + *t++ = c; + con[who].numPending++; + } + } + if (c == '\n' || con[who].numPending >= MAX_STRING_LENGTH - 1) { + *--t = '\0'; + con[who].numPending = 0; + con[fd].outPos = 0; + return 1; + } + } + + *t = '\0'; + e = ((totalCount == 0) || (errno != EWOULDBLOCK)) ? -1 : 0; + return (e); +} +*/ + +PUBLIC int net_init(int port) +{ + int i; + int opt; + struct sockaddr_in serv_addr; + struct linger lingeropt; + +/* 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 + */ + no_file = getdtablesize(); + if (no_file > MAX_PLAYER + 6) + no_file = MAX_PLAYER + 6; + max_connections = no_file - 6; + for (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((char *) &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); + + /** added in an 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)); + +/* +#ifdef DEBUG + opt = 1; + setsockopt(sockfd, SOL_SOCKET, SO_DEBUG, (char *)&opt, sizeof(opt)); +#endif +*/ + + 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) +{ + int i; + for (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(char *com, int timeout) +{ + struct sockaddr_in cli_addr; + int cli_len = (int) sizeof(struct sockaddr_in); + + int fd, loop, nfound, lineComplete; + fd_set readfds; + 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); + for (loop = 0; loop < no_file; loop++) { + if (con[loop].status != NETSTAT_EMPTY) { + fd = con[loop].fd; + lineComplete = readline2(com, fd); + if (lineComplete == 0) /* partial line: do nothing with it */ + continue; + if (lineComplete > 0) { /* complete line: process it */ +#ifdef TIMESEAL + if (!(parseInput(com, &con[loop]))) continue; +#endif + if (process_input(fd, 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 i, total; + + total = sizeof(con); + for (i=0; i < no_file; i++) + total += con[i].sndbufsize; + + return(total); +} |