/* * mcclient - mud compression server (windows version) * * Copyright (C) 1998 Oliver Jowett <icecube@ihug.co.nz> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING); if not, write to the * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* Mingw32 redirect-and-decompress server for windows (also linux and probably * most unixes with minor tweaks, but there are unix clients that natively * support the protocol anyway..) */ /* Modified: 20000528 */ /* how do you reliably detect windows? ugh. */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #ifdef LINUX #define WSAEWOULDBLOCK EWOULDBLOCK #define WSAEINPROGRESS EINPROGRESS #define WSAGetLastError() errno #define INVALID_SOCKET (-1) #define closesocket close #define SOCKET int #define ioctlsocket ioctl #include <errno.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/time.h> #include <unistd.h> #include <netdb.h> #include <sys/ioctl.h> #else #include <windows.h> #endif #define UPDATE_INTERVAL 300 #include "mccpDecompress.h" typedef struct s_redirect { char *name; struct sockaddr_in remote; SOCKET controlfd; struct s_redirect *next; } redirect; typedef struct s_connection { char *name; int port; SOCKET localfd; SOCKET remotefd; char *to_mud; /* waiting to send to mud */ int to_mud_len; int to_mud_size; char *from_mud; /* waiting to decompress */ int from_mud_len; int from_mud_size; char *to_client; /* waiting to send to client */ int to_client_len; int to_client_size; int closing; /* now closing? */ mc_state *mcinfo; struct s_connection *next; } connection; redirect *redirect_list = NULL; connection *connection_list = NULL; void init(void) { #ifndef LINUX WSADATA wsadata; #define WSVERS MAKEWORD(2,2) #endif fprintf(stderr, "mcclient: initialising\n"); #ifndef LINUX if (WSAStartup(WSVERS, &wsadata) != 0) { printf("Failed to initialise Windows Sockets.\n"); exit(1); } #endif } void cleanup(void) { fprintf(stderr, "mcclient: shutting down\n"); #ifndef LINUX WSACleanup(); #endif } void close_connection(connection *c); void nonblock(SOCKET s) { unsigned long on = 1; ioctlsocket(s, FIONBIO, &on); } void new_connection(redirect *r) { SOCKET s, remote; connection *c; struct sockaddr_in peer; int size; size = sizeof(peer); s = accept(r->controlfd, (struct sockaddr *)&peer, &size); if (s == INVALID_SOCKET) return; fprintf(stderr, "New connection to port %d from %s:%d.\n", ntohs(r->remote.sin_port), inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));; nonblock(s); remote = socket(AF_INET, SOCK_STREAM, 0); if (remote == INVALID_SOCKET) { fprintf(stderr, "socket: %d\n", WSAGetLastError()); closesocket(s); return; } c = malloc(sizeof(*c)); c->to_mud_len = 0; c->from_mud_len = 0; c->to_client_len = 0; c->to_mud_size = 1024; c->from_mud_size = 1024; c->to_client_size = 1024; c->to_mud = malloc(c->to_mud_size); c->from_mud = malloc(c->from_mud_size); c->to_client = malloc(c->to_client_size); c->mcinfo = mudcompress_new(); c->localfd = s; c->remotefd = remote; c->name = r->name; c->port = ntohs(r->remote.sin_port); c->closing = 0; fprintf(stderr, " redirecting to [%s] %s:%d ... ", inet_ntoa(r->remote.sin_addr), r->name, ntohs(r->remote.sin_port)); if (connect(remote, (struct sockaddr *)&r->remote, sizeof(struct sockaddr_in)) < 0 && WSAGetLastError() != WSAEINPROGRESS) { fprintf(stderr, "failed: %d\n", WSAGetLastError()); close_connection(c); return; } nonblock(remote); c->next = connection_list; connection_list = c; fprintf(stderr, "succeeded.\n"); } void close_connection(connection *c) { connection *c2; unsigned long comp, uncomp; fprintf(stderr, "Closing connection to %s:%d.\n", c->name, c->port); mudcompress_stats(c->mcinfo, &comp, &uncomp); fprintf(stderr, " compression: %ld bytes decompressed to %ld bytes, %5.1f%%\n", comp, uncomp, 100.0 - (uncomp ? (float)comp / (float)uncomp * 100.0 : 0.0)); if (c->localfd != INVALID_SOCKET) closesocket(c->localfd); if (c->remotefd != INVALID_SOCKET) closesocket(c->remotefd); free(c->to_mud); free(c->from_mud); free(c->to_client); mudcompress_delete(c->mcinfo); if (c == connection_list) connection_list = c->next; else { for (c2 = connection_list; c2 && c2->next != c; c2 = c2->next) ; if (c2) c2->next = c->next; } free(c); } void conn_exception(connection *c) { close_connection(c); } void mud_send(connection *c, const char *buf, int len) { int old; old = c->to_mud_size; while (c->to_mud_size < c->to_mud_len + len) c->to_mud_size *= 2; if (c->to_mud_size != old) c->to_mud = realloc(c->to_mud, c->to_mud_size); memcpy(c->to_mud + c->to_mud_len, buf, len); c->to_mud_len += len; } int conn_read_local(connection *c) { int n; char buf[2048]; for (;;) { n = recv(c->localfd, buf, sizeof(buf), 0); if (n == 0) { /* EOF */ close_connection(c); return 0; } if (n < 0) { if (WSAGetLastError() == WSAEWOULDBLOCK) return 1; #ifdef LINUX if (errno == EAGAIN) return 1; #endif close_connection(c); return 0; } mud_send(c, buf, n); } } int conn_read_remote(connection *c) { int n; char buf[2048]; const char *resp; int old; for (;;) { n = recv(c->remotefd, buf, sizeof(buf), 0); if (n == 0) { /* EOF */ c->closing = 1; return 1; } if (n < 0) { if (WSAGetLastError() == WSAEWOULDBLOCK) return 1; #ifdef LINUX if (errno == EAGAIN) return 1; #endif close_connection(c); return 0; } mudcompress_receive(c->mcinfo, buf, n); resp = mudcompress_response(c->mcinfo); while (resp) { mud_send(c, resp, strlen(resp)); resp = mudcompress_response(c->mcinfo); } n = mudcompress_pending(c->mcinfo); while (n) { old = c->to_client_size; while (c->to_client_size < c->to_client_len + n) c->to_client_size *= 2; if (c->to_client_size != old) c->to_client = realloc(c->to_client, c->to_client_size); n = mudcompress_get(c->mcinfo, c->to_client + c->to_client_len, c->to_client_size - c->to_client_len); c->to_client_len += n; n = mudcompress_pending(c->mcinfo); } if (mudcompress_error(c->mcinfo)) { fprintf(stderr, "%s:%d: compression error!\n", c->name, c->port); close_connection(c); return 0; } } } int conn_write_local(connection *c) { int n; while (c->to_client_len) { n = send(c->localfd, c->to_client, c->to_client_len, 0); if (n == 0) return 1; if (n < 0) { if (WSAGetLastError() == WSAEWOULDBLOCK) return 1; #ifdef LINUX if (errno == EAGAIN) return 1; #endif close_connection(c); return 0; } c->to_client_len -= n; memmove(c->to_client, c->to_client + n, c->to_client_len); } if (c->closing) { close_connection(c); return 0; } return 1; } int conn_write_remote(connection *c) { int n; while (c->to_mud_len) { n = send(c->remotefd, c->to_mud, c->to_mud_len, 0); if (n == 0) return 1; if (n < 0) { if (WSAGetLastError() == WSAEWOULDBLOCK) return 1; #ifdef LINUX if (errno == EAGAIN) return 1; #endif close_connection(c); return 0; } c->to_mud_len -= n; memmove(c->to_mud, c->to_mud + n, c->to_mud_len); } return 1; } void loop(void) { time_t last_update = time(NULL); fprintf(stderr, "Now accepting connections.\n"); for (;;) { fd_set in, out, exc; int n; connection *c, *cnext; redirect *r; struct timeval tv; FD_ZERO(&in); FD_ZERO(&out); FD_ZERO(&exc); for (c = connection_list; c; c = c->next) { FD_SET(c->localfd, &in); FD_SET(c->remotefd, &in); FD_SET(c->localfd, &exc); FD_SET(c->remotefd, &exc); if (c->to_client_len > 0) FD_SET(c->localfd, &out); if (c->to_mud_len > 0) FD_SET(c->remotefd, &out); } for (r = redirect_list; r; r = r->next) { FD_SET(r->controlfd, &in); } /* Now get the info */ tv.tv_sec = UPDATE_INTERVAL - (time(NULL) - last_update); tv.tv_usec = 0; n = select(FD_SETSIZE, &in, &out, &exc, &tv); if (n < 0) { /* Hm. */ fprintf(stderr, "select: %d\n", WSAGetLastError()); cleanup(); exit(1); } /* New connections? */ for (r = redirect_list; r; r = r->next) { if (FD_ISSET(r->controlfd, &in)) { new_connection(r); } } /* Existing connections? */ for (c = connection_list; c; c = cnext) { cnext = c->next; if (FD_ISSET(c->localfd, &exc)) { conn_exception(c); continue; } if (FD_ISSET(c->remotefd, &exc)) { conn_exception(c); continue; } if (FD_ISSET(c->localfd, &in)) if (!conn_read_local(c)) continue; if (c->closing || FD_ISSET(c->localfd, &out)) if (!conn_write_local(c)) continue; if (FD_ISSET(c->remotefd, &in)) if (!conn_read_remote(c)) continue; if (FD_ISSET(c->remotefd, &out)) if (!conn_write_remote(c)) continue; } /* Compression stats update? */ if (time(NULL) >= last_update + UPDATE_INTERVAL) { unsigned long comp, uncomp; for (c = connection_list; c; c = cnext) { cnext = c->next; if (mudcompress_compressing(c->mcinfo)) { mudcompress_stats(c->mcinfo, &comp, &uncomp); fprintf(stderr, "%s:%d: decompressed %ld -> %ld bytes, %5.1f%%\n", c->name, c->port, comp, uncomp, 100.0 - (uncomp ? (float)comp / (float)uncomp * 100.0 : 0.0)); } } last_update = time(NULL); } /* Loop */ } } void readconfig(void) { FILE *f; char line[1024]; char remote_h[100]; short local_p, remote_p; f = fopen("mcclient.cfg", "r"); if (!f) { fprintf(stderr, "Can't read mcclient.cfg.\n"); cleanup(); exit(1); } while (fgets(line, 1024, f) != NULL) { if (line[0] == '\n' || line[0] == '#') continue; if (sscanf(line, "%hu %100s %hu", &local_p, remote_h, &remote_p) == 3) { struct sockaddr_in local; struct in_addr remote; redirect *r; SOCKET control; int on = 1; if ((remote.s_addr = inet_addr (remote_h)) == (unsigned long)-1) { struct hostent *h; if (!(h = gethostbyname (remote_h))) { fprintf(stderr, "Can't resolve host: %s.\n", remote_h); cleanup(); exit(1); } memcpy ((char *) &remote, h->h_addr, sizeof (remote)); } control = socket(AF_INET, SOCK_STREAM, 0); if (control == INVALID_SOCKET) { fprintf(stderr, "socket: %d\n", WSAGetLastError()); cleanup(); exit(1); } setsockopt(control, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); local.sin_family = AF_INET; local.sin_addr.s_addr = inet_addr("127.0.0.1"); /* avoid remote proxying */ local.sin_port = htons(local_p); if (bind(control, (struct sockaddr *)&local, sizeof(local)) < 0) { fprintf(stderr, "bind port %d: %d\n", local_p, WSAGetLastError()); cleanup(); exit(1); } if (listen(control, 3) < 0) { fprintf(stderr, "listen port %d: %d\n", local_p, WSAGetLastError()); cleanup(); exit(1); } r = malloc(sizeof(*r)); r->name = strdup(remote_h); r->remote.sin_family = AF_INET; r->remote.sin_addr = remote; r->remote.sin_port = htons(remote_p); r->controlfd = control; r->next = redirect_list; redirect_list = r; fprintf(stdout, "Local port %d redirects to remote host [%s] %s:%d\n", local_p, inet_ntoa(remote), remote_h, remote_p); } else { fprintf(stderr, "Bad config line: %s", line); cleanup(); exit(1); } } fclose(f); if (!redirect_list) { fprintf(stderr, "No redirections configured!\n"); cleanup(); exit(1); } } int main(int argc, char *argv[]) { init(); readconfig(); loop(); return 0; /* never reached */ }