aboutsummaryrefslogtreecommitdiffstats
path: root/FICS/network.c
diff options
context:
space:
mode:
Diffstat (limited to 'FICS/network.c')
-rw-r--r--FICS/network.c688
1 files changed, 688 insertions, 0 deletions
diff --git a/FICS/network.c b/FICS/network.c
new file mode 100644
index 0000000..19c6f22
--- /dev/null
+++ b/FICS/network.c
@@ -0,0 +1,688 @@
+/* network.c
+ *
+ */
+
+#include "stdinclude.h"
+
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <arpa/telnet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+
+#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) {
+ const int upbound = (int)strlen(str);
+
+ for (i = 0; i < upbound && 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;
+ // XXX: fallthrough here?
+ 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 bytes_received, state, fd, v_pending;
+ ssize_t ret;
+ static const uint8_t ayt[] = "[Responding to AYT: Yes, I'm here.]\n";
+ static const uint8_t will_sga[] = { IAC, WILL, TELOPT_SGA, '\0' };
+ static const uint8_t will_tm[] = { IAC, WILL, TELOPT_TM, '\0' };
+ unsigned char *start, *s, *d;
+
+ 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;
+ v_pending = con[who].numPending;
+ fd = con[who].fd;
+
+ ret = recv(fd, start + v_pending, MAX_STRING_LENGTH - 1 - v_pending, 0);
+ if (ret < INT_MIN || ret > INT_MAX)
+ errx(1, "%s: return out of bounds", __func__);
+ bytes_received = (int)ret;
+
+ if (bytes_received == 0) { // error: they've disconnected
+ return -1;
+ } else if (bytes_received == -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
+ bytes_received = 0;
+ }
+ }
+
+ if (con[who].processed)
+ s += v_pending;
+ else {
+ if (bytes_received > INT_MAX - v_pending)
+ errx(1, "%s: integer overflow", __func__);
+ bytes_received += v_pending;
+ }
+ d = s;
+
+ while (bytes_received-- > 0) {
+ 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 (bytes_received > 0)
+ memmove(start, s + 1, bytes_received);
+ con[who].state = 0;
+ con[who].numPending = bytes_received;
+ 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, sizeof ayt - 1, 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 (bytes_received > 0)
+ memmove(start, s + 1, bytes_received);
+ con[who].state = 0;
+ con[who].numPending = bytes_received;
+ 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) {
+ if (send(fd, (char *)will_tm,
+ sizeof will_tm - 1, 0) == -1)
+ warn("%s: cannot send", __func__);
+ } else if (*s == TELOPT_SGA) {
+ if (send(fd, (char *)will_sga,
+ sizeof will_sga - 1, 0) == -1)
+ warn("%s: cannot send", __func__);
+ }
+ state = 2;
+ break;
+ }
+
+ s++;
+ } // while
+
+ 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 p_port)
+{
+ int opt, ret;
+ 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(p_port);
+
+ /*
+ * Attempt to allow rebinding to the port...
+ */
+
+ opt = 1;
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt,
+ sizeof opt);
+ if (ret == -1)
+ warn("%s: SO_REUSEADDR", __func__);
+
+ opt = 1;
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt,
+ sizeof opt);
+ if (ret == -1)
+ warn("%s: SO_KEEPALIVE", __func__);
+
+ lingeropt.l_onoff = 0;
+ lingeropt.l_linger = 0;
+ ret = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *) &lingeropt,
+ sizeof(lingeropt));
+ if (ret == -1)
+ warn("%s: SO_LINGER", __func__);
+
+ 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)
+{
+ int ret;
+ static unsigned char wont_echo[] = {IAC, WONT, TELOPT_ECHO, '\0'};
+
+ ret = send(fd, (char *)wont_echo, sizeof wont_echo - 1, 0);
+ if (ret == -1)
+ warn("%s: cannot send", __func__);
+}
+
+PUBLIC void
+turn_echo_off(int fd)
+{
+ int ret;
+ static unsigned char will_echo[] = {IAC, WILL, TELOPT_ECHO, '\0'};
+
+ ret = send(fd, (char *)will_echo, sizeof will_echo - 1, 0);
+ if (ret == -1)
+ warn("%s: cannot send", __func__);
+}
+
+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;
+}