//  $Id: DCCrelay.java,v 1.2 2002/11/01 14:25:42 Administrator Exp $
//  Copyright (c) 2002 mobileways.de. All rights reserved.

/*
 *  DISCLAIMER OF WARRANTY
 *
 *  To the extent permitted under applicable law, this source code
 *  ("DCCrelay.java") is LICENSED to you "AS IS".
 *
 *  THERE IS NO WARRANTY OF ANY KIND, either expressed or implied,
 *  as to its use or performance or fitness for any particular purpose.
 *  IN NO EVENT WILL mobileways.de or its suppliers be liable to you
 *  or any other party for any damages, claims, costs or any consequential,
 *  direct, indirect, incidental damages, or any lost profits or lost
 *  savings EVEN IF mobileways.de or any representative has been advised
 *  of the possibility of such loss, damages, claims or costs or for any
 *  claim by any third party.
 *
 */

//  This source code was NOT written to win a beauty contest ...

import java.io.*;
import java.net.*;

interface TimeoutInterface
{
	public abstract boolean timeout();
}

class Timeout extends Thread
{
	protected long timeout;
	protected TimeoutInterface timeoutInterface;

	public Timeout(long timeout, TimeoutInterface tInterface)
	{
		this.timeout = timeout;
		timeoutInterface = tInterface;
	}

	public void run()
	{
		try
		{
			do
			{
				Thread.sleep(timeout);
			}
			while (!timeoutInterface.timeout());
		}	
		catch (Exception e) { }
	}
}

class Pipe extends Thread
{
	protected InputStream in;
	protected OutputStream out;

	// should be synchronized in a proper implementation !!!
	public boolean active = false;

	public int amount = 0;

	public Pipe(InputStream in, OutputStream out)
	{
		this.in = in;
		this.out = out;
	}

	public void run()
	{
		byte[] buffer = new byte[8192];
		int n;

		try
		{
			while ((n = in.read(buffer)) > 0)
			{
				active = true;
				amount += n;

				out.write(buffer, 0, n);
				out.flush();
			}
		}
		catch (Exception e)
		{
		}

		close();
	}

	public void close()
	{
		try { in.close(); } catch (Exception e) { }
		try { out.close(); } catch (Exception e) { }
	}
}

class DCCtunnel extends Thread implements TimeoutInterface
{
	protected static int globalId = 0;

	protected int id;
	protected ServerSocket serverSocket;
	protected Socket senderSocket;
	protected Pipe pipe1, pipe2;

	public DCCtunnel(Socket senderSocket)
	throws Exception
	{
		id = globalId++;
		this.senderSocket = senderSocket;
		start();
	}	

	public void run()
	{
		try
		{
			System.err.println(id + ": Request from "
				+ senderSocket.getInetAddress().getHostName());

			InputStream senderIn = senderSocket.getInputStream();
			OutputStream senderOut = senderSocket.getOutputStream();

			serverSocket = new ServerSocket(0);

			// 0 for direct tunnel, 1 for redirect to other relay,
			// 2 for reject!
			byte[] hello = new byte[7];
			hello[0] = 0;

			byte[] temp = DCCrelay.localhost.getAddress();
			for (int i = 0; i < 4; i++)
				hello[i + 1] = temp[i];

			int port = serverSocket.getLocalPort();
			hello[5] = (byte) (port >> 8);
			hello[6] = (byte)  port;

			senderOut.write(hello);
			senderOut.flush();

			System.err.println(id + ": Waiting on "
				+ DCCrelay.localhost + ":" + port);

			// 3 min. for connection timeout
			Timeout timeout = new Timeout(180 * 1000, this);
			timeout.start();

			Socket s = serverSocket.accept();

			serverSocket.close();
			serverSocket = null;

			timeout.interrupt();

			System.err.println(id + ": Tunneling from "
				+ senderSocket.getInetAddress().getHostName()
				+ " to " + s.getInetAddress().getHostName());

			pipe1 = new Pipe(senderIn, s.getOutputStream());
			pipe1.start();

			pipe2 = new Pipe(s.getInputStream(), senderOut);

			// 1 min. for read timeout
			timeout = new Timeout(60 * 1000, this);
			timeout.start();

			// do not run this pipe as thread !!!
			pipe2.run();

			timeout.interrupt();

			System.err.println(id + ": Finished ("
				+ (pipe1.amount+pipe2.amount) + "b)");
		}
		catch (Exception e)
		{
		}
	}

	protected synchronized void close()
	{
		if (serverSocket == null && senderSocket == null
			&& pipe1 == null && pipe2 == null)
		{
			return;
		}

		if (serverSocket != null)
		{
			try { serverSocket.close(); } catch (Exception e) { };
			serverSocket = null;
		}

		if (senderSocket != null)
		{
			try { senderSocket.close(); } catch (Exception e) { };
			senderSocket = null;
		}

		int amount = 0;

		if (pipe1 != null)
		{
			pipe1.close();
			amount += pipe1.amount;
			pipe1 = null;
		}

		if (pipe2 != null)
		{
			pipe2.close();
			amount += pipe2.amount;
			pipe2 = null;
		}

		System.err.println(id + ": Closed (" + amount + "b)");
	}

	public boolean timeout()
	{
		if (serverSocket != null || pipe1 == null || pipe2 == null
			|| (!pipe1.active && !pipe2.active))
		{
			System.err.println(id + ": Timeout");
			close();
			return true;
		}
		else
			return pipe1.active = pipe2.active = false;
	}
}

public class DCCrelay
{
	static public InetAddress localhost;

	static public void main(String args[])
	throws Exception
	{
		if (args.length != 1 && args.length != 2 && args.length != 4)
		{
			System.err.print("Usage: java DCCrelay ");
			System.err.print("port [local-host]");
			System.err.print(
				" | -r port redirect-host redirect-port");
			System.err.println();

			System.exit(-1);
		}

		byte[] redirectHello = null;

		int port;

		if (args.length == 4)
		{
			byte[] temp = args[2].getBytes();

			redirectHello = new byte[1 + temp.length + 1 + 2];
			redirectHello[0] = 1;

			System.arraycopy(temp,0, redirectHello,1, temp.length);
			redirectHello[1 + temp.length] = (byte) 0;

			port = Integer.parseInt(args[3]);
			redirectHello[1 + temp.length + 1] = (byte) (port >> 8);
			redirectHello[1 + temp.length + 2] = (byte)  port;

			port = Integer.parseInt(args[1]);
		}
		else
		{
			port = Integer.parseInt(args[0]);
		}

		if (args.length == 2)
			localhost = InetAddress.getByName(args[1]);
		else
			localhost = InetAddress.getLocalHost();

		ServerSocket s = new ServerSocket(port);

		System.err.print("Server on "
			+ s.getInetAddress().getHostName() + ":" + port
			+ " (" + localhost + ")");

		if (redirectHello == null)
		{
			System.err.println();

			for ( ; ; )
			{
				try
				{
					new DCCtunnel(s.accept());
				}
				catch (Exception e)
				{
					System.err.println(e);
				}
			}
		}
		else
		{
			// for testing-purpose only ...

			System.err.println(" [redirecting to " + args[2]
				+ ":" + args[3] + "]");

			Socket cs;
			OutputStream out;

			for ( ; ; )
			{
				try
				{
					cs = s.accept();
					out = cs.getOutputStream();
					out.write(redirectHello);
					cs.close();
				}
				catch (Exception e)
				{
				}
			}
		}
	}
}
