mcclient-0.4/linux/
mcclient-0.4/win32/
mcclient-0.4/win32/mcclient/
/*
 * 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 */
}