/* tinyMUD port concentrator by Robert Hood */ /* Revision 2.0 */ #include <stdio.h> #include <signal.h> #include <sys/param.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/time.h> #include <sys/errno.h> #include <fcntl.h> #include <netdb.h> #include "config.h" void queue_message(int port, char *data, int len); void writelog(const char *fmt,...); #define BUFLEN 65536 #define CONC_MESSAGE "[ Connected to the TinyMUD port concentrator ]\n" #define PANIC_MESSAGE "\nGoing Down - Bye!\n" struct message { char *data; short len; struct message *next; }; struct conc_list { char status; /* * Status: 0 = Not connected 1 = Connected 2 = Disconnecting (waiting till * queue is empty) */ struct message *first, *last; } *clist; int mud_sock; int sock; int pid; int port = TINYPORT; int intport = INTERNAL_PORT; int clvl = 1; main(argc, argv) int argc; char *argv[]; { int l; if (argc > 1) port = atoi(argv[1]); if (argc > 2) intport = atoi(argv[2]); if (argc > 3) clvl = atoi(argv[3]); signal(SIGPIPE, SIG_IGN); /* Ignore I/O signals */ for (l = 3; l < NOFILE; ++l) /* Close all files from last process */ close(l); /* except stdin, stdout, stderr */ pid = 1; connect_mud(); /* Connect to interface.c */ setup(); /* Setup listen port */ mainloop(); /* main loop */ } connect_mud() { int temp; struct sockaddr_in sin; mud_sock = 0; while (mud_sock == 0) { mud_sock = socket(AF_INET, SOCK_STREAM, 0); if (mud_sock < 0) { perror("socket"); mud_sock = 0; } else { temp = 1; setsockopt(mud_sock, SOL_SOCKET, SO_REUSEADDR, (char *)&temp, sizeof(temp)); sin.sin_family = AF_INET; sin.sin_port = htons(intport); sin.sin_addr.s_addr = htonl(0x7F000001); temp = connect(mud_sock, (struct sockaddr *) & sin, sizeof(sin)); if (temp < 0) { perror("connect"); close(mud_sock); mud_sock = 0; } } if (mud_sock == 0) { sleep(1); fputs("retrying....\n", stderr); } } if (fcntl(mud_sock, F_SETFL, FNDELAY) == -1) { perror("make_nonblocking: fcntl"); } if (fcntl(mud_sock, F_SETFD, 1) == -1) { perror("close on execve: fcntl"); } #ifdef DEBUG fputs("connected!\n", stderr); #endif } setup() { int temp; struct sockaddr_in sin; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 1) { perror("socket"); exit(-1); } temp = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&temp, sizeof(temp)); sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = htonl(INADDR_ANY); temp = bind(sock, (struct sockaddr *) & sin, sizeof(sin)); if (temp < 0) { perror("bind"); exit(1); } temp = listen(sock, 5); if (temp < 0) { perror("listen"); exit(1); } } mainloop() { int found, newsock, lastsock, len, loop; int accepting, current = 0; int temp; struct timeval tv; struct sockaddr_in sin; struct hostent *hent; fd_set in, out; char *data; char *buf, header[4]; struct conc_list *cptr; struct message *tmsg; short templen; char *mainbuf, *outbuf; int mainlen, outlen; int command; int hlen; char *hostnm; /* Allocate huge buffer */ data = (char *)malloc(65536); /* Allocate array, one for each possible socket */ clist = (struct conc_list *) malloc(sizeof(struct conc_list) * NOFILE); /* Allocate I/O buffers for main I/O socket */ mainbuf = (char *)malloc(BUFLEN); mainlen = 0; outbuf = (char *)malloc(BUFLEN); outlen = 0; if (!data || !clist || !mainbuf || !outbuf) { perror("malloc"); exit(1); } /* Init array */ for (loop = 0; loop < NOFILE; ++loop) { cptr = &(clist[loop]); cptr->status = 0; cptr->first = 0; cptr->last = 0; } /* * Accept connections flag ON accepting = 1; /* lastsock for select() */ lastsock = sock + 1; /* mud_sock has already been established */ clist[mud_sock].status = 1; /* Special port # for control messages */ clist[0].status = 1; while (1) { if (pid < 0) { pid = vfork(); } if (pid == 0) { char pstr[32], istr[32], cstr[32]; sprintf(pstr, "%d", port); sprintf(istr, "%d", intport); sprintf(cstr, "%d", clvl + 1); execlp("concentrate", "conc", pstr, istr, cstr, 0); writelog("CONC %d:ACK!!!!!! exec failed! Exiting...\n", clvl); exit(1); /* Gee...now what? Should I try again? */ } /* zero out port selector masks */ FD_ZERO(&in); FD_ZERO(&out); /* set apropriate bit masks for I/O */ if (accepting) FD_SET(sock, &in); for (loop = 1; loop < NOFILE; ++loop) { cptr = &(clist[loop]); if (cptr->status) { FD_SET(loop, &in); if (cptr->first) FD_SET(loop, &out); } } if (outlen > 0) FD_SET(loop, &out); /* timeout for select */ tv.tv_sec = 1000; tv.tv_usec = 0; /* look for ports waiting for I/O */ found = select(lastsock, &in, &out, (fd_set *) 0, &tv); /* None found, skip the rest... */ if (found < 0) continue; /* New connection? */ if (accepting && FD_ISSET(sock, &in)) { len = sizeof(sin); newsock = accept(sock, (struct sockaddr *) & sin, &len); /* This limits the # of connections per concentrator */ if (newsock >= (NOFILE - 5)) { close(sock); accepting = 0; pid = -1; } if (newsock >= lastsock) lastsock = newsock + 1; cptr = &(clist[newsock]); cptr->status = 1; cptr->first = 0; cptr->last = 0; /* set to non-blocking mode */ if (fcntl(newsock, F_SETFL, FNDELAY) == -1) { perror("make_nonblocking: fcntl"); } /* set to close on execv */ if (fcntl(newsock, F_SETFD, 1) == -1) { perror("close on execv: fcntl"); } temp = 1; if (setsockopt(newsock, SOL_SOCKET, SO_KEEPALIVE, (char *)&temp, sizeof(temp)) < 0) { perror("keepalive setsockopt"); } queue_message(newsock, CONC_MESSAGE, sizeof(CONC_MESSAGE) - 1); /* build control code for connect */ data[0] = 0; data[1] = 1; /* connect */ data[2] = newsock; bcopy(&(sin.sin_addr.s_addr), data + 3, 4); hent = gethostbyaddr(&(sin.sin_addr.s_addr), sizeof(sin.sin_addr.s_addr), AF_INET); if (hent) strcpy(data + 7, hent->h_name); else strcpy(data + 7, inet_ntoa(sin.sin_addr.s_addr)); queue_message(mud_sock, data, 7 + strlen(data + 7)); #ifdef DEBUG writelog("CONC %d: USER CONNECT: sock %d, host %s\n", clvl, newsock, data + 7); #endif } /* recieve data from ports */ for (loop = 0; loop < NOFILE; ++loop) { cptr = &(clist[loop]); if (cptr->status && FD_ISSET(loop, &in)) { if (loop == 0) { } else if (loop == mud_sock) { if (mainlen < BUFLEN) { len = recv(loop, mainbuf + mainlen, BUFLEN - mainlen, 0); if (len <= 0) { /* This is quite useless, but what else am I supposed to do? */ writelog("CONC %d: Lost Connection\n", clvl); panic(); } mainlen += len; } while (mainlen > 2) { bcopy(mainbuf, &templen, 2); if (mainlen >= (templen + 2)) { queue_message(*(mainbuf + 2), mainbuf + 3, templen - 1); mainlen = mainlen - templen - 2; bcopy(mainbuf + templen + 2, mainbuf, mainlen); } else break; } } else { /* data + 1 so we can add port later w/o a bcopy */ len = recv(loop, data + 1, 65530, 0); if (len == 0) { disconnect(loop); } else if (len < 0) { /* Hmm..... */ writelog("CONC %d: recv: %s\n", clvl, strerror(errno)); } else { /* Add the port # to the data, and send it to interface.c */ data[0] = loop; queue_message(mud_sock, data, len + 1); } } } } /* Handle output */ for (loop = 0; loop < NOFILE; ++loop) { cptr = &(clist[loop]); if ((loop == 0) && (cptr->first)) { command = *(cptr->first->data); switch (command) { case 2: /* disconnect */ if (clist[*(cptr->first->data + 1)].status) clist[*(cptr->first->data + 1)].status = 2; else writelog("CONC %d: Recieved dissconnect for unknown user\n", clvl); break; default: writelog("CONC %d: Recieved unknown command %d\n", clvl, command); break; } free(cptr->first->data); tmsg = cptr->first; cptr->first = cptr->first->next; free(tmsg); if (!cptr->first) cptr->last = 0; } else if ((loop == mud_sock) && FD_ISSET(mud_sock, &out) && ((cptr->first) || (outlen > 0))) { while ((cptr->first) && ((BUFLEN - outlen) > (cptr->first->len + 2))) { templen = cptr->first->len; bcopy(&(templen), outbuf + outlen, 2); bcopy(cptr->first->data, outbuf + outlen + 2, templen); outlen += templen + 2; free(cptr->first->data); tmsg = cptr->first; cptr->first = cptr->first->next; free(tmsg); if (!cptr->first) cptr->last = 0; } if (outlen) { len = send(mud_sock, outbuf, outlen, 0); if (len > 0) { outlen -= len; bcopy(outbuf + len, outbuf, outlen); } else { panic(); } } } else if (FD_ISSET(loop, &out) && (cptr->first)) { len = send(loop, cptr->first->data, cptr->first->len, 0); free(cptr->first->data); tmsg = cptr->first; cptr->first = cptr->first->next; free(tmsg); if (!cptr->first) cptr->last = 0; } /* Test for pending disconnect */ else if ((cptr->status == 2) && (cptr->first == 0)) { cptr->status = 0; shutdown(loop, 0); close(loop); } } /* Test for emptyness */ if (!accepting) { for (loop = mud_sock + 1; loop < NOFILE; ++loop) if (clist[loop].status) break; if (loop == NOFILE) exit(0); } } } /* Properly disconnect a user */ disconnect(user) int user; { char header[4]; /* make control message for disconnect */ header[0] = 0; header[1] = 2; /* disconnect code */ header[2] = user; queue_message(mud_sock, header, 3); /* shutdown this socket */ clist[user].status = 0; close(user); #ifdef DEBUG writelog("CONC %d: USER DISCONNECT: %d\n", clvl, user); #endif } void queue_message(int port, char *data, int len) { struct message *ptr; ptr = (struct message *) malloc(sizeof(struct message)); ptr->data = (char *)malloc(len); ptr->len = len; bcopy(data, ptr->data, len); ptr->next = 0; if (clist[port].last == 0) clist[port].first = ptr; else (clist[port].last)->next = ptr; clist[port].last = ptr; } /* Kill off all connections quickly */ panic() { int loop; for (loop = 1; loop < NOFILE; ++loop) { if (clist[loop].status) { send(loop, PANIC_MESSAGE, sizeof(PANIC_MESSAGE), 0); shutdown(loop, 0); close(loop); } } exit(1); } /* Modified to send stuff to the main server for logging */ void writelog(const char *fmt,...) { va_list list; struct tm *tm; long t; char buffer[2048]; va_start(list, fmt); vsprintf(buffer + 2, fmt, list); buffer[0] = 0; buffer[1] = 4; /* remote log command */ queue_message(mud_sock, buffer, strlen(buffer + 2) + 2); va_end(list); }