/*
 

  Redirector

  keeps at least one listening socket, for incoming connections from servers.

  server socket protocol:

  Remote server connects to redirector and gives one or more of the commands
  below.  After each command we listen for another command.

  Commands:
    listen <listen-port-number> <connect-port-number>
      Listen on the listen port number.  When a connection comes in,
      connect back to the system that sent us this command at the
      connect port number, then go into transparent mode.  Listen port
      number can be 0 in which case we choose one.  This command returns
      a status code and the port number chosen.

    connect <ip-address> <port-number>
      Connect to the specified address and port.  Listen for handshake
      string; if not found, exit.  Pass handshake string back.  Go into
      transparent mode.

  Handshake:
    When connected to, the redirector prints a handshake string.  If this
    is not seen in response to the "connect" command an error is returned.

  Protocol:
    Remote server connects to redirector.  Gives listen command for first
    step in chain (listening port is not important).  Gives connect
    command for next step in chain, specifying 2nd redirector address and
    server port.  Gives listen command there, and so on.  On the last
    step in the chain give listen command for the desired port which will
    be published.

*/



#if WIN32

#include <windows.h>
#include <winsock.h>
#include <stdio.h>

WSADATA ws;

#else

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <string.h>
#include <ctype.h>

typedef int SOCKET;

#endif


#define CR '\015'
#define LF '\012'

#define RESP_SUCCESS	"Done\015\012"
#define RESP_HSHAKE	"Redirection Server\015\012"
#define RESP_ILLEGAL	"Illegal command\015\012"
#define RESP_SOCKERR	"Unable to create socket\015\012"
#define RESP_BINDERR	"Unable to bind to requested port\015\012"
#define RESP_HOSTERR	"Unable to locate requested host\015\012"
#define RESP_CONNERR	"Unable to connect to requested host/port\015\012"
#define RESP_HSHAKERR	"Unable to handshake with requested host/port\015\012"

#define CMD_CONNECT	"connect "
#define CMD_LISTEN	"listen "


char *progname;

usage()
{
    fprintf (stderr, "Usage: %s port\n\
  port is the port to listen on.  Commands on that port are\n\
    listen <listen-port-number> <connectback-port-number>\n\
      Listens on specified port, connects back when connected\n\
    connect <remote-host> <port-number>\n\
      Connect to specified machine, go into transparent mode\n", progname );
    exit (1);
}


static void
shuttle (SOCKET in, SOCKET out)
{
    char buf[1024];
    int n;

    while ((n = recv (in, buf, sizeof(buf), 0)) > 0)
	send (out, buf, n, 0);
}


/* Go into transparent mode between the two sockets, then exit */
static void
transparent (SOCKET s1, SOCKET s2)
{
    if (fork() == 0) {
	shuttle (s1, s2);
	shutdown( s1, 2 );
	shutdown( s2, 2 );
	exit(0);
    } else {
	shuttle (s2, s1);
	shutdown( s1, 2 );
	shutdown( s2, 2 );
	wait(0);
	exit(0);
    }
}

/* Wait on the specified socket, then connect back and go transparent */
static void
listener (SOCKET s, struct sockaddr_in *remoteaddr)
{
    struct sockaddr_in peeraddr;
    SOCKET s1, s2;
    int peeraddrsize;

    for ( ; ; ) {
	while (waitpid(-1, 0, WNOHANG) > 0)
	    ;
	peeraddrsize = sizeof(peeraddr);
	s1 = accept (s, (struct sockaddr *)&peeraddr, &peeraddrsize);
	if (s1 > 0) {
	    s2 = socket(AF_INET, SOCK_STREAM, 0);
	    if (s2 < 0) {
		close(s1);
	    } else if (connect (s2, (struct sockaddr *)remoteaddr,
				sizeof(struct sockaddr_in)) < 0) {
		close(s1);
	    } else {
		if (fork() == 0) {
		    close( s );
		    transparent(s1, s2);
		} else {
		    close( s1 );
		    close( s2 );
		}
	    }
	}
    }
}


/* Get a LF-terminated line from the socket */
static int
getline( SOCKET s, char *buf, int buflen )
{
    char *bp = buf;
    int n;

    while( bp < buf + buflen - 1 ) {
	n = recv( s, bp, 1, 0 );
	if ( n <= 0 )
	    return -1;
	if( *bp++ == LF ) {
	    *bp = '\0';
	    return 0;
	}
    }
}

/* Return an error and exit */
static void
commanderror( SOCKET s )
{
    send( s, RESP_ILLEGAL, strlen(RESP_ILLEGAL), 0 );
    close( s );
    exit (0);
}
    


static void
cmd_listen( SOCKET s, struct sockaddr_in *remoteaddr, char *bp )
{
    char *bp2;
    int listenport;
    int remoteport;
    SOCKET s1;
    struct sockaddr_in sockaddr;

    while( *bp && isspace(*bp))
	++bp;
    bp2 = strchr( bp, ' ' );
    if (bp2==NULL)
	commanderror( s );
    ++bp2;
    while( *bp2 && isspace(*bp2))
	++bp2;
    listenport = atoi(bp);
    remoteport = atoi(bp2);

    if( listenport < 0  || remoteport <= 0 )
	commanderror( s );

    remoteaddr->sin_port = (unsigned short)htons(remoteport);
    
    s1 = socket(AF_INET, SOCK_STREAM, 0);
    if (s1 < 0) {
	send( s, RESP_SOCKERR, strlen(RESP_SOCKERR), 0 );
	close( s );
	exit (0);
    }
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = (unsigned short)htons(listenport);
    sockaddr.sin_addr.s_addr = INADDR_ANY;
    if (bind (s1, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
	send( s, RESP_BINDERR, strlen(RESP_BINDERR), 0 );
	close( s );
	exit (0);
    }
    listen( s1, 5 );
    if( fork() == 0 ) {
	close( s );
	listener ( s1, remoteaddr );
    }
    close(s1);
    send( s, RESP_SUCCESS, strlen(RESP_SUCCESS), 0 );
}


static void
cmd_connect( SOCKET s, struct sockaddr_in *remoteaddr, char *bp )
{
    struct hostent *targetinfo;
    struct sockaddr_in sockaddr;
    SOCKET s1;
    char *bp2;
    char *target;
    int remoteport;
    int n;
    char buf[1024];

    while( *bp && isspace(*bp))
	++bp;
    bp2 = strchr( bp, ' ' );
    if (bp2==NULL)
	commanderror( s );
    *bp2++ = '\0';
    while( *bp2 && isspace(*bp2))
	++bp2;
    target = bp;
    remoteport = atoi(bp2);

    s1 = socket(AF_INET, SOCK_STREAM, 0);
    if (s1 < 0) {
	send( s, RESP_SOCKERR, strlen(RESP_SOCKERR), 0 );
	close( s );
	exit (0);
    }
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = (unsigned short)htons(remoteport);

    if (!(targetinfo = gethostbyname(target)) ||
	!targetinfo->h_addr_list) {
	send( s, RESP_HOSTERR, strlen(RESP_HOSTERR), 0 );
	close( s );
	exit (0);
    }
    sockaddr.sin_addr.s_addr = **(u_long **)targetinfo->h_addr_list;
    if (connect (s1, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
	send( s, RESP_CONNERR, strlen(RESP_CONNERR), 0 );
	close( s );
	exit (0);
    }

    n = getline( s1, buf, sizeof(buf) );
    if ( n < 0  || strncmp( buf, RESP_HSHAKE, strlen(RESP_HSHAKE) ) != 0 ) {
	send( s, RESP_HSHAKERR, strlen(RESP_HSHAKERR), 0 );
	close( s );
	exit (0);
    }
    send( s, buf, strlen(buf), 0 );

    transparent( s, s1 );
}

/* Handle commands on a server socket connection */
static void
command( SOCKET s, struct sockaddr_in *remoteaddr )
{
    char buf[1024];
    char *bp;
    int n;

    for ( ; ; ) {
	send( s, RESP_HSHAKE, strlen(RESP_HSHAKE), 0 );
	while (waitpid(-1, 0, WNOHANG) > 0)
	    ;
	n = getline( s, buf, sizeof(buf) );
	if ( n < 0 ) {
	    close( s );
	    exit (0);
	}

	if (strncmp(buf, CMD_LISTEN, strlen(CMD_LISTEN)) == 0) {
	    cmd_listen( s, remoteaddr, buf+strlen(CMD_LISTEN) );
	} else if (strncmp(buf, CMD_CONNECT, strlen(CMD_CONNECT)) == 0) {
	    cmd_connect( s, remoteaddr, buf+strlen(CMD_CONNECT) );
	} else {
	    commanderror( s );
	}
    }
}


/* Wait on the server socket, handle commands */
static void
server( SOCKET s )
{
    SOCKET s1;
    struct sockaddr_in peeraddr;
    int peeraddrsize;

    for ( ; ; ) {
	while (waitpid(-1, 0, WNOHANG) > 0)
	    ;
	peeraddrsize = sizeof(peeraddr);
	s1 = accept (s, (struct sockaddr *)&peeraddr, &peeraddrsize);
	if (s1 > 0) {
	    if (fork() == 0) {
		close( s );
		command( s1, &peeraddr );
	    } else {
		close( s1 );
	    }
	}
    }
}


main (int ac, char **av)
{
    int port;
    SOCKET s, s1;
    int n;
    struct sockaddr_in sockaddr, otheraddr;
    int otheraddrsize;
    char buf[1024];

#if WIN32
    WSAStartup (0x0101, &ws);
#endif

    progname = av[0];
    if (ac != 2)
	usage();

    port = atoi(av[1]);

    if (port < 0  ||  port > 65536)
	usage();
    
    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
	perror ("socket");
	exit (2);
    }
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = (unsigned short)htons(port);
    otheraddrsize = sizeof(otheraddr);
    sockaddr.sin_addr.s_addr = INADDR_ANY;
    if (bind (s, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
	perror ("bind");
	exit (2);
    }
    if (listen(s, 5) < 0) {
	perror ("listen");
	exit (2);
    }
    server( s );
}

