diff options
| author | Kenny Root <kenny@the-b.org> | 2008-11-08 10:01:55 +0000 | 
|---|---|---|
| committer | Kenny Root <kenny@the-b.org> | 2008-11-08 10:01:55 +0000 | 
| commit | da6d73f5a5818b3cc874f7ed319014d591e101e8 (patch) | |
| tree | ba799dfa55a47c77d674af005a77a207de33d0d5 | |
| parent | a15922f44e480a8a6c09741507b1af00d1468d3b (diff) | |
| download | connectbot-da6d73f5a5818b3cc874f7ed319014d591e101e8.tar.gz connectbot-da6d73f5a5818b3cc874f7ed319014d591e101e8.tar.bz2 connectbot-da6d73f5a5818b3cc874f7ed319014d591e101e8.zip  | |
* Add dynamic port forwarding.
26 files changed, 3880 insertions, 8 deletions
diff --git a/res/layout/dia_portforward.xml b/res/layout/dia_portforward.xml index 0bd00c0..6e7c522 100644 --- a/res/layout/dia_portforward.xml +++ b/res/layout/dia_portforward.xml @@ -53,7 +53,7 @@  				android:layout_width="wrap_content"  				android:layout_height="wrap_content"  				android:text="@string/portforward_local" -				android:paddingRight="30dip" +				android:paddingRight="10dip"  				/>  			<RadioButton @@ -61,8 +61,15 @@  				android:layout_width="wrap_content"  				android:layout_height="wrap_content"  				android:text="@string/portforward_remote" +				android:paddingRight="10dip"  				/> +			<RadioButton +				android:id="@+id/portforward_dynamic" +				android:layout_width="wrap_content" +				android:layout_height="wrap_content" +				android:text="@string/portforward_dynamic" +				/>  		</RadioGroup>  		<TableLayout  diff --git a/res/layout/wiz_eula.xml b/res/layout/wiz_eula.xml index 8427333..a6bcd8d 100644 --- a/res/layout/wiz_eula.xml +++ b/res/layout/wiz_eula.xml @@ -77,7 +77,7 @@  		android:layout_height="wrap_content"  		android:paddingTop="10dip"  		android:autoLink="web" -		android:text="Copyright \u00a9 2007-2008 Kenny Root http://the-b.org, Jeffrey Sharkey http://jsharkey.org\n\nBased in part on the Trilead SSH2 client, provided under a BSD-style license.  Copyright \u00a9 2007 Trilead AG.  http://www.trilead.com\n\nAlso based on JTA Telnet/SSH client, provided under the GPLv2 license.  Copyright \u00a9 Matthias L. Jugel, Marcus Meiner 1996-2005.  http://www.javassh.org" +		android:text="Copyright \u00a9 2007-2008 Kenny Root http://the-b.org, Jeffrey Sharkey http://jsharkey.org\n\nBased in part on the Trilead SSH2 client, provided under a BSD-style license.  Copyright \u00a9 2007 Trilead AG.  http://www.trilead.com\n\nAlso based on JTA Telnet/SSH client, provided under the GPLv2 license.  Copyright \u00a9 Matthias L. Jugel, Marcus Meiner 1996-2005.  http://www.javassh.org\n\nAlso based in part on the JSOCKS library, provided under the GNU LGPL license. http://jsocks.sourceforge.net/"  		android:textSize="14sp"  		android:textColor="#bebebe"  		/> diff --git a/res/values/strings.xml b/res/values/strings.xml index 57e842a..a8c1021 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -102,6 +102,7 @@  	<string name="portforward_local">Local</string>  	<string name="portforward_remote">Remote</string> +	<string name="portforward_dynamic">Dynamic</string>  	<string name="portforward_pos">Create port forward</string>  	<string name="portforward_neg">Cancel</string> diff --git a/src/com/trilead/ssh2/Connection.java b/src/com/trilead/ssh2/Connection.java index 8fbf6fa..2b244c2 100644 --- a/src/com/trilead/ssh2/Connection.java +++ b/src/com/trilead/ssh2/Connection.java @@ -933,6 +933,58 @@ public class Connection  	}
  	/**
 +	 * Creates a new {@link DynamicPortForwarder}. A
 +	 * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive
 +	 * at a local port via the secure tunnel to another host that is chosen via
 +	 * the SOCKS protocol.
 +	 * <p>
 +	 * This method must only be called after one has passed successfully the
 +	 * authentication step. There is no limit on the number of concurrent
 +	 * forwardings.
 +	 * 
 +	 * @param local_port
 +	 * @return A {@link DynamicPortForwarder} object.
 +	 * @throws IOException
 +	 */
 +	public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException
 +	{
 +		if (tm == null)
 +			throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
 +
 +		if (!authenticated)
 +			throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
 +
 +		return new DynamicPortForwarder(cm, local_port);
 +	}
 +	
 +	/**
 +	 * Creates a new {@link DynamicPortForwarder}. A
 +	 * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive
 +	 * at a local port via the secure tunnel to another host that is chosen via
 +	 * the SOCKS protocol.
 +	 * <p>
 +	 * This method must only be called after one has passed successfully the
 +	 * authentication step. There is no limit on the number of concurrent
 +	 * forwardings.
 +	 * 
 +	 * @param addr
 +	 *            specifies the InetSocketAddress where the local socket shall
 +	 *            be bound to.
 +	 * @return A {@link DynamicPortForwarder} object.
 +	 * @throws IOException
 +	 */
 +	public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException
 +	{
 +		if (tm == null)
 +			throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
 +
 +		if (!authenticated)
 +			throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
 +
 +		return new DynamicPortForwarder(cm, addr);
 +	}
 +	
 +	/**
  	 * Create a very basic {@link SCPClient} that can be used to copy files
  	 * from/to the SSH-2 server.
  	 * <p>
 diff --git a/src/com/trilead/ssh2/DynamicPortForwarder.java b/src/com/trilead/ssh2/DynamicPortForwarder.java new file mode 100644 index 0000000..1fece14 --- /dev/null +++ b/src/com/trilead/ssh2/DynamicPortForwarder.java @@ -0,0 +1,67 @@ +/* +	ConnectBot: simple, powerful, open-source SSH client for Android +	Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey +	 +	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 3 of the License, or +	(at your option) any dater 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.  If not, see <http://www.gnu.org/licenses/>. +*/ +package com.trilead.ssh2; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import com.trilead.ssh2.channel.ChannelManager; +import com.trilead.ssh2.channel.DynamicAcceptThread; + +/** + * A <code>DynamicPortForwarder</code> forwards TCP/IP connections to a local + * port via the secure tunnel to another host which is selected via the + * SOCKS protocol. Checkout {@link Connection#createDynamicPortForwarder(int)} + * on how to create one. + *  + * @author Kenny Root + * @version $Id: $ + */ +public class DynamicPortForwarder { +	ChannelManager cm; + +	DynamicAcceptThread dat; + +	DynamicPortForwarder(ChannelManager cm, int local_port) +			throws IOException +	{ +		this.cm = cm; + +		dat = new DynamicAcceptThread(cm, local_port); +		dat.setDaemon(true); +		dat.start(); +	} +	 +	DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr) throws IOException { +		this.cm = cm; + +		dat = new DynamicAcceptThread(cm, addr); +		dat.setDaemon(true); +		dat.start(); +	} + +	/** +	 * Stop TCP/IP forwarding of newly arriving connections. +	 *  +	 * @throws IOException +	 */ +	public void close() throws IOException +	{ +		dat.stopWorking(); +	} +} diff --git a/src/com/trilead/ssh2/channel/DynamicAcceptThread.java b/src/com/trilead/ssh2/channel/DynamicAcceptThread.java new file mode 100644 index 0000000..18fa279 --- /dev/null +++ b/src/com/trilead/ssh2/channel/DynamicAcceptThread.java @@ -0,0 +1,199 @@ +/* +	ConnectBot: simple, powerful, open-source SSH client for Android +	Copyright (C) 2007-2008 Kenny Root, Jeffrey Sharkey +	 +	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 3 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.  If not, see <http://www.gnu.org/licenses/>. + */ +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + +import net.sourceforge.jsocks.Proxy; +import net.sourceforge.jsocks.ProxyMessage; +import net.sourceforge.jsocks.ProxyServer; +import net.sourceforge.jsocks.Socks4Message; +import net.sourceforge.jsocks.Socks5Message; +import net.sourceforge.jsocks.server.ServerAuthenticator; +import net.sourceforge.jsocks.server.ServerAuthenticatorNone; + +/** + * DynamicAcceptThread. + *  + * @author Kenny Root + * @version $Id: + */ +public class DynamicAcceptThread extends Thread implements IChannelWorkerThread { +	private ChannelManager cm; +	private ServerSocket ss; +	 +	class DynamicAcceptRunnable extends ProxyServer { +		private ServerAuthenticator auth; +		private Socket sock; +		private InputStream in; +		private OutputStream out; +		private ProxyMessage msg; +		 +		public DynamicAcceptRunnable(ServerAuthenticator auth, Socket sock) { +			super(auth, sock); +			this.auth = auth; +			this.sock = sock; +		} + +		protected void startSession() throws IOException { +			sock.setSoTimeout(ProxyServer.iddleTimeout); + +			try { +				auth = auth.startSession(sock); +			} catch (IOException ioe) { +				System.out.println("Could not start SOCKS session"); +				ioe.printStackTrace(); +				auth = null; +				return; +			} + +			if (auth == null) { // Authentication failed +				System.out.println("SOCKS auth failed"); +				return; +			} + +			in = auth.getInputStream(); +			out = auth.getOutputStream(); + +			msg = readMsg(in); +			handleRequest(msg); +		} +		 +		protected void onConnect(ProxyMessage msg) throws IOException { +			ProxyMessage response = null; +			Channel cn = null; +			StreamForwarder r2l = null; +			StreamForwarder l2r = null; + +			try { +				/* +				 * This may fail, e.g., if the remote port is closed (in +				 * optimistic terms: not open yet) +				 */ + +				cn = cm.openDirectTCPIPChannel(msg.host, msg.port, +						sock.getInetAddress().getHostAddress(), +						sock.getPort()); + +			} catch (IOException e) { +				/* +				 * Simply close the local socket and wait for the next incoming +				 * connection +				 */ + +				try { +					sock.close(); +				} catch (IOException ignore) { +				} + +				return; +			} + +			try { +				r2l = new StreamForwarder(cn, null, null, cn.stdoutStream, out, "RemoteToLocal"); +				l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote"); +			} catch (IOException e) { +				try { +					/* +					 * This message is only visible during debugging, since we +					 * discard the channel immediatelly +					 */ +					cn.cm.closeChannel(cn, +							"Weird error during creation of StreamForwarder (" +									+ e.getMessage() + ")", true); +				} catch (IOException ignore) { +				} + +				return; +			} + +			r2l.setDaemon(true); +			l2r.setDaemon(true); +			r2l.start(); +			l2r.start(); +			 +			if (msg instanceof Socks5Message) { +				response = new Socks5Message(Proxy.SOCKS_SUCCESS, sock +						.getLocalAddress(), sock.getLocalPort()); +			} else { +				response = new Socks4Message(Socks4Message.REPLY_OK, sock +						.getLocalAddress(), sock.getLocalPort()); + +			} +			response.write(out); +		} +	} +	 +	public DynamicAcceptThread(ChannelManager cm, int local_port) +			throws IOException { +		this.cm = cm; + +		ss = new ServerSocket(local_port); +	} +	 +	public DynamicAcceptThread(ChannelManager cm, InetSocketAddress localAddress) +			throws IOException { +		this.cm = cm; + +		ss = new ServerSocket(); +		ss.bind(localAddress); +	} + +	public void run() { +		try { +			cm.registerThread(this); +		} catch (IOException e) { +			stopWorking(); +			return; +		} + +		while (true) { +			Socket sock = null; + +			try { +				sock = ss.accept(); +			} catch (IOException e) { +				stopWorking(); +				return; +			} + +			DynamicAcceptRunnable dar = new DynamicAcceptRunnable(new ServerAuthenticatorNone(), sock); +			Thread t = new Thread(dar); +			t.setDaemon(true); +			t.start(); +		} +	} +	 +	/* +	 * (non-Javadoc) +	 *  +	 * @see com.trilead.ssh2.channel.IChannelWorkerThread#stopWorking() +	 */ +	public void stopWorking() { +		try { +			/* This will lead to an IOException in the ss.accept() call */ +			ss.close(); +		} catch (IOException e) { +		} +	} +} diff --git a/src/net/sourceforge/jsocks/Authentication.java b/src/net/sourceforge/jsocks/Authentication.java new file mode 100644 index 0000000..4e9eae1 --- /dev/null +++ b/src/net/sourceforge/jsocks/Authentication.java @@ -0,0 +1,34 @@ +package net.sourceforge.jsocks;
 +
 +/**
 + The Authentication interface provides for performing method specific
 + authentication for SOCKS5 connections.
 +*/
 +public interface Authentication{
 +   /**
 +     This method is called when SOCKS5 server have selected a particular
 +     authentication method, for whch an implementaion have been registered.
 +
 +     <p>
 +     This method should return an array {inputstream,outputstream 
 +     [,UDPEncapsulation]}. The reason for that is that SOCKS5 protocol
 +     allows to have method specific encapsulation of data on the socket for 
 +     purposes of integrity or security. And this encapsulation should be 
 +     performed by those streams returned from the method. It is also possible
 +     to encapsulate datagrams. If authentication method supports such 
 +     encapsulation an instance of the UDPEncapsulation interface should be
 +     returned as third element of the array, otherwise either null should be
 +     returned as third element, or array should contain only 2 elements.
 +
 +     @param methodId Authentication method selected by the server.
 +     @param proxySocket Socket used to conect to the proxy.
 +     @return Two or three element array containing 
 +             Input/Output streams which should be used on this connection.
 +             Third argument is optional and should contain an instance
 +             of UDPEncapsulation. It should be provided if the authentication
 +             method used requires any encapsulation to be done on the
 +             datagrams.
 +   */
 +   Object[] doSocksAuthentication(int methodId,java.net.Socket proxySocket)
 +           throws java.io.IOException;
 +}
 diff --git a/src/net/sourceforge/jsocks/AuthenticationNone.java b/src/net/sourceforge/jsocks/AuthenticationNone.java new file mode 100644 index 0000000..676b2da --- /dev/null +++ b/src/net/sourceforge/jsocks/AuthenticationNone.java @@ -0,0 +1,17 @@ +package net.sourceforge.jsocks;
 +
 +/**
 +  SOCKS5 none authentication. Dummy class does almost nothing.
 +*/
 +public class AuthenticationNone implements Authentication{
 +
 +    public Object[] doSocksAuthentication(int methodId,
 +                                          java.net.Socket proxySocket)
 +           throws java.io.IOException{
 +
 +       if(methodId!=0) return null;
 +
 +       return new Object[] { proxySocket.getInputStream(),
 +                             proxySocket.getOutputStream()};
 +   }
 +}
 diff --git a/src/net/sourceforge/jsocks/Proxy.java b/src/net/sourceforge/jsocks/Proxy.java new file mode 100644 index 0000000..9d9b3fe --- /dev/null +++ b/src/net/sourceforge/jsocks/Proxy.java @@ -0,0 +1,404 @@ +package net.sourceforge.jsocks;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.InterruptedIOException;
 +import java.io.OutputStream;
 +import java.net.InetAddress;
 +import java.net.Socket;
 +import java.net.UnknownHostException;
 +
 +/**
 +  Abstract class Proxy, base for classes Socks4Proxy and Socks5Proxy.
 +  Defines methods for specifying default proxy, to be
 +  used by all classes of this package.
 +*/
 +
 +public abstract class Proxy{
 +
 +//Data members
 +   //protected InetRange directHosts = new InetRange();
 +
 +   protected InetAddress proxyIP = null;
 +   protected String proxyHost = null;
 +   protected int proxyPort;
 +   protected Socket proxySocket = null;
 +
 +   protected InputStream in;
 +   protected OutputStream out;
 +
 +   protected int version;
 +
 +   protected Proxy chainProxy = null;
 +
 +
 +//Protected static/class variables
 +   protected static Proxy defaultProxy = null;
 +
 +//Constructors
 +//====================
 +	Proxy(String proxyHost, int proxyPort) throws UnknownHostException {
 +		this.proxyHost = proxyHost;
 +		this.proxyIP = InetAddress.getByName(proxyHost);
 +		this.proxyPort = proxyPort;
 +	}
 +
 +   Proxy(Proxy chainProxy,InetAddress proxyIP,int proxyPort){
 +      this.chainProxy = chainProxy;
 +      this.proxyIP = proxyIP;
 +      this.proxyPort = proxyPort;
 +   }
 +
 +   Proxy(InetAddress proxyIP,int proxyPort){
 +      this.proxyIP = proxyIP;
 +      this.proxyPort = proxyPort;
 +   }
 +
 +   Proxy(Proxy p){
 +      this.proxyIP = p.proxyIP;
 +      this.proxyPort = p.proxyPort;
 +      this.version = p.version;
 +   }
 +
 +//Public instance methods
 +//========================
 +
 +   /**
 +      Get the port on which proxy server is running.
 +    * @return Proxy port.
 +    */
 +   public int getPort(){
 +     return proxyPort;
 +   }
 +   /**
 +      Get the ip address of the proxy server host.
 +    * @return Proxy InetAddress.
 +    */
 +   public InetAddress getInetAddress(){
 +     return proxyIP;
 +   }
 +
 +    /**
 +       Get string representation of this proxy.
 +     * @returns string in the form:proxyHost:proxyPort \t Version versionNumber
 +     */
 +    public String toString(){
 +       return (""+proxyIP.getHostName()+":"+proxyPort+"\tVersion "+version);
 +    }
 +
 +
 +//Public Static(Class) Methods
 +//==============================
 +
 +   /**
 +    * Sets SOCKS4 proxy as default.
 +      @param hostName Host name on which SOCKS4 server is running.
 +      @param port Port on which SOCKS4 server is running.
 +      @param user Username to use for communications with proxy.
 +    */
 +   public static void setDefaultProxy(String hostName,int port,String user)
 +                             throws UnknownHostException{
 +      defaultProxy = new Socks4Proxy(hostName,port,user);
 +   }
 +
 +   /**
 +    * Sets SOCKS4 proxy as default.
 +      @param ipAddress Host address on which SOCKS4 server is running.
 +      @param port Port on which SOCKS4 server is running.
 +      @param user Username to use for communications with proxy.
 +    */
 +   public static void setDefaultProxy(InetAddress ipAddress,int port,
 +                                      String user){
 +      defaultProxy = new Socks4Proxy(ipAddress,port,user);
 +   }
 +   /**
 +    * Sets SOCKS5 proxy as default.
 +    * Default proxy only supports no-authentication.
 +      @param hostName Host name on which SOCKS5 server is running.
 +      @param port Port on which SOCKS5 server is running.
 +    */
 +   public static void setDefaultProxy(String hostName,int port)
 +                             throws UnknownHostException{
 +      defaultProxy = new Socks5Proxy(hostName,port);
 +   }
 +   /**
 +    * Sets SOCKS5 proxy as default.
 +    * Default proxy only supports no-authentication.
 +      @param ipAddress Host address on which SOCKS5 server is running.
 +      @param port Port on which SOCKS5 server is running.
 +    */
 +   public static void setDefaultProxy(InetAddress ipAddress,int port){
 +      defaultProxy = new Socks5Proxy(ipAddress,port);
 +   }
 +   /**
 +    * Sets default proxy.
 +      @param p Proxy to use as default proxy.
 +    */
 +   public static void setDefaultProxy(Proxy p){
 +     defaultProxy = p;
 +   }
 +
 +   /**
 +      Get current default proxy.
 +    * @return Current default proxy, or null if none is set.
 +    */
 +   public static Proxy getDefaultProxy(){
 +     return defaultProxy;
 +   }
 +
 +   /**
 +     Parses strings in the form: host[:port:user:password], and creates
 +     proxy from information obtained from parsing.
 +     <p>
 +     Defaults: port = 1080.<br>
 +     If user specified but not password, creates Socks4Proxy, if user
 +     not specified creates Socks5Proxy, if both user and password are
 +     speciefied creates Socks5Proxy with user/password authentication.
 +     @param proxy_entry String in the form host[:port:user:password]
 +     @return Proxy created from the string, null if entry was somehow
 +             invalid(host unknown for example, or empty string)
 +   */
 +   public static Proxy parseProxy(String proxy_entry){
 +
 +      String proxy_host;
 +      int proxy_port = 1080;
 +      String proxy_user = null;
 +      String proxy_password = null;
 +      Proxy proxy;
 +
 +      java.util.StringTokenizer st = new java.util.StringTokenizer(
 +                                         proxy_entry,":");
 +      if(st.countTokens() < 1) return null;
 +
 +      proxy_host = st.nextToken();
 +      if(st.hasMoreTokens())
 +         try{
 +           proxy_port = Integer.parseInt(st.nextToken().trim());
 +         }catch(NumberFormatException nfe){}
 +
 +      if(st.hasMoreTokens())
 +         proxy_user = st.nextToken();
 +
 +      if(st.hasMoreTokens())
 +         proxy_password = st.nextToken();
 +
 +      try{
 +         if(proxy_user == null)
 +           proxy = new Socks5Proxy(proxy_host,proxy_port);
 +         else if(proxy_password == null)
 +           proxy = new Socks4Proxy(proxy_host,proxy_port,proxy_user);
 +         else{
 +           proxy = new Socks5Proxy(proxy_host,proxy_port);
 +           /*
 +           UserPasswordAuthentication upa = new UserPasswordAuthentication(
 +                                            proxy_user, proxy_password);
 +
 +           ((Socks5Proxy)proxy).setAuthenticationMethod(upa.METHOD_ID,upa);
 +           */
 +         }
 +      }catch(UnknownHostException uhe){
 +         return null;
 +      }
 +
 +      return proxy;
 +   }
 +
 +
 +//Protected Methods
 +//=================
 +
 +   protected void startSession()throws SocksException{
 +       try{
 +         proxySocket = new Socket(proxyIP,proxyPort);
 +         in = proxySocket.getInputStream();
 +         out = proxySocket.getOutputStream();
 +       }catch(SocksException se){
 +         throw se;
 +       }catch(IOException io_ex){
 +         throw new SocksException(SOCKS_PROXY_IO_ERROR,""+io_ex);
 +       }
 +   }
 +
 +   protected abstract Proxy copy();
 +   protected abstract ProxyMessage formMessage(int cmd,InetAddress ip,int port);
 +   protected abstract ProxyMessage formMessage(int cmd,String host,int port)
 +             throws UnknownHostException;
 +   protected abstract ProxyMessage formMessage(InputStream in)
 +             throws SocksException,
 +                    IOException;
 +   
 +
 +   protected ProxyMessage connect(InetAddress ip,int port)
 +             throws SocksException{
 +      try{
 +         startSession();
 +         ProxyMessage request  = formMessage(SOCKS_CMD_CONNECT,
 +			                     ip,port);
 +         return exchange(request);
 +      }catch(SocksException se){
 +         endSession();
 +         throw se;
 +      }
 +   }
 +   protected ProxyMessage connect(String host,int port)
 +             throws UnknownHostException,SocksException{
 +      try{
 +         startSession();
 +         ProxyMessage request  = formMessage(SOCKS_CMD_CONNECT,
 +			                     host,port);
 +         return exchange(request);
 +      }catch(SocksException se){
 +         endSession();
 +         throw se;
 +      }
 +   }
 +
 +   protected ProxyMessage bind(InetAddress ip,int port)
 +             throws SocksException{
 +      try{
 +         startSession();
 +         ProxyMessage request  = formMessage(SOCKS_CMD_BIND,
 +				             ip,port);
 +         return exchange(request);
 +      }catch(SocksException se){
 +         endSession();
 +         throw se;
 +      }
 +   }
 +   protected ProxyMessage bind(String host,int port)
 +             throws UnknownHostException,SocksException{
 +      try{
 +         startSession();
 +         ProxyMessage request  = formMessage(SOCKS_CMD_BIND,
 +				             host,port);
 +         return exchange(request);
 +      }catch(SocksException se){
 +         endSession();
 +         throw se;
 +      }
 +   }
 +
 +   protected ProxyMessage accept()
 +             throws IOException,SocksException{
 +      ProxyMessage msg;
 +      try{
 +         msg = formMessage(in);
 +      }catch(InterruptedIOException iioe){
 +         throw iioe;
 +      }catch(IOException io_ex){
 +         endSession();
 +         throw new SocksException(SOCKS_PROXY_IO_ERROR,"While Trying accept:"
 +         +io_ex);
 +      }
 +      return msg;
 +   }
 +
 +   protected ProxyMessage udpAssociate(InetAddress ip,int port)
 +             throws SocksException{
 +      try{
 +         startSession();
 +         ProxyMessage request  = formMessage(SOCKS_CMD_UDP_ASSOCIATE,
 +				             ip,port);
 +         if(request != null)
 +           return exchange(request);
 +      }catch(SocksException se){
 +         endSession();
 +         throw se;
 +      }
 +      //Only get here if request was null
 +      endSession();
 +      throw new SocksException(SOCKS_METHOD_NOTSUPPORTED,
 +      "This version of proxy does not support UDP associate, use version 5");
 +   }
 +   protected ProxyMessage udpAssociate(String host,int port)
 +             throws UnknownHostException,SocksException{
 +      try{
 +         startSession();
 +         ProxyMessage request  = formMessage(SOCKS_CMD_UDP_ASSOCIATE,
 +				             host,port);
 +         if(request != null) return exchange(request);
 +      }catch(SocksException se){
 +         endSession();
 +         throw se;
 +      }
 +      //Only get here if request was null
 +      endSession();
 +      throw new SocksException(SOCKS_METHOD_NOTSUPPORTED,
 +      "This version of proxy does not support UDP associate, use version 5");
 +   }
 +
 +
 +   protected void endSession(){
 +      try{
 +         if(proxySocket!=null) proxySocket.close();
 +         proxySocket = null;
 +      }catch(IOException io_ex){
 +      }
 +   }
 +
 +   /**
 +    *Sends the request to SOCKS server
 +    */
 +   protected void sendMsg(ProxyMessage msg)throws SocksException,
 +                                                  IOException{
 +      msg.write(out);
 +   }
 +
 +   /** 
 +    * Reads the reply from the SOCKS server
 +    */
 +   protected ProxyMessage readMsg()throws SocksException,
 +                                          IOException{
 +      return formMessage(in);
 +   }
 +   /**
 +    *Sends the request reads reply and returns it
 +    *throws exception if something wrong with IO
 +    *or the reply code is not zero
 +    */
 +   protected ProxyMessage exchange(ProxyMessage request)
 +                           throws SocksException{
 +      ProxyMessage reply;
 +      try{
 +         request.write(out);
 +         reply = formMessage(in);
 +      }catch(SocksException s_ex){
 +         throw s_ex;
 +      }catch(IOException ioe){
 +         throw(new SocksException(SOCKS_PROXY_IO_ERROR,""+ioe));
 +      }
 +      return reply;
 +   }
 +
 +
 +//Private methods
 +//===============
 +
 +
 +//Constants
 +
 +   public static final int SOCKS_SUCCESS		=0;
 +   public static final int SOCKS_FAILURE		=1;
 +   public static final int SOCKS_BADCONNECT		=2;
 +   public static final int SOCKS_BADNETWORK		=3;
 +   public static final int SOCKS_HOST_UNREACHABLE	=4;
 +   public static final int SOCKS_CONNECTION_REFUSED	=5;
 +   public static final int SOCKS_TTL_EXPIRE		=6;
 +   public static final int SOCKS_CMD_NOT_SUPPORTED	=7;
 +   public static final int SOCKS_ADDR_NOT_SUPPORTED	=8;
 +
 +   public static final int SOCKS_NO_PROXY		=1<<16;
 +   public static final int SOCKS_PROXY_NO_CONNECT	=2<<16;
 +   public static final int SOCKS_PROXY_IO_ERROR		=3<<16;
 +   public static final int SOCKS_AUTH_NOT_SUPPORTED	=4<<16;
 +   public static final int SOCKS_AUTH_FAILURE		=5<<16;
 +   public static final int SOCKS_JUST_ERROR		=6<<16;
 +
 +   public static final int SOCKS_DIRECT_FAILED		=7<<16;
 +   public static final int SOCKS_METHOD_NOTSUPPORTED	=8<<16;
 +
 +
 +   static final int SOCKS_CMD_CONNECT 		=0x1;
 +   static final int SOCKS_CMD_BIND		=0x2;
 +   static final int SOCKS_CMD_UDP_ASSOCIATE	=0x3;
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/ProxyMessage.java b/src/net/sourceforge/jsocks/ProxyMessage.java new file mode 100644 index 0000000..6ea8b4b --- /dev/null +++ b/src/net/sourceforge/jsocks/ProxyMessage.java @@ -0,0 +1,109 @@ +package net.sourceforge.jsocks;
 +
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +import java.net.InetAddress;
 +import java.net.UnknownHostException;
 +
 +/**
 + Abstract class which describes SOCKS4/5 response/request.
 +*/
 +public abstract class ProxyMessage{
 +   /** Host as an IP address */
 +   public InetAddress ip=null;
 +   /** SOCKS version, or version of the response for SOCKS4*/
 +   public int version;
 +   /** Port field of the request/response*/
 +   public int port;
 +   /** Request/response code as an int*/
 +   public int command;
 +   /** Host as string.*/
 +   public String host=null;
 +   /** User field for SOCKS4 request messages*/
 +   public String user=null;
 +
 +   ProxyMessage(int command,InetAddress ip,int port){
 +      this.command = command;
 +      this.ip      = ip;
 +      this.port    = port;
 +   }
 +
 +   ProxyMessage(){
 +   }
 +
 +
 +   /**
 +     Initialises Message from the stream. Reads server response from
 +     given stream.
 +     @param in Input stream to read response from.
 +     @throws SocksException If server response code is not SOCKS_SUCCESS(0), or
 +     if any error with protocol occurs.
 +     @throws IOException If any error happens with I/O.
 +   */
 +   public abstract void read(InputStream in)
 +                                    throws SocksException,
 +                                           IOException;
 +
 +
 +   /**
 +     Initialises Message from the stream. Reads server response or client 
 +     request from given stream.
 +     
 +     @param in Input stream to read response from.
 +     @param clinetMode If true read server response, else read client request.
 +     @throws SocksException If server response code is not SOCKS_SUCCESS(0) and
 +     reading in client mode, or if any error with protocol occurs.
 +     @throws IOException If any error happens with I/O.
 +   */
 +   public abstract void read(InputStream in,boolean client_mode)
 +                                    throws SocksException,
 +                                           IOException;
 +
 +
 +   /**
 +    Writes the message to the stream.
 +    @param out Output stream to which message should be written.
 +   */
 +   public abstract void write(OutputStream out)throws SocksException,
 +                                             IOException;
 +
 +   /**
 +    Get the Address field of this message as InetAddress object.
 +    @return Host address or null, if one can't be determined.
 +   */
 +   public InetAddress getInetAddress() throws UnknownHostException{
 +     return ip;
 +   }
 +
 +
 +   /**
 +    Get string representaion of this message.
 +    @return string representation of this message.
 +   */
 +   public String toString(){
 +      return 
 +      "Proxy Message:\n"+
 +      "Version:"+ version+"\n"+
 +      "Command:"+ command+"\n"+
 +      "IP:     "+ ip+"\n"+
 +      "Port:   "+ port+"\n"+
 +      "User:   "+ user+"\n" ;
 +   }
 +
 +//Package methods
 +//////////////////
 +
 +   static final String bytes2IPV4(byte[] addr,int offset){
 +      String hostName = ""+(addr[offset] & 0xFF);
 +      for(int i = offset+1;i<offset+4;++i)
 +        hostName+="."+(addr[i] & 0xFF);
 +      return hostName;
 +   }
 +
 +   static final String bytes2IPV6(byte[] addr,int offset){
 +     //Have no idea how they look like!
 +     return null;
 +   }
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/ProxyServer.java b/src/net/sourceforge/jsocks/ProxyServer.java new file mode 100644 index 0000000..39f6383 --- /dev/null +++ b/src/net/sourceforge/jsocks/ProxyServer.java @@ -0,0 +1,591 @@ +package net.sourceforge.jsocks;
 +import java.io.EOFException;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.InterruptedIOException;
 +import java.io.OutputStream;
 +import java.io.PrintStream;
 +import java.io.PushbackInputStream;
 +import java.net.ConnectException;
 +import java.net.InetAddress;
 +import java.net.NoRouteToHostException;
 +import java.net.ServerSocket;
 +import java.net.Socket;
 +
 +import net.sourceforge.jsocks.server.ServerAuthenticator;
 +
 +/**
 +    SOCKS4 and SOCKS5 proxy, handles both protocols simultaniously.
 +    Implements all SOCKS commands, including UDP relaying.
 +    <p>
 +    In order to use it you will need to implement ServerAuthenticator
 +    interface. There is an implementation of this interface which does
 +    no authentication ServerAuthenticatorNone, but it is very dangerous
 +    to use, as it will give access to your local network to anybody
 +    in the world. One should never use this authentication scheme unless
 +    one have pretty good reason to do so.
 +    There is a couple of other authentication schemes in socks.server package.
 +    @see socks.server.ServerAuthenticator
 +*/
 +public class ProxyServer implements Runnable{
 +
 +   ServerAuthenticator auth;
 +   ProxyMessage msg = null;
 +
 +   Socket sock=null,remote_sock=null;
 +   ServerSocket ss=null;
 +   UDPRelayServer relayServer = null;
 +   InputStream in,remote_in;
 +   OutputStream out,remote_out;
 +
 +   int mode;
 +   static final int START_MODE	= 0;
 +   static final int ACCEPT_MODE	= 1;
 +   static final int PIPE_MODE	= 2;
 +   static final int ABORT_MODE	= 3;
 +
 +   static final int BUF_SIZE	= 8192;
 +
 +   Thread pipe_thread1,pipe_thread2;
 +   long lastReadTime;
 +
 +   protected static int iddleTimeout	= 180000; //3 minutes
 +   static int acceptTimeout	= 180000; //3 minutes
 +
 +   static PrintStream log = null;
 +   static Proxy proxy;
 +
 +
 +//Public Constructors
 +/////////////////////
 +
 +
 +   /**
 +    Creates a proxy server with given Authentication scheme.
 +    @param auth Authentication scheme to be used.
 +    */
 +   public ProxyServer(ServerAuthenticator auth){
 +     this.auth = auth;
 +   }
 +
 +//Other constructors
 +////////////////////
 +
 +   protected ProxyServer(ServerAuthenticator auth,Socket s){
 +      this.auth  = auth;
 +      this.sock  = s;
 +      mode = START_MODE;
 +   }
 +
 +//Public methods
 +/////////////////
 +
 +   /**
 +     Set the logging stream. Specifying null disables logging.
 +   */
 +   public static void setLog(OutputStream out){
 +      if(out == null){
 +        log = null;
 +      }else{
 +        log = new PrintStream(out,true);
 +      }
 +
 +      UDPRelayServer.log = log;
 +   }
 +   
 +   /**
 +    Set proxy.
 +    <p>
 +    Allows Proxy chaining so that one Proxy server is connected to another
 +    and so on. If proxy supports SOCKSv4, then only some SOCKSv5 requests
 +    can be handled, UDP would not work, however CONNECT and BIND will be
 +    translated.
 +
 +    @param p Proxy which should be used to handle user requests.
 +   */
 +   public static void setProxy(Proxy p){
 +      proxy =p;
 +      UDPRelayServer.proxy = proxy;
 +   }
 +
 +   /**
 +    Get proxy.
 +    @return Proxy wich is used to handle user requests.
 +   */
 +   public static Proxy getProxy(){
 +      return proxy;
 +   }
 +
 +   /**
 +    Sets the timeout for connections, how long shoud server wait
 +    for data to arrive before dropping the connection.<br>
 +    Zero timeout implies infinity.<br>
 +    Default timeout is 3 minutes.
 +   */
 +   public static void setIddleTimeout(int timeout){
 +      iddleTimeout = timeout;
 +   }
 +   /**
 +    Sets the timeout for BIND command, how long the server should
 +    wait for the incoming connection.<br>
 +    Zero timeout implies infinity.<br>
 +    Default timeout is 3 minutes.
 +   */
 +   public static void setAcceptTimeout(int timeout){
 +      acceptTimeout = timeout;
 +   }
 +
 +   /**
 +    Sets the timeout for UDPRelay server.<br>
 +    Zero timeout implies infinity.<br>
 +    Default timeout is 3 minutes.
 +   */
 +   public static void setUDPTimeout(int timeout){
 +      UDPRelayServer.setTimeout(timeout);
 +   }
 +
 +   /**
 +     Sets the size of the datagrams used in the UDPRelayServer.<br>
 +     Default size is 64K, a bit more than maximum possible size of the
 +     datagram.
 +    */
 +   public static void setDatagramSize(int size){
 +      UDPRelayServer.setDatagramSize(size);
 +   }
 +
 +
 +   /**
 +     Start the Proxy server at given port.<br>
 +     This methods blocks.
 +    */
 +   public void start(int port){
 +      start(port,5,null);
 +   }
 +
 +   /**
 +     Create a server with the specified port, listen backlog, and local 
 +     IP address to bind to. The localIP argument can be used on a multi-homed
 +     host for a ServerSocket that will only accept connect requests to one of 
 +     its addresses. If localIP is null, it will default accepting connections 
 +     on any/all local addresses. The port must be between 0 and 65535, 
 +     inclusive. <br>
 +     This methods blocks. 
 +    */
 +   public void start(int port,int backlog,InetAddress localIP){
 +      try{
 +        ss = new ServerSocket(port,backlog,localIP);
 +        log("Starting SOCKS Proxy on:"+ss.getInetAddress().getHostAddress()+":"
 +                                      +ss.getLocalPort());
 +        while(true){
 +          Socket s = ss.accept();
 +          log("Accepted from:"+s.getInetAddress().getHostName()+":"
 +                              +s.getPort());
 +          ProxyServer ps = new ProxyServer(auth,s);
 +          (new Thread(ps)).start();
 +        }
 +      }catch(IOException ioe){
 +        ioe.printStackTrace();
 +      }finally{
 +      }
 +   }
 +
 +   /**
 +     Stop server operation.It would be wise to interrupt thread running the
 +     server afterwards.
 +    */
 +   public void stop(){
 +     try{
 +       if(ss != null) ss.close();
 +     }catch(IOException ioe){
 +     }
 +   }
 +
 +//Runnable interface
 +////////////////////
 +   public void run(){
 +      switch(mode){
 +        case START_MODE:
 +         try{
 +           startSession();
 +         }catch(IOException ioe){
 +           handleException(ioe);
 +           //ioe.printStackTrace();
 +         }finally{
 +           abort();
 +           if(auth!=null) auth.endSession();
 +           log("Main thread(client->remote)stopped.");
 +         }
 +        break;
 +        case ACCEPT_MODE:
 +          try{
 +            doAccept();
 +            mode = PIPE_MODE;
 +            pipe_thread1.interrupt(); //Tell other thread that connection have
 +                                      //been accepted.
 +            pipe(remote_in,out);
 +          }catch(IOException ioe){
 +            //log("Accept exception:"+ioe);
 +            handleException(ioe);
 +          }finally{
 +            abort();
 +            log("Accept thread(remote->client) stopped");
 +          }
 +        break;
 +        case PIPE_MODE:
 +         try{
 +            pipe(remote_in,out);
 +         }catch(IOException ioe){
 +         }finally{
 +            abort();
 +            log("Support thread(remote->client) stopped");
 +         }
 +        break;
 +        case ABORT_MODE:
 +        break;
 +        default:
 +         log("Unexpected MODE "+mode);
 +      }
 +   }
 +
 +//Private methods
 +/////////////////
 +   private void startSession() throws IOException{
 +     sock.setSoTimeout(iddleTimeout);
 +
 +     try{
 +        auth = auth.startSession(sock);
 +     }catch(IOException ioe){
 +       log("Auth throwed exception:"+ioe);
 +       auth = null;
 +       return;
 +     }
 +
 +     if(auth == null){ //Authentication failed
 +        log("Authentication failed");
 +        return;
 +     }
 +
 +     in = auth.getInputStream();
 +     out = auth.getOutputStream();
 +
 +     msg = readMsg(in);
 +     handleRequest(msg);
 +   }
 +   
 +   protected void handleRequest(ProxyMessage msg)
 +                throws IOException{
 +      if(!auth.checkRequest(msg)) throw new 
 +                                  SocksException(Proxy.SOCKS_FAILURE);
 +
 +      if(msg.ip == null){
 +        if(msg instanceof Socks5Message){
 +          msg.ip = InetAddress.getByName(msg.host);
 +        }else
 +          throw new SocksException(Proxy.SOCKS_FAILURE);
 +      }
 +      log(msg);
 +
 +      switch(msg.command){
 +        case Proxy.SOCKS_CMD_CONNECT:
 +          onConnect(msg);
 +        break;
 +        case Proxy.SOCKS_CMD_BIND:
 +          onBind(msg);
 +        break;
 +        case Proxy.SOCKS_CMD_UDP_ASSOCIATE:
 +          onUDP(msg);
 +        break;
 +        default:
 +          throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED);
 +      }
 +   }
 +
 +   private void handleException(IOException ioe){
 +      //If we couldn't read the request, return;
 +      if(msg == null) return;
 +      //If have been aborted by other thread
 +      if(mode == ABORT_MODE) return;
 +      //If the request was successfully completed, but exception happened later
 +      if(mode == PIPE_MODE) return;
 +
 +      int error_code = Proxy.SOCKS_FAILURE;
 +
 +      if(ioe instanceof SocksException)
 +          error_code = ((SocksException)ioe).errCode;
 +      else if(ioe instanceof NoRouteToHostException)
 +          error_code = Proxy.SOCKS_HOST_UNREACHABLE;
 +      else if(ioe instanceof ConnectException)
 +          error_code = Proxy.SOCKS_CONNECTION_REFUSED;
 +      else if(ioe instanceof InterruptedIOException)
 +          error_code = Proxy.SOCKS_TTL_EXPIRE;
 +
 +      if(error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED || error_code < 0){
 +          error_code = Proxy.SOCKS_FAILURE; 
 +      }
 +
 +      sendErrorMessage(error_code);
 +   }
 +
 +   private void onConnect(ProxyMessage msg) throws IOException{
 +      Socket s;
 +      ProxyMessage response = null;
 +
 +      s = new Socket(msg.ip,msg.port);
 +
 +      log("Connected to "+s.getInetAddress()+":"+s.getPort());
 +
 +      if(msg instanceof Socks5Message){
 +        response = new Socks5Message(Proxy.SOCKS_SUCCESS,
 +                                         s.getLocalAddress(),
 +                                         s.getLocalPort());
 +      }else{
 +        response = new Socks4Message(Socks4Message.REPLY_OK,
 +                                     s.getLocalAddress(),s.getLocalPort());
 +
 +      }
 +      response.write(out);
 +      startPipe(s);
 +   }
 +
 +   private void onBind(ProxyMessage msg) throws IOException{
 +      ProxyMessage response = null;
 +
 +      if(proxy == null)
 +        ss = new ServerSocket(0);
 +      else
 +        ss = new SocksServerSocket(proxy, msg.ip, msg.port);
 +
 +      ss.setSoTimeout(acceptTimeout);
 +
 +      log("Trying accept on "+ss.getInetAddress()+":"+ss.getLocalPort());
 +
 +      if(msg.version == 5)
 +         response = new Socks5Message(Proxy.SOCKS_SUCCESS,ss.getInetAddress(),
 +                                                          ss.getLocalPort());
 +      else
 +         response = new Socks4Message(Socks4Message.REPLY_OK,
 +                                      ss.getInetAddress(),
 +                                      ss.getLocalPort());
 +      response.write(out);
 +
 +      mode = ACCEPT_MODE;
 +
 +      pipe_thread1 = Thread.currentThread();
 +      pipe_thread2 = new Thread(this);
 +      pipe_thread2.start();
 +
 +      //Make timeout infinit.
 +      sock.setSoTimeout(0);
 +      int eof=0;
 +
 +      try{
 +        while((eof=in.read())>=0){
 +          if(mode != ACCEPT_MODE){
 +            if(mode != PIPE_MODE) return;//Accept failed
 +
 +            remote_out.write(eof);
 +            break;
 +          }
 +        }
 +      }catch(EOFException eofe){
 +        //System.out.println("EOF exception");
 +        return;//Connection closed while we were trying to accept.
 +      }catch(InterruptedIOException iioe){ 
 +        //Accept thread interrupted us.
 +        //System.out.println("Interrupted");
 +        if(mode != PIPE_MODE)
 +          return;//If accept thread was not successfull return.
 +      }finally{
 +        //System.out.println("Finnaly!");
 +      }
 +
 +      if(eof < 0)//Connection closed while we were trying to accept;
 +        return;
 +      
 +      //Do not restore timeout, instead timeout is set on the
 +      //remote socket. It does not make any difference.
 +
 +      pipe(in,remote_out);
 +   }
 +
 +   private void onUDP(ProxyMessage msg) throws IOException{
 +      if(msg.ip.getHostAddress().equals("0.0.0.0"))
 +         msg.ip = sock.getInetAddress();
 +      log("Creating UDP relay server for "+msg.ip+":"+msg.port);
 +      relayServer = new UDPRelayServer(msg.ip,msg.port,
 +                        Thread.currentThread(),sock,auth);
 +
 +      ProxyMessage response;
 +
 +      response = new Socks5Message(Proxy.SOCKS_SUCCESS,
 +                                   relayServer.relayIP,relayServer.relayPort);
 +
 +      response.write(out);
 +
 +      relayServer.start();
 +
 +      //Make timeout infinit.
 +      sock.setSoTimeout(0);
 +      try{
 +        while(in.read()>=0) /*do nothing*/;
 +      }catch(EOFException eofe){
 +      }
 +   }
 +
 +//Private methods
 +//////////////////
 +
 +   private void doAccept() throws IOException{
 +      Socket s;
 +      long startTime = System.currentTimeMillis();
 +
 +      while(true){
 +         s = ss.accept();
 +         if(s.getInetAddress().equals(msg.ip)){
 +            //got the connection from the right host
 +            //Close listenning socket.
 +            ss.close();
 +            break;
 +         }else if(ss instanceof SocksServerSocket){
 +            //We can't accept more then one connection
 +            s.close();
 +            ss.close();
 +            throw new SocksException(Proxy.SOCKS_FAILURE);
 +         }else{
 +            if(acceptTimeout!=0){ //If timeout is not infinit
 +               int newTimeout = acceptTimeout-(int)(System.currentTimeMillis()-
 +                                                           startTime);
 +               if(newTimeout <= 0) throw new InterruptedIOException(
 +                                "In doAccept()");
 +               ss.setSoTimeout(newTimeout);
 +            }
 +            s.close(); //Drop all connections from other hosts
 +         }
 +      }
 +
 +      //Accepted connection
 +      remote_sock = s;
 +      remote_in = s.getInputStream();
 +      remote_out = s.getOutputStream();
 +
 +      //Set timeout
 +      remote_sock.setSoTimeout(iddleTimeout);
 +
 +      log("Accepted from "+s.getInetAddress()+":"+s.getPort());
 +
 +      ProxyMessage response;
 +
 +      if(msg.version == 5)
 +         response = new Socks5Message(Proxy.SOCKS_SUCCESS, s.getInetAddress(),
 +                                                           s.getPort());
 +      else
 +         response = new Socks4Message(Socks4Message.REPLY_OK,
 +                                      s.getInetAddress(), s.getPort());
 +      response.write(out);
 +   }
 +
 +   protected ProxyMessage readMsg(InputStream in) throws IOException{
 +      PushbackInputStream push_in;  
 +      if(in instanceof PushbackInputStream)
 +         push_in = (PushbackInputStream) in;
 +      else
 +         push_in = new PushbackInputStream(in);
 +
 +      int version = push_in.read();
 +      push_in.unread(version);
 +
 +
 +      ProxyMessage msg;
 +
 +      if(version == 5){
 +        msg = new Socks5Message(push_in,false);
 +      }else if(version == 4){
 +        msg = new Socks4Message(push_in,false);
 +      }else{
 +        throw new SocksException(Proxy.SOCKS_FAILURE);
 +      }
 +      return msg;
 +   }
 +
 +   private void startPipe(Socket s){
 +      mode = PIPE_MODE;
 +      remote_sock = s;
 +      try{
 +         remote_in = s.getInputStream();
 +         remote_out = s.getOutputStream();
 +         pipe_thread1 = Thread.currentThread();
 +         pipe_thread2 = new Thread(this);
 +         pipe_thread2.start();
 +         pipe(in,remote_out);
 +      }catch(IOException ioe){
 +      }
 +   }
 +
 +   private void sendErrorMessage(int error_code){
 +      ProxyMessage err_msg;
 +      if(msg instanceof Socks4Message)
 +         err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED);
 +      else
 +         err_msg = new Socks5Message(error_code);
 +      try{
 +         err_msg.write(out);
 +      }catch(IOException ioe){}
 +   }
 +
 +   private synchronized void abort(){
 +      if(mode == ABORT_MODE) return;
 +      mode = ABORT_MODE;
 +      try{
 +         log("Aborting operation");
 +         if(remote_sock != null) remote_sock.close();
 +         if(sock != null) sock.close();
 +         if(relayServer!=null) relayServer.stop();
 +         if(ss!=null) ss.close();
 +         if(pipe_thread1 != null) pipe_thread1.interrupt();
 +         if(pipe_thread2 != null) pipe_thread2.interrupt();
 +      }catch(IOException ioe){}
 +   }
 +
 +   static final void log(String s){
 +     if(log != null){
 +       log.println(s);
 +       log.flush();
 +     }
 +   }
 +
 +   static final void log(ProxyMessage msg){
 +      log("Request version:"+msg.version+
 +          "\tCommand: "+command2String(msg.command));
 +      log("IP:"+msg.ip +"\tPort:"+msg.port+
 +         (msg.version==4?"\tUser:"+msg.user:""));
 +   }
 +
 +   private void pipe(InputStream in,OutputStream out) throws IOException{
 +      lastReadTime = System.currentTimeMillis();
 +      byte[] buf = new byte[BUF_SIZE];
 +      int len = 0;
 +      while(len >= 0){
 +         try{
 +           if(len!=0){
 +             out.write(buf,0,len);
 +             out.flush();
 +           }
 +           len= in.read(buf);
 +           lastReadTime = System.currentTimeMillis();
 +         }catch(InterruptedIOException iioe){
 +           if(iddleTimeout == 0) return;//Other thread interrupted us.
 +           long timeSinceRead = System.currentTimeMillis() - lastReadTime;
 +           if(timeSinceRead >= iddleTimeout - 1000) //-1s for adjustment.
 +              return;
 +           len = 0;
 +
 +         }
 +      }
 +   }
 +   static final String command_names[] = {"CONNECT","BIND","UDP_ASSOCIATE"};
 +
 +   static final String command2String(int cmd){
 +      if(cmd > 0 && cmd < 4) return command_names[cmd-1];
 +      else return "Unknown Command "+cmd;
 +   }
 +}
 diff --git a/src/net/sourceforge/jsocks/Socks4Message.java b/src/net/sourceforge/jsocks/Socks4Message.java new file mode 100644 index 0000000..40d0ac9 --- /dev/null +++ b/src/net/sourceforge/jsocks/Socks4Message.java @@ -0,0 +1,157 @@ +package net.sourceforge.jsocks;
 +import java.io.*;
 +import java.net.*;
 +
 +/** 
 +  SOCKS4 Reply/Request message.
 +*/
 +
 +public class Socks4Message extends ProxyMessage{
 +
 +   private byte[] msgBytes;
 +   private int msgLength;
 +
 +   /**
 +    * Server failed reply, cmd command for failed request
 +    */
 +   public Socks4Message(int cmd){
 +      super(cmd,null,0);
 +      this.user    = null;
 +
 +      msgLength = 2;
 +      msgBytes = new byte[2];
 +
 +      msgBytes[0] = (byte) 0;
 +      msgBytes[1] = (byte) command;
 +   }
 +
 +   /** 
 +    *  Server successfull reply
 +    */
 +   public Socks4Message(int cmd,InetAddress ip,int port){
 +      this(0,cmd,ip,port,null);
 +   }
 +
 +   /** 
 +    *  Client request
 +    */
 +   public Socks4Message(int cmd,InetAddress ip,int port,String user){
 +      this(SOCKS_VERSION,cmd,ip,port,user);
 +   }
 +
 +   /**
 +    * Most general constructor
 +    */
 +   public Socks4Message(int version, int cmd,
 +                        InetAddress ip,int port,String user){
 +      super(cmd,ip,port);
 +      this.user    = user;
 +      this.version = version;
 +
 +      msgLength = user == null?8:9+user.length();
 +      msgBytes = new byte[msgLength];
 +
 +      msgBytes[0] = (byte) version;
 +      msgBytes[1] = (byte) command;
 +      msgBytes[2] = (byte) (port >> 8);
 +      msgBytes[3] = (byte) port;
 +
 +      byte[] addr;
 +
 +      if(ip != null)
 +        addr = ip.getAddress();
 +      else{
 +        addr = new byte[4];
 +        addr[0]=addr[1]=addr[2]=addr[3]=0;
 +      }
 +      System.arraycopy(addr,0,msgBytes,4,4);
 +
 +      if(user != null){
 +         byte[] buf = user.getBytes();
 +         System.arraycopy(buf,0,msgBytes,8,buf.length);
 +         msgBytes[msgBytes.length -1 ] = 0;
 +      }
 +   }
 +
 +   /**
 +    *Initialise from the stream
 +    *If clientMode is true attempts to read a server response
 +    *otherwise reads a client request
 +    *see read for more detail
 +    */
 +   public Socks4Message(InputStream in, boolean clientMode) throws IOException{
 +      msgBytes = null;
 +      read(in,clientMode);
 +   }
 +
 +   public void read(InputStream in) throws IOException{
 +        read(in,true);
 +   }
 +
 +   public void read(InputStream in, boolean clientMode) throws IOException{
 +       DataInputStream d_in = new DataInputStream(in);
 +       version= d_in.readUnsignedByte();
 +       command = d_in.readUnsignedByte();
 +       if(clientMode && command != REPLY_OK){
 +          String errMsg;
 +          if(command >REPLY_OK && command < REPLY_BAD_IDENTD)
 +             errMsg = replyMessage[command-REPLY_OK];
 +          else
 +             errMsg = "Unknown Reply Code";
 +          throw new SocksException(command,errMsg);
 +       }
 +       port = d_in.readUnsignedShort();
 +       byte[] addr = new byte[4];
 +       d_in.readFully(addr);
 +       ip=bytes2IP(addr);
 +       host = ip.getHostName();
 +       if(!clientMode){
 +          int b = in.read();
 +          //Hope there are no idiots with user name bigger than this
 +          byte[] userBytes = new byte[256];
 +          int i = 0;
 +          for(i =0;i<userBytes.length && b>0;++i){
 +             userBytes[i] = (byte) b;
 +             b = in.read();
 +          }
 +          user = new String(userBytes,0,i);
 +       }
 +   }
 +   public void write(OutputStream out) throws IOException{
 +      if(msgBytes == null){
 +         Socks4Message msg = new Socks4Message(version,command,ip,port,user);
 +         msgBytes = msg.msgBytes;
 +         msgLength = msg.msgLength;
 +      }
 +      out.write(msgBytes);
 +   }
 +
 +   //Class methods
 +   static InetAddress bytes2IP(byte[] addr){
 +      String s = bytes2IPV4(addr,0);
 +      try{
 +         return InetAddress.getByName(s);
 +      }catch(UnknownHostException uh_ex){
 +        return null;
 +      }
 +   }
 +
 +   //Constants
 +
 +   static final String[] replyMessage ={
 +          "Request Granted",
 +          "Request Rejected or Failed",
 +          "Failed request, can't connect to Identd",
 +          "Failed request, bad user name"};
 +
 +   static final int SOCKS_VERSION = 4;
 +
 +   public final static int REQUEST_CONNECT		= 1;
 +   public final static int REQUEST_BIND   		= 2;
 +
 +   public final static int REPLY_OK 			= 90;
 +   public final static int REPLY_REJECTED		= 91;
 +   public final static int REPLY_NO_CONNECT		= 92;
 +   public final static int REPLY_BAD_IDENTD		= 93;
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/Socks4Proxy.java b/src/net/sourceforge/jsocks/Socks4Proxy.java new file mode 100644 index 0000000..f920b75 --- /dev/null +++ b/src/net/sourceforge/jsocks/Socks4Proxy.java @@ -0,0 +1,107 @@ +package net.sourceforge.jsocks;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.net.InetAddress;
 +import java.net.UnknownHostException;
 +
 +/**
 +  Proxy which describes SOCKS4 proxy.
 +*/
 +
 +public class Socks4Proxy extends Proxy implements Cloneable{
 +
 +//Data members
 +   String user;
 +
 +//Public Constructors
 +//====================
 +
 +   /**
 +     Creates the SOCKS4 proxy
 +     @param p Proxy to use to connect to this proxy, allows proxy chaining.
 +     @param proxyHost Address of the proxy server.
 +     @param proxyPort Port of the proxy server
 +     @param user User name to use for identification purposes.
 +     @throws UnknownHostException If proxyHost can't be resolved.
 +    */
 +   public Socks4Proxy(String proxyHost,int proxyPort,String user)
 +          throws UnknownHostException{ 
 +      super(proxyHost,proxyPort);
 +      this.user = new String(user);
 +      version = 4;
 +   }
 +
 +   /**
 +     Creates the SOCKS4 proxy
 +     @param p Proxy to use to connect to this proxy, allows proxy chaining.
 +     @param proxyIP Address of the proxy server.
 +     @param proxyPort Port of the proxy server
 +     @param user User name to use for identification purposes.
 +    */
 +   public Socks4Proxy(Proxy p,InetAddress proxyIP,int proxyPort,String user){
 +      super(p,proxyIP,proxyPort);
 +      this.user = new String(user);
 +      version = 4;
 +   }
 +
 +   /**
 +     Creates the SOCKS4 proxy
 +     @param proxyIP Address of the proxy server.
 +     @param proxyPort Port of the proxy server
 +     @param user User name to use for identification purposes.
 +    */
 +   public Socks4Proxy(InetAddress proxyIP,int proxyPort,String user){
 +      this(null,proxyIP,proxyPort,user);
 +   }
 +
 +//Public instance methods
 +//========================
 +
 +   /**
 +    * Creates a clone of this proxy. Changes made to the clone should not
 +    * affect this object. 
 +    */
 +   public Object clone(){
 +      Socks4Proxy newProxy = new Socks4Proxy(proxyIP,proxyPort,user);
 +      newProxy.chainProxy = chainProxy;
 +      return newProxy;
 +   }
 +
 +
 +//Public Static(Class) Methods
 +//==============================
 +
 +
 +//Protected Methods
 +//=================
 +
 +   protected Proxy copy(){
 +       Socks4Proxy copy = new Socks4Proxy(proxyIP,proxyPort,user);
 +       copy.chainProxy = chainProxy;
 +       return copy;
 +    }
 +
 +   protected ProxyMessage formMessage(int cmd,InetAddress ip,int port){
 +       switch(cmd){
 +         case SOCKS_CMD_CONNECT:
 +           cmd = Socks4Message.REQUEST_CONNECT;
 +         break;
 +         case SOCKS_CMD_BIND:
 +           cmd = Socks4Message.REQUEST_BIND;
 +         break;
 +         default:
 +           return null;
 +       }
 +       return new Socks4Message(cmd,ip,port,user);
 +   }
 +   protected ProxyMessage formMessage(int cmd,String host,int port)
 +             throws UnknownHostException{
 +       return formMessage(cmd,InetAddress.getByName(host),port);
 +   }
 +   protected ProxyMessage formMessage(InputStream in)
 +             throws SocksException,
 +                    IOException{
 +       return new Socks4Message(in,true);
 +   }
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/Socks5DatagramSocket.java b/src/net/sourceforge/jsocks/Socks5DatagramSocket.java new file mode 100644 index 0000000..c1bda57 --- /dev/null +++ b/src/net/sourceforge/jsocks/Socks5DatagramSocket.java @@ -0,0 +1,467 @@ +package net.sourceforge.jsocks;
 +import java.net.*;
 +import java.io.*;
 +
 +/**
 +  Datagram socket to interract through the firewall.<BR>
 +  Can be used same way as the normal DatagramSocket. One should
 +  be carefull though with the datagram sizes used, as additional data
 +  is present in both incomming and outgoing datagrams.
 +   <p>
 +   SOCKS5 protocol allows to send host address as either:
 +   <ul>
 +    <li> IPV4, normal 4 byte address. (10 bytes header size)
 +    <li> IPV6, version 6 ip address (not supported by Java as for now).
 +         22 bytes header size.
 +    <li> Host name,(7+length of the host name bytes header size).
 +   </ul>
 +  As with other Socks equivalents, direct addresses are handled
 +  transparently, that is data will be send directly when required 
 +  by the proxy settings.
 +  <p>
 +  <b>NOTE:</b><br>
 +  Unlike other SOCKS Sockets, it <b>does not</b> support proxy chaining,
 +  and will throw an exception if proxy has a chain proxy attached. The
 +  reason for that is not my laziness, but rather the restrictions of
 +  the SOCKSv5 protocol. Basicaly SOCKSv5 proxy server, needs to know from
 +  which host:port datagrams will be send for association, and returns address
 +  to which datagrams should be send by the client, but it does not
 +  inform client from which host:port it is going to send datagrams, in fact
 +  there is even no guarantee they will be send at all and from the same address
 +  each time.
 + 
 + */
 +public class Socks5DatagramSocket extends DatagramSocket{
 +
 +   InetAddress relayIP;
 +   int relayPort;
 +   Socks5Proxy proxy;
 +   private boolean server_mode = false;
 +   UDPEncapsulation encapsulation;
 +
 +
 +   /**
 +      Construct Datagram socket for communication over SOCKS5 proxy
 +      server. This constructor uses default proxy, the one set with
 +      Proxy.setDefaultProxy() method. If default proxy is not set or 
 +      it is set to version4 proxy, which does not support datagram
 +      forwarding, throws SocksException.
 +
 +    */
 +   public Socks5DatagramSocket() throws SocksException,
 +                                        IOException{
 +      this(Proxy.defaultProxy,0,null);
 +   }
 +   /**
 +      Construct Datagram socket for communication over SOCKS5 proxy
 +      server. And binds it to the specified local port.
 +      This constructor uses default proxy, the one set with
 +      Proxy.setDefaultProxy() method. If default proxy is not set or 
 +      it is set to version4 proxy, which does not support datagram
 +      forwarding, throws SocksException.
 +    */
 +   public Socks5DatagramSocket(int port) throws SocksException,
 +                                                IOException{
 +      this(Proxy.defaultProxy,port,null);
 +   }
 +   /**
 +      Construct Datagram socket for communication over SOCKS5 proxy
 +      server. And binds it to the specified local port and address.
 +      This constructor uses default proxy, the one set with
 +      Proxy.setDefaultProxy() method. If default proxy is not set or 
 +      it is set to version4 proxy, which does not support datagram
 +      forwarding, throws SocksException.
 +    */
 +   public Socks5DatagramSocket(int port,InetAddress ip) throws SocksException,
 +                                        IOException{
 +      this(Proxy.defaultProxy,port,ip);
 +   }
 +
 +   /**
 +     Constructs datagram socket for communication over specified proxy.
 +     And binds it to the given local address and port. Address of null 
 +     and port of 0, signify any availabale port/address.
 +     Might throw SocksException, if:
 +     <ol>
 +      <li> Given version of proxy does not support UDP_ASSOCIATE.
 +      <li> Proxy can't be reached.
 +      <li> Authorization fails.
 +      <li> Proxy does not want to perform udp forwarding, for any reason.
 +     </ol>
 +     Might throw IOException if binding dtagram socket to given address/port
 +     fails.
 +     See java.net.DatagramSocket for more details.
 +    */
 +   public Socks5DatagramSocket(Proxy p,int port,InetAddress ip)
 +                                        throws SocksException,
 +                                               IOException{
 +      super(port,ip);
 +      if(p == null) throw new SocksException(Proxy.SOCKS_NO_PROXY);
 +      if(!(p instanceof Socks5Proxy)) 
 +         throw new SocksException(-1,"Datagram Socket needs Proxy version 5");
 +
 +      if(p.chainProxy != null)
 +           throw new SocksException(Proxy.SOCKS_JUST_ERROR,
 +               "Datagram Sockets do not support proxy chaining.");
 +
 +      proxy =(Socks5Proxy) p.copy();
 +
 +      ProxyMessage msg = proxy.udpAssociate(super.getLocalAddress(),
 +                                            super.getLocalPort());
 +      relayIP = msg.ip;
 +      if(relayIP.getHostAddress().equals("0.0.0.0")) relayIP = proxy.proxyIP;
 +      relayPort = msg.port;
 +
 +      encapsulation = proxy.udp_encapsulation;
 +
 +      //debug("Datagram Socket:"+getLocalAddress()+":"+getLocalPort()+"\n");
 +      //debug("Socks5Datagram: "+relayIP+":"+relayPort+"\n");
 +   }
 +
 +   /**
 +     Used by UDPRelayServer.
 +    */
 +   Socks5DatagramSocket(boolean server_mode,UDPEncapsulation encapsulation,
 +                        InetAddress relayIP,int relayPort)
 +                        throws IOException{
 +      super();
 +      this.server_mode = server_mode;
 +      this.relayIP = relayIP;
 +      this.relayPort = relayPort;
 +      this.encapsulation = encapsulation;
 +      this.proxy = null;
 +   }
 +
 +   /**
 +     Sends the Datagram either through the proxy or directly depending
 +     on current proxy settings and destination address. <BR>
 +
 +     <B> NOTE: </B> DatagramPacket size should be at least 10 bytes less
 +                    than the systems limit.
 +
 +     <P>
 +     See documentation on java.net.DatagramSocket
 +     for full details on how to use this method. 
 +     @param dp Datagram to send.
 +     @throws IOException If error happens with I/O.
 +    */
 +   public void send(DatagramPacket dp) throws IOException{
 +     //If the host should be accessed directly, send it as is.
 +     if(!server_mode){
 +        super.send(dp);
 +        //debug("Sending directly:");
 +        return;
 +     }
 +
 +     byte[] head = formHeader(dp.getAddress(),dp.getPort());
 +     byte[] buf = new byte[head.length + dp.getLength()];
 +     byte[] data = dp.getData();
 +     //Merge head and data
 +     System.arraycopy(head,0,buf,0,head.length);
 +     //System.arraycopy(data,dp.getOffset(),buf,head.length,dp.getLength());
 +     System.arraycopy(data,0,buf,head.length,dp.getLength());
 +
 +     if(encapsulation != null)
 +        buf = encapsulation.udpEncapsulate(buf,true);
 +
 +     super.send(new DatagramPacket(buf,buf.length,relayIP,relayPort));
 +   }
 +   /**
 +     This method allows to send datagram packets with address type DOMAINNAME.
 +     SOCKS5 allows to specify host as names rather than ip addresses.Using
 +     this method one can send udp datagrams through the proxy, without having
 +     to know the ip address of the destination host.
 +     <p> 
 +     If proxy specified for that socket has an option resolveAddrLocally set
 +     to true host will be resolved, and the datagram will be send with address
 +     type IPV4, if resolve fails, UnknownHostException is thrown.
 +     @param dp Datagram to send, it should contain valid port and data
 +     @param host Host name to which datagram should be send.
 +     @throws IOException If error happens with I/O, or the host can't be 
 +     resolved when proxy settings say that hosts should be resolved locally.
 +     @see Socks5Proxy#resolveAddrLocally(boolean)
 +    */
 +    public void send(DatagramPacket dp, String host) throws IOException {
 +		dp.setAddress(InetAddress.getByName(host));
 +		super.send(dp);
 +	}
 +
 +   /**
 +    * Receives udp packet. If packet have arrived from the proxy relay server,
 +    * it is processed and address and port of the packet are set to the
 +    * address and port of sending host.<BR>
 +    * If the packet arrived from anywhere else it is not changed.<br>
 +    * <B> NOTE: </B> DatagramPacket size should be at least 10 bytes bigger
 +    * than the largest packet you expect (this is for IPV4 addresses). 
 +    * For hostnames and IPV6 it is even more.
 +      @param dp Datagram in which all relevent information will be copied.
 +    */
 +   public void receive(DatagramPacket dp) throws IOException{
 +      super.receive(dp);
 +
 +      if(server_mode){
 +        //Drop all datagrams not from relayIP/relayPort
 +         int init_length = dp.getLength();
 +         int initTimeout = getSoTimeout();
 +         long startTime = System.currentTimeMillis();
 +
 +         while(!relayIP.equals(dp.getAddress()) ||
 +                relayPort != dp.getPort()){
 +
 +           //Restore datagram size
 +           dp.setLength(init_length);
 +
 +           //If there is a non-infinit timeout on this socket
 +           //Make sure that it happens no matter how often unexpected
 +           //packets arrive.
 +           if(initTimeout != 0){
 +             int newTimeout = initTimeout - (int)(System.currentTimeMillis() - 
 +                                                        startTime);
 +             if(newTimeout <= 0) throw new InterruptedIOException(
 +                                 "In Socks5DatagramSocket->receive()");
 +             setSoTimeout(newTimeout);
 +           }
 +
 +           super.receive(dp);
 +         }
 +
 +         //Restore timeout settings
 +         if(initTimeout != 0) setSoTimeout(initTimeout);
 +
 +      }else if(!relayIP.equals(dp.getAddress()) ||
 +                relayPort != dp.getPort()) 
 +          return; // Recieved direct packet
 +      //If the datagram is not from the relay server, return it it as is.
 +
 +      byte[] data;
 +      data = dp.getData();
 +
 +      if(encapsulation != null)
 +         data = encapsulation.udpEncapsulate(data,false);
 +
 +      int offset = 0; //Java 1.1
 +      //int offset = dp.getOffset(); //Java 1.2
 +
 +      ByteArrayInputStream bIn = new ByteArrayInputStream(data,offset,
 +                                                              dp.getLength());
 +
 +
 +      ProxyMessage msg = new Socks5Message(bIn);
 +      dp.setPort(msg.port);
 +      dp.setAddress(msg.getInetAddress());
 +
 +      //what wasn't read by the Message is the data
 +      int data_length = bIn.available();
 +      //Shift data to the left
 +      System.arraycopy(data,offset+dp.getLength()-data_length,
 +                       data,offset,data_length);
 +
 +
 +      dp.setLength(data_length);
 +   }
 +
 +   /**
 +    * Returns port assigned by the proxy, to which datagrams are relayed.
 +    * It is not the same port to which other party should send datagrams.
 +      @return Port assigned by socks server to which datagrams are send
 +      for association.
 +    */
 +   public int getLocalPort(){
 +     if(server_mode) return super.getLocalPort();
 +     return relayPort;
 +   }
 +   /**
 +    * Address assigned by the proxy, to which datagrams are send for relay.
 +    * It is not necesseraly the same address, to which other party should send
 +    * datagrams.
 +      @return Address to which datagrams are send for association.
 +    */
 +   public InetAddress getLocalAddress(){
 +     if(server_mode) return super.getLocalAddress();
 +     return relayIP;
 +   }
 +
 +   /**
 +    * Closes datagram socket, and proxy connection.
 +    */
 +   public void close(){
 +      if(!server_mode) proxy.endSession();
 +      super.close();
 +   }
 +
 +   /**
 +     This method checks wether proxy still runs udp forwarding service
 +     for this socket.
 +     <p>
 +     This methods checks wether the primary connection to proxy server
 +     is active. If it is, chances are that proxy continues to forward
 +     datagrams being send from this socket. If it was closed, most likely
 +     datagrams are no longer being forwarded by the server.
 +     <p>
 +     Proxy might decide to stop forwarding datagrams, in which case it
 +     should close primary connection. This method allows to check, wether
 +     this have been done.
 +     <p>
 +     You can specify timeout for which we should be checking EOF condition
 +     on the primary connection. Timeout is in milliseconds. Specifying 0 as
 +     timeout implies infinity, in which case method will block, until 
 +     connection to proxy is closed or an error happens, and then return false.
 +     <p>
 +     One possible scenario is to call isProxyactive(0) in separate thread,
 +     and once it returned notify other threads about this event.
 +
 +     @param timeout For how long this method should block, before returning.
 +     @return true if connection to proxy is active, false if eof or error
 +             condition have been encountered on the connection.
 +   */
 +   public boolean isProxyAlive(int timeout){
 +     if(server_mode) return false;
 +     if(proxy != null){
 +         try{
 +           proxy.proxySocket.setSoTimeout(timeout);
 +
 +           int eof = proxy.in.read();
 +           if(eof < 0) return false; // EOF encountered.
 +           else return true;         // This really should not happen
 +
 +         }catch(InterruptedIOException iioe){
 +            return true;          // read timed out.
 +         }catch(IOException ioe){
 +            return false;
 +         }
 +     }
 +     return false;
 +   }
 +
 +//PRIVATE METHODS
 +//////////////////
 +
 +
 +   private byte[] formHeader(InetAddress ip, int port){
 +      Socks5Message request = new Socks5Message(0,ip,port);
 +      request.data[0] = 0;
 +      return request.data;
 +   }
 +
 +
 +   private byte[] formHeader(String host,int port){
 +      Socks5Message request = new Socks5Message(0,host,port);
 +      request.data[0] = 0;
 +      return request.data;
 +   }
 +
 +
 +/*======================================================================
 +
 +//Mainly Test functions
 +//////////////////////
 +
 +   private String bytes2String(byte[] b){
 +      String s="";
 +      char[] hex_digit = { '0','1','2','3','4','5','6','7','8','9',
 +                           'A','B','C','D','E','F'}; 
 +      for(int i=0;i<b.length;++i){
 +          int i1 = (b[i] & 0xF0) >> 4;
 +          int i2 = b[i] & 0xF;
 +          s+=hex_digit[i1];
 +          s+=hex_digit[i2];
 +          s+=" ";
 +      }
 +      return s;
 +   }
 +   private static final void debug(String s){
 +      if(DEBUG)
 +         System.out.print(s);
 +   }
 +
 +   private static final boolean DEBUG = true;
 +
 +
 +   public static void usage(){
 +      System.err.print(
 +    "Usage: java Socks.SocksDatagramSocket host port [socksHost socksPort]\n");
 +   }
 +
 +   static final int defaultProxyPort = 1080;           //Default Port
 +   static final String defaultProxyHost = "www-proxy"; //Default proxy
 +
 +   public static void main(String args[]){
 +      int port;
 +      String host;
 +      int proxyPort;
 +      String proxyHost;
 +      InetAddress ip;
 +
 +      if(args.length > 1 && args.length < 5){
 +	 try{
 +
 +	     host = args[0];
 +	     port = Integer.parseInt(args[1]);
 +
 +	     proxyPort =(args.length > 3)? Integer.parseInt(args[3])	     
 +	                                 : defaultProxyPort;
 +
 +	     host = args[0];
 +	     ip = InetAddress.getByName(host);
 +
 +	     proxyHost =(args.length > 2)? args[2]
 +	                                 : defaultProxyHost;
 +
 +	     Proxy.setDefaultProxy(proxyHost,proxyPort);
 +	     Proxy p = Proxy.getDefaultProxy();
 +	     p.addDirect("lux");
 +
 +
 +	     DatagramSocket ds = new Socks5DatagramSocket();
 +	                             
 +
 +	     BufferedReader in = new BufferedReader(
 +				 new InputStreamReader(System.in));
 +             String s;
 +
 +             System.out.print("Enter line:");
 +             s = in.readLine();
 +
 +	     while(s != null){
 +                byte[] data = (s+"\r\n").getBytes();
 +                DatagramPacket dp = new DatagramPacket(data,0,data.length,
 +                                        ip,port);
 +                System.out.println("Sending to: "+ip+":"+port);
 +                ds.send(dp);
 +	        dp = new DatagramPacket(new byte[1024],1024);
 +
 +	        System.out.println("Trying to recieve on port:"+
 +	                            ds.getLocalPort());
 +	        ds.receive(dp);
 +	        System.out.print("Recieved:\n"+
 +	                         "From:"+dp.getAddress()+":"+dp.getPort()+
 +	                         "\n\n"+
 +                new String(dp.getData(),dp.getOffset(),dp.getLength())+"\n"
 +                );
 +                System.out.print("Enter line:");
 +                s = in.readLine();
 +
 +	     }
 +	     ds.close();
 +	     System.exit(1);
 +
 +	 }catch(SocksException s_ex){
 +	   System.err.println("SocksException:"+s_ex);
 +	   s_ex.printStackTrace();
 +	   System.exit(1); 
 +	 }catch(IOException io_ex){
 +	   io_ex.printStackTrace();
 +	   System.exit(1);
 +	 }catch(NumberFormatException num_ex){
 +	   usage();
 +	   num_ex.printStackTrace();
 +	   System.exit(1);
 +	 }
 +
 +      }else{
 +	usage();
 +      }
 +   }
 +*/
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/Socks5Message.java b/src/net/sourceforge/jsocks/Socks5Message.java new file mode 100644 index 0000000..49539c4 --- /dev/null +++ b/src/net/sourceforge/jsocks/Socks5Message.java @@ -0,0 +1,292 @@ +package net.sourceforge.jsocks;
 +
 +import java.io.DataInputStream;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +import java.net.InetAddress;
 +import java.net.UnknownHostException;
 +
 +/**
 + SOCKS5 request/response message.
 +*/
 +
 +public class Socks5Message extends ProxyMessage{
 +   /** Address type of given message*/
 +   public int addrType;
 +
 +   byte[] data;
 +
 +   /**
 +    Server error response.
 +    @param cmd Error code.
 +   */
 +   public Socks5Message(int cmd){
 +      super(cmd,null,0);
 +      data = new byte[3];
 +      data[0] = SOCKS_VERSION; //Version.
 +      data[1] = (byte)cmd;     //Reply code for some kind of failure.
 +      data[2] = 0;             //Reserved byte.
 +   }
 +
 +   /**
 +    Construct client request or server response.
 +    @param cmd - Request/Response code.
 +    @param ip  - IP field.
 +    @paarm port - port field.
 +   */
 +   public Socks5Message(int cmd,InetAddress ip,int port){
 +      super(cmd,ip,port);
 +      this.host = ip==null?"0.0.0.0":ip.getHostName();
 +      this.version = SOCKS_VERSION;
 +
 +      byte[] addr;
 +
 +      if(ip == null){
 +         addr = new byte[4];
 +         addr[0]=addr[1]=addr[2]=addr[3]=0;
 +      }else
 +         addr = ip.getAddress();
 +
 +      addrType = addr.length == 4 ? SOCKS_ATYP_IPV4
 +                                  : SOCKS_ATYP_IPV6;
 + 
 +      data = new byte[6+addr.length];
 +      data[0] = (byte) SOCKS_VERSION;		//Version
 +      data[1] = (byte) command;			//Command
 +      data[2] = (byte) 0;			//Reserved byte
 +      data[3] = (byte) addrType;		//Address type
 + 
 +      //Put Address
 +      System.arraycopy(addr,0,data,4,addr.length);
 +      //Put port
 +      data[data.length-2] = (byte)(port>>8);
 +      data[data.length-1] = (byte)(port);
 +   }
 +
 +
 +   /**
 +    Construct client request or server response.
 +    @param cmd - Request/Response code.
 +    @param hostName  - IP field as hostName, uses ADDR_TYPE of HOSTNAME.
 +    @paarm port - port field.
 +   */
 +   public Socks5Message(int cmd,String hostName,int port){
 +      super(cmd,null,port);
 +      this.host = hostName;
 +      this.version = SOCKS_VERSION;
 +
 +      //System.out.println("Doing ATYP_DOMAINNAME");
 +
 +      addrType = SOCKS_ATYP_DOMAINNAME;
 +      byte addr[] = hostName.getBytes();
 + 
 +      data =new byte[7+addr.length];
 +      data[0] = (byte) SOCKS_VERSION;		//Version
 +      data[1] = (byte) command;			//Command
 +      data[2] = (byte) 0;			//Reserved byte
 +      data[3] = (byte) SOCKS_ATYP_DOMAINNAME;	//Address type
 +      data[4] = (byte) addr.length;		//Length of the address
 +
 +      //Put Address
 +      System.arraycopy(addr,0,data,5,addr.length);
 +      //Put port
 +      data[data.length-2] = (byte)(port >>8);
 +      data[data.length-1] = (byte)(port);
 +   }
 +
 +   /**
 +     Initialises Message from the stream. Reads server response from
 +     given stream.
 +     @param in Input stream to read response from.
 +     @throws SocksException If server response code is not SOCKS_SUCCESS(0), or
 +     if any error with protocol occurs.
 +     @throws IOException If any error happens with I/O.
 +   */
 +   public Socks5Message(InputStream in) throws SocksException,
 +                                              IOException{
 +      this(in,true);
 +   }
 +
 +   /**
 +     Initialises Message from the stream. Reads server response or client 
 +     request from given stream.
 +     
 +     @param in Input stream to read response from.
 +     @param clinetMode If true read server response, else read client request.
 +     @throws SocksException If server response code is not SOCKS_SUCCESS(0) and
 +     reading in client mode, or if any error with protocol occurs.
 +     @throws IOException If any error happens with I/O.
 +   */
 +   public Socks5Message(InputStream in,boolean clientMode)throws SocksException,
 +                                              IOException{
 +      read(in,clientMode);
 +   }
 +
 +
 +   /**
 +     Initialises Message from the stream. Reads server response from
 +     given stream.
 +     @param in Input stream to read response from.
 +     @throws SocksException If server response code is not SOCKS_SUCCESS(0), or
 +     if any error with protocol occurs.
 +     @throws IOException If any error happens with I/O.
 +   */
 +   public void read(InputStream in) throws SocksException,
 +                                           IOException{
 +       read(in,true);
 +   }
 +
 +
 +   /**
 +     Initialises Message from the stream. Reads server response or client 
 +     request from given stream.
 +     
 +     @param in Input stream to read response from.
 +     @param clinetMode If true read server response, else read client request.
 +     @throws SocksException If server response code is not SOCKS_SUCCESS(0) and
 +     reading in client mode, or if any error with protocol occurs.
 +     @throws IOException If any error happens with I/O.
 +   */
 +   public void read(InputStream in,boolean clientMode) throws SocksException,
 +                                           IOException{
 +      data = null;
 +      ip = null;
 +
 +      DataInputStream di = new DataInputStream(in);
 +
 +      version = di.readUnsignedByte();
 +      command = di.readUnsignedByte();
 +      if(clientMode && command != 0)
 +        throw new SocksException(command);
 +
 +      @SuppressWarnings("unused")
 +      int reserved = di.readUnsignedByte();
 +      addrType = di.readUnsignedByte();
 +
 +      byte addr[];
 +
 +      switch(addrType){
 +         case SOCKS_ATYP_IPV4:
 +            addr = new byte[4];
 +            di.readFully(addr);
 +            host = bytes2IPV4(addr,0);
 +         break;
 +         case SOCKS_ATYP_IPV6:
 +           addr = new byte[SOCKS_IPV6_LENGTH];//I believe it is 16 bytes,huge!
 +           di.readFully(addr);
 +           host = bytes2IPV6(addr,0);
 +         break;
 +         case SOCKS_ATYP_DOMAINNAME:
 +           //System.out.println("Reading ATYP_DOMAINNAME");
 +           addr = new byte[di.readUnsignedByte()];//Next byte shows the length
 +           di.readFully(addr);
 +           host = new String(addr);
 +         break;
 +         default:
 +            throw(new SocksException(Proxy.SOCKS_JUST_ERROR));
 +      }
 +
 +      port = di.readUnsignedShort();
 +
 +      if(addrType != SOCKS_ATYP_DOMAINNAME && doResolveIP){
 +         try{
 +            ip = InetAddress.getByName(host);
 +         }catch(UnknownHostException uh_ex){
 +         }
 +      }
 +   }
 +
 +   /**
 +    Writes the message to the stream.
 +    @param out Output stream to which message should be written.
 +   */
 +   public void write(OutputStream out)throws SocksException,
 +                                             IOException{
 +     if(data == null){
 +       Socks5Message msg;
 +
 +       if(addrType == SOCKS_ATYP_DOMAINNAME)
 +          msg = new Socks5Message(command,host,port);
 +       else{
 +          if(ip == null){
 +             try{
 +               ip = InetAddress.getByName(host);
 +             }catch(UnknownHostException uh_ex){
 +               throw new SocksException(Proxy.SOCKS_JUST_ERROR);
 +             }
 +          }
 +          msg = new Socks5Message(command,ip,port);
 +       }
 +       data = msg.data;
 +     }
 +     out.write(data);
 +   }
 +
 +   /**
 +    Returns IP field of the message as IP, if the message was created
 +    with ATYP of HOSTNAME, it will attempt to resolve the hostname,
 +    which might fail.
 +    @throws UnknownHostException if host can't be resolved.
 +   */
 +   public InetAddress getInetAddress() throws UnknownHostException{
 +     if(ip!=null) return ip;
 +
 +     return (ip=InetAddress.getByName(host));
 +   }
 +
 +   /**
 +     Returns string representation of the message.
 +   */
 +   public String toString(){
 +      String s=
 +        "Socks5Message:"+"\n"+
 +        "VN   "+version+"\n"+
 +        "CMD  "+command+"\n"+
 +        "ATYP "+addrType+"\n"+
 +        "ADDR "+host+"\n"+
 +        "PORT "+port+"\n";
 +      return s;
 +   }
 +            
 +
 +   /**
 +    *Wether to resolve hostIP returned from SOCKS server
 +    *that is wether to create InetAddress object from the
 +    *hostName string
 +    */
 +   static public boolean resolveIP(){ return doResolveIP;}
 +
 +   /**
 +    *Wether to resolve hostIP returned from SOCKS server
 +    *that is wether to create InetAddress object from the
 +    *hostName string
 +    *@param doResolve Wether to resolve hostIP from SOCKS server.
 +    *@return Previous value.
 +    */
 +   static public boolean resolveIP(boolean doResolve){
 +      boolean old = doResolveIP;
 +      doResolveIP = doResolve;
 +      return old;
 +   }
 +
 +   /*
 +   private static final void debug(String s){
 +      if(DEBUG)
 +         System.out.print(s);
 +   }
 +   private static final boolean DEBUG = false;
 +   */
 +
 +   //SOCKS5 constants
 +   public static final int SOCKS_VERSION		=5;
 +
 +   public static final int SOCKS_ATYP_IPV4		=0x1; //Where is 2??
 +   public static final int SOCKS_ATYP_DOMAINNAME	=0x3; //!!!!rfc1928
 +   public static final int SOCKS_ATYP_IPV6		=0x4;
 +
 +   public static final int SOCKS_IPV6_LENGTH		=16;
 +
 +   static boolean doResolveIP = true;
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/Socks5Proxy.java b/src/net/sourceforge/jsocks/Socks5Proxy.java new file mode 100644 index 0000000..8b1d946 --- /dev/null +++ b/src/net/sourceforge/jsocks/Socks5Proxy.java @@ -0,0 +1,231 @@ +package net.sourceforge.jsocks;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +import java.net.InetAddress;
 +import java.net.Socket;
 +import java.net.SocketException;
 +import java.net.UnknownHostException;
 +import java.util.Enumeration;
 +import java.util.Hashtable;
 +
 +/**
 + SOCKS5 Proxy.
 +*/
 +
 +public class Socks5Proxy extends Proxy implements Cloneable{
 +
 +//Data members
 +   private Hashtable<Integer, Authentication> authMethods = new Hashtable<Integer, Authentication>();
 +   private int selectedMethod;
 +
 +   boolean resolveAddrLocally = true;
 +   UDPEncapsulation udp_encapsulation=null;
 +
 +
 +//Public Constructors
 +//====================
 +
 +   /**
 +     Creates SOCKS5 proxy.
 +     @param proxyHost Host on which a Proxy server runs.
 +     @param proxyPort Port on which a Proxy server listens for connections.
 +     @throws UnknownHostException If proxyHost can't be resolved.
 +   */
 +   public Socks5Proxy(String proxyHost,int proxyPort)
 +          throws UnknownHostException{ 
 +      super(proxyHost,proxyPort);
 +      version = 5;
 +      setAuthenticationMethod(0,new AuthenticationNone());
 +   }
 +
 +
 +   /**
 +     Creates SOCKS5 proxy.
 +     @param proxyIP Host on which a Proxy server runs.
 +     @param proxyPort Port on which a Proxy server listens for connections.
 +   */
 +   public Socks5Proxy(InetAddress proxyIP,int proxyPort){
 +      super(proxyIP,proxyPort);
 +      version = 5;
 +      setAuthenticationMethod(0,new AuthenticationNone());
 +   }
 +
 +
 +//Public instance methods
 +//========================
 +
 +
 +   /**
 +    * Wether to resolve address locally or to let proxy do so.
 +      <p>
 +      SOCKS5 protocol allows to send host names rather then IPs in the
 +      requests, this option controls wether the hostnames should be send
 +      to the proxy server as names, or should they be resolved locally.
 +      @param doResolve Wether to perform resolution locally.
 +      @return Previous settings.
 +    */
 +   public boolean resolveAddrLocally(boolean doResolve){
 +      boolean old = resolveAddrLocally;
 +      resolveAddrLocally = doResolve;
 +      return old;
 +   }
 +   /**
 +    Get current setting on how the addresses should be handled.
 +    @return Current setting for address resolution.
 +    @see Socks5Proxy#resolveAddrLocally(boolean doResolve)
 +   */
 +   public boolean resolveAddrLocally(){
 +      return resolveAddrLocally;
 +   }
 +
 +   /**
 +     Adds another authentication method.
 +     @param methodId Authentication method id, see rfc1928
 +     @param method Implementation of Authentication
 +     @see Authentication
 +   */
 +   public boolean setAuthenticationMethod(int methodId,
 +                                          Authentication method){
 +      if(methodId<0 || methodId > 255)
 +          return false;
 +      if(method == null){
 +        //Want to remove a particular method
 +	return (authMethods.remove(new Integer(methodId)) != null);
 +      }else{//Add the method, or rewrite old one
 +	authMethods.put(new Integer(methodId),method);
 +      }
 +      return true;
 +   }
 +   
 +   /**
 +    Get authentication method, which corresponds to given method id
 +    @param methodId Authentication method id.
 +    @return Implementation for given method or null, if one was not set.
 +   */
 +   public Authentication getAuthenticationMethod(int methodId){
 +      Object method = authMethods.get(new Integer(methodId));
 +      if(method == null) return null;
 +      return (Authentication)method;
 +   }
 +
 +   /**
 +    Creates a clone of this Proxy.
 +   */
 +   @SuppressWarnings("unchecked")
 +public Object clone(){
 +      Socks5Proxy newProxy = new Socks5Proxy(proxyIP,proxyPort);
 +      newProxy.authMethods = (Hashtable<Integer, Authentication>) this.authMethods.clone();
 +      newProxy.resolveAddrLocally = resolveAddrLocally;
 +      newProxy.chainProxy = chainProxy;
 +      return newProxy;
 +   }
 +
 +//Public Static(Class) Methods
 +//==============================
 +
 +
 +//Protected Methods
 +//=================
 +
 +   protected Proxy copy(){
 +       Socks5Proxy copy = new Socks5Proxy(proxyIP,proxyPort);
 +       copy.authMethods = this.authMethods; //same Hash, no copy
 +       copy.chainProxy = this.chainProxy;
 +       copy.resolveAddrLocally = this.resolveAddrLocally;
 +       return copy;
 +    }
 +   /**
 +    *
 +    *
 +    */
 +   protected void startSession()throws SocksException{
 +      super.startSession();
 +      Authentication auth;
 +      Socket ps = proxySocket; //The name is too long
 +
 +      try{
 +
 +	 byte nMethods = (byte) authMethods.size();  //Number of methods
 +
 +         byte[] buf = new byte[2+nMethods]; //2 is for VER,NMETHODS
 +         buf[0] = (byte) version;
 +         buf[1] = nMethods;                 //Number of methods 
 +         int i=2;
 +
 +         Enumeration<Integer> ids = authMethods.keys();
 +         while(ids.hasMoreElements())
 +            buf[i++] = (byte)((Integer)ids.nextElement()).intValue();
 +
 +         out.write(buf);
 +         out.flush();
 +
 +         int versionNumber  = in.read();
 +         selectedMethod = in.read();
 +
 +         if(versionNumber < 0 || selectedMethod < 0){
 +           //EOF condition was reached
 +           endSession();
 +           throw(new SocksException(SOCKS_PROXY_IO_ERROR,
 +                 "Connection to proxy lost."));
 +         }
 +         if(versionNumber < version){
 +           //What should we do??
 +         }
 +         if(selectedMethod == 0xFF){ //No method selected
 +            ps.close();
 +            throw ( new SocksException(SOCKS_AUTH_NOT_SUPPORTED));
 +         }
 +
 +         auth = getAuthenticationMethod(selectedMethod);
 +         if(auth == null){
 +            //This shouldn't happen, unless method was removed by other
 +            //thread, or the server stuffed up
 +            throw(new SocksException(SOCKS_JUST_ERROR,
 +                      "Speciefied Authentication not found!"));
 +         }
 +         Object[] in_out = auth.doSocksAuthentication(selectedMethod,ps);
 +         if(in_out == null){
 +            //Authentication failed by some reason
 +            throw(new SocksException(SOCKS_AUTH_FAILURE));
 +         }
 +         //Most authentication methods are expected to return
 +         //simply the input/output streams associated with
 +         //the socket. However if the auth. method requires
 +         //some kind of encryption/decryption being done on the
 +         //connection it should provide classes to handle I/O.
 +
 +         in = (InputStream) in_out[0];
 +         out = (OutputStream) in_out[1];
 +         if(in_out.length > 2) 
 +            udp_encapsulation = (UDPEncapsulation) in_out[2];
 +
 +      }catch(SocksException s_ex){
 +         throw s_ex;
 +      }catch(UnknownHostException uh_ex){
 +         throw(new SocksException(SOCKS_PROXY_NO_CONNECT));
 +      }catch(SocketException so_ex){
 +         throw(new SocksException(SOCKS_PROXY_NO_CONNECT));
 +      }catch(IOException io_ex){
 +         //System.err.println(io_ex);
 +         throw(new SocksException(SOCKS_PROXY_IO_ERROR,""+io_ex));
 +      }
 +   }
 +
 +   protected ProxyMessage formMessage(int cmd,InetAddress ip,int port){
 +       return new Socks5Message(cmd,ip,port);
 +   }
 +   protected ProxyMessage formMessage(int cmd,String host,int port)
 +             throws UnknownHostException{
 +       if(resolveAddrLocally)
 +          return formMessage(cmd,InetAddress.getByName(host),port);
 +       else
 +          return new Socks5Message(cmd,host,port);
 +   }
 +   protected ProxyMessage formMessage(InputStream in)
 +             throws SocksException,
 +                    IOException{
 +       return new Socks5Message(in);
 +   }
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/SocksException.java b/src/net/sourceforge/jsocks/SocksException.java new file mode 100644 index 0000000..6c22faa --- /dev/null +++ b/src/net/sourceforge/jsocks/SocksException.java @@ -0,0 +1,80 @@ +package net.sourceforge.jsocks;
 +
 +/**
 + Exception thrown by various socks classes to indicate errors
 + with protocol or unsuccessful server responses.
 +*/
 +public class SocksException extends java.io.IOException{
 +	private static final long serialVersionUID = 6141184566248512277L;
 +
 +   /**
 +    Construct a SocksException with given error code.
 +    <p>
 +    Tries to look up message which corresponds to this error code.
 +    @param errCode Error code for this exception.
 +   */
 +   public SocksException(int errCode){
 +       this.errCode = errCode;
 +       if((errCode >> 16) == 0){
 +          //Server reply error message
 +          errString = errCode <= serverReplyMessage.length ?
 +                      serverReplyMessage[errCode] :
 +                      UNASSIGNED_ERROR_MESSAGE;
 +       }else{
 +          //Local error
 +          errCode = (errCode >> 16) -1;
 +          errString = errCode <= localErrorMessage.length ?
 +                      localErrorMessage[errCode] :
 +                      UNASSIGNED_ERROR_MESSAGE;
 +       }
 +   }
 +   /**
 +    Constructs a SocksException with given error code and message.
 +    @param errCode  Error code.
 +    @param errString Error Message.
 +   */
 +   public SocksException(int errCode,String errString){
 +       this.errCode = errCode;
 +       this.errString = errString;
 +   }
 +   /**
 +    Get the error code associated with this exception.
 +    @return Error code associated with this exception.
 +   */
 +   public int getErrorCode(){
 +      return errCode;
 +   }
 +   /**
 +    Get human readable representation of this exception.
 +    @return String represntation of this exception.
 +   */
 +   public String toString(){
 +      return errString;
 +   }
 +
 +   static final String UNASSIGNED_ERROR_MESSAGE =
 +                  "Unknown error message";
 +   static final String serverReplyMessage[] = { 
 +                  "Succeeded", 
 +                  "General SOCKS server failure",
 +                  "Connection not allowed by ruleset",
 +                  "Network unreachable",
 +                  "Host unreachable",
 +                  "Connection refused",
 +                  "TTL expired",
 +                  "Command not supported",
 +                  "Address type not supported" };
 +
 +   static final String localErrorMessage[] ={
 +                  "SOCKS server not specified",
 +                  "Unable to contact SOCKS server",
 +                  "IO error",
 +                  "None of Authentication methods are supported",
 +                  "Authentication failed",
 +                  "General SOCKS fault" };
 +
 +   String errString;
 +   int errCode;
 +
 +}//End of SocksException class
 +
 diff --git a/src/net/sourceforge/jsocks/SocksServerSocket.java b/src/net/sourceforge/jsocks/SocksServerSocket.java new file mode 100644 index 0000000..3095b28 --- /dev/null +++ b/src/net/sourceforge/jsocks/SocksServerSocket.java @@ -0,0 +1,179 @@ +package net.sourceforge.jsocks;
 +
 +import java.net.*;
 +import java.io.*;
 +
 +/**
 +   SocksServerSocket allows to accept connections from one particular
 +   host through the SOCKS4 or SOCKS5 proxy.
 +*/
 +public class SocksServerSocket extends ServerSocket{
 +   //Data members
 +   protected Proxy proxy;
 +   protected String localHost;
 +   protected InetAddress localIP;
 +   protected int localPort;
 +
 +   boolean doing_direct = false;
 +   InetAddress  remoteAddr;
 +
 +   /**
 +    *Creates ServerSocket capable of accepting one connection
 +    *through the firewall, uses given proxy.
 +    *@param host Host from which the connection should be recieved.
 +    *@param port Port number of the primary connection.
 +    */
 +   public SocksServerSocket(String host, int port) throws SocksException,
 +			UnknownHostException, IOException {
 +
 +		super(0);
 +		remoteAddr = InetAddress.getByName(host);
 +		doDirect();
 +	}
 +
 +   /**
 +    * Creates ServerSocket capable of accepting one connection
 +    * through the firewall, uses default Proxy.
 +    *@param ip Host from which the connection should be recieved.
 +    *@param port Port number of the primary connection.
 +    */
 +   public SocksServerSocket(InetAddress ip, int port) throws SocksException,
 +                                                             IOException{
 +      this(Proxy.defaultProxy,ip,port);
 +   }
 +
 +   /**
 +    *Creates ServerSocket capable of accepting one connection
 +    *through the firewall, uses given proxy.
 +    *@param ip   Host from which the connection should be recieved.
 +    *@param port Port number of the primary connection.
 +    */
 +    public SocksServerSocket(Proxy p, InetAddress ip, int port)
 +			throws SocksException, IOException {
 +		super(0);
 +
 +		remoteAddr = ip;
 +		doDirect();
 +	}
 +
 +
 +   /**
 +    * Accepts the incoming connection.
 +    */
 +   public Socket accept() throws IOException{
 +      Socket s;
 +
 +      if(!doing_direct){
 +         if(proxy == null) return null;
 +
 +         ProxyMessage msg = proxy.accept();
 +         s = msg.ip == null? new SocksSocket(msg.host,msg.port,proxy)
 +                                  : new SocksSocket(msg.ip,msg.port,proxy);
 +         //Set timeout back to 0
 +         proxy.proxySocket.setSoTimeout(0);
 +      }else{ //Direct Connection
 +
 +          //Mimic the proxy behaviour,
 +          //only accept connections from the speciefed host.
 +          while(true){
 +            s = super.accept();
 +            if(s.getInetAddress().equals(remoteAddr)){
 +               //got the connection from the right host
 +               //Close listenning socket.
 +               break;
 +            }else
 +               s.close(); //Drop all connections from other hosts
 +          }
 +
 +      }
 +      proxy = null;
 +      //Return accepted socket
 +      return s;
 +   }
 +
 +   /**
 +    * Closes the connection to proxy if socket have not been accepted, if
 +    * the direct connection is used, closes direct ServerSocket. If the 
 +    * client socket have been allready accepted, does nothing.
 +    */
 +   public void close() throws IOException{
 +      super.close();
 +      if(proxy != null) proxy.endSession();
 +      proxy = null;
 +   }
 +
 +   /**
 +     Get the name of the host proxy is using to listen for incoming
 +     connection.
 +     <P>
 +     Usefull when address is returned by proxy as the hostname.
 +     @return the hostname of the address proxy is using to listen
 +     for incoming connection.
 +    */
 +   public String getHost(){
 +      return localHost;
 +   }
 +
 +   /**
 +    * Get address assigned by proxy to listen for incomming
 +    * connections, or the local machine address if doing direct
 +    * connection.
 +    */
 +   public InetAddress getInetAddress(){
 +      if(localIP == null){
 +	 try{
 +	   localIP = InetAddress.getByName(localHost);
 +	 }catch(UnknownHostException e){
 +	   return null;
 +	 }
 +      }
 +      return localIP;
 +   }
 +
 +   /**
 +    *  Get port assigned by proxy to listen for incoming connections, or
 +       the port chosen by local system, if accepting directly.
 +    */
 +   public int getLocalPort(){
 +      return localPort;
 +   }
 +
 +   /**
 +    Set Timeout.
 +
 +    @param timeout Amount of time in milliseconds, accept should wait for
 +                   incoming connection before failing with exception.
 +                   Zero timeout implies infinity.
 +   */
 +   public void setSoTimeout(int timeout) throws SocketException{
 +      super.setSoTimeout(timeout);
 +      if(!doing_direct) proxy.proxySocket.setSoTimeout(timeout);
 +   }
 +
 +
 +//Private Methods
 +//////////////////
 +
 +   private void processReply(ProxyMessage reply)throws SocksException{
 +      localPort = reply.port;
 +      /*
 +       * If the server have assigned same host as it was contacted on
 +       * it might return an address of all zeros
 +       */
 +      if(reply.host.equals("0.0.0.0")){
 +         localIP = proxy.proxyIP;
 +         localHost = localIP.getHostName();
 +      }else{
 +         localHost = reply.host;
 +         localIP = reply.ip;
 +      }
 +   }
 +
 +   private void doDirect(){
 +      doing_direct = true;
 +      localPort = super.getLocalPort();
 +      localIP = super.getInetAddress();
 +      localHost = localIP.getHostName();
 +   }
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/SocksSocket.java b/src/net/sourceforge/jsocks/SocksSocket.java new file mode 100644 index 0000000..5263bb6 --- /dev/null +++ b/src/net/sourceforge/jsocks/SocksSocket.java @@ -0,0 +1,305 @@ +package net.sourceforge.jsocks;
 +
 +import java.net.*;
 +import java.io.*;
 +
 +/**
 + * SocksSocket tryies to look very similar to normal Socket,
 + * while allowing connections through the SOCKS4 or 5 proxy.
 + * To use this class you will have to identify proxy you need
 + * to use, Proxy class allows you to set default proxy, which
 + * will be used by all Socks aware sockets. You can also create
 + * either Socks4Proxy or Socks5Proxy, and use them by passing to the 
 + * appropriate constructors.
 + * <P>
 + * Using Socks package can be as easy as that:
 + *
 + * <pre><tt>
 + *
 + *     import Socks.*;
 + *     ....
 + *
 + *     try{
 + *        //Specify SOCKS5 proxy
 + *        Proxy.setDefaultProxy("socks-proxy",1080);
 + *
 + *        //OR you still use SOCKS4
 + *        //Code below uses SOCKS4 proxy
 + *        //Proxy.setDefaultProxy("socks-proxy",1080,userName);
 + *
 + *        Socket s = SocksSocket("some.host.of.mine",13);
 + *        readTimeFromSock(s);
 + *     }catch(SocksException sock_ex){
 + *        //Usually it will turn in more or less meaningfull message
 + *        System.err.println("SocksException:"+sock_ex);
 + *     }
 + *
 + * </tt></pre>
 + *<P>
 + * However if the need exist for more control, like resolving addresses
 + * remotely, or using some non-trivial authentication schemes, it can be done.
 + */
 +
 +public class SocksSocket extends Socket{
 +   //Data members
 +   protected Proxy proxy;
 +   protected String localHost, remoteHost;
 +   protected InetAddress localIP, remoteIP;
 +   protected int localPort,remotePort;
 +
 +   private Socket directSock = null;
 +
 +   /**
 +    * Tryies to connect to given host and port
 +    * using default proxy. If no default proxy speciefied
 +    * it throws SocksException with error code SOCKS_NO_PROXY.
 +      @param host Machine to connect to.
 +      @param port Port to which to connect.
 +    * @see SocksSocket#SocksSocket(Proxy,String,int)
 +    * @see Socks5Proxy#resolveAddrLocally
 +    */
 +   public SocksSocket(String host,int port)
 +	  throws SocksException,UnknownHostException{
 +      this(Proxy.defaultProxy,host,port);
 +   }
 +   /**
 +    * Connects to host port using given proxy server.
 +      @param p Proxy to use.
 +      @param host Machine to connect to.
 +      @param port Port to which to connect.
 +      @throws UnknownHostException 
 +      If one of the following happens:
 +      <ol>
 +
 +      <li> Proxy settings say that address should be resolved locally, but
 +           this fails.
 +      <li> Proxy settings say that the host should be contacted directly but
 +           host name can't be resolved. 
 +      </ol>
 +      @throws SocksException
 +      If one of the following happens:
 +      <ul>
 +       <li> Proxy is is null.
 +       <li> Proxy settings say that the host should be contacted directly but
 +            this fails.
 +       <li> Socks Server can't be contacted.
 +       <li> Authentication fails.
 +       <li> Connection is not allowed by the SOCKS proxy.
 +       <li> SOCKS proxy can't establish the connection.
 +       <li> Any IO error occured.
 +       <li> Any protocol error occured.
 +      </ul>
 +      @throws IOexception if anything is wrong with I/O.
 +      @see Socks5Proxy#resolveAddrLocally
 +    */
 +    public SocksSocket(Proxy p, String host, int port) throws SocksException,
 +			UnknownHostException {
 +		remoteHost = host;
 +		remotePort = port;
 +		remoteIP = InetAddress.getByName(host);
 +		doDirect();
 +	}
 +
 +   /**
 +      Connects to given ip and port using given Proxy server.
 +      @param p Proxy to use.
 +      @param ip Machine to connect to.
 +      @param port Port to which to connect.
 +
 +    */
 +   public SocksSocket(InetAddress ip, int port) throws SocksException{
 +      this.remoteIP = ip;
 +      this.remotePort = port;
 +      this.remoteHost = ip.getHostName();
 +      doDirect();
 +   }
 +
 +
 +   /**
 +    * These 2 constructors are used by the SocksServerSocket.
 +    * This socket simply overrides remoteHost, remotePort
 +    */
 +   protected SocksSocket(String  host,int port,Proxy proxy){
 +      this.remotePort = port;
 +      this.proxy = proxy;
 +      this.localIP = proxy.proxySocket.getLocalAddress();
 +      this.localPort = proxy.proxySocket.getLocalPort();
 +      this.remoteHost = host;
 +   }
 +   protected SocksSocket(InetAddress ip,int port,Proxy proxy){
 +      remoteIP = ip;
 +      remotePort = port;
 +      this.proxy = proxy;
 +      this.localIP = proxy.proxySocket.getLocalAddress();
 +      this.localPort = proxy.proxySocket.getLocalPort();
 +      remoteHost = remoteIP.getHostName();
 +   }
 +
 +   /**
 +    * Same as Socket
 +    */
 +   public void close() throws IOException{
 +      if(proxy!= null)proxy.endSession();
 +      proxy = null;
 +   }
 +   /**
 +    * Same as Socket
 +    */
 +   public InputStream getInputStream(){
 +      return proxy.in;
 +   }
 +   /**
 +    * Same as Socket
 +    */
 +   public OutputStream getOutputStream(){
 +      return proxy.out;
 +   }
 +   /**
 +    * Same as Socket
 +    */
 +   public int getPort(){
 +      return remotePort;
 +   }
 +   /**
 +    * Returns remote host name, it is usefull in cases when addresses
 +    * are resolved by proxy, and we can't create InetAddress object.
 +      @return The name of the host this socket is connected to.
 +    */
 +   public String getHost(){
 +      return remoteHost;
 +   }
 +   /**
 +    * Get remote host as InetAddress object, might return null if 
 +    * addresses are resolved by proxy, and it is not possible to resolve
 +    * it locally
 +      @return Ip address of the host this socket is connected to, or null
 +      if address was returned by the proxy as DOMAINNAME and can't be
 +      resolved locally.
 +    */
 +   public InetAddress getInetAddress(){
 +      if(remoteIP == null){
 +	 try{
 +	   remoteIP = InetAddress.getByName(remoteHost);
 +	 }catch(UnknownHostException e){
 +	   return null;
 +	 }
 +      }
 +      return remoteIP;
 +   }
 +
 +   /**
 +    * Get the port assigned by the proxy for the socket, not
 +    * the port on locall machine as in Socket. 
 +      @return Port of the socket used on the proxy server.
 +    */
 +   public int getLocalPort(){
 +      return localPort;
 +   }
 +
 +   /**
 +    * Get address assigned by proxy to make a remote connection,
 +    * it might be different from the host specified for the proxy.
 +    * Can return null if socks server returned this address as hostname
 +    * and it can't be resolved locally, use getLocalHost() then.
 +      @return Address proxy is using to make a connection.
 +    */
 +   public InetAddress getLocalAddress(){
 +      if(localIP == null){
 +	 try{
 +	    localIP = InetAddress.getByName(localHost);
 +	 }catch(UnknownHostException e){
 +	   return null;
 +	 }
 +      }
 +      return localIP;
 +   }
 +   /**
 +      Get name of the host, proxy has assigned to make a remote connection
 +      for this socket. This method is usefull when proxy have returned
 +      address as hostname, and we can't resolve it on this machine.
 +      @return The name of the host proxy is using to make a connection.
 +   */
 +   public String getLocalHost(){
 +      return localHost;
 +   }
 +
 +   /**
 +     Same as socket.
 +   */
 +   public void setSoLinger(boolean on,int val) throws SocketException{
 +      proxy.proxySocket.setSoLinger(on,val);
 +   }
 +   /**
 +     Same as socket.
 +   */
 +   public int getSoLinger(int timeout) throws SocketException{
 +      return proxy.proxySocket.getSoLinger();
 +   }
 +   /**
 +     Same as socket.
 +   */
 +   public void setSoTimeout(int timeout) throws SocketException{
 +      proxy.proxySocket.setSoTimeout(timeout);
 +   }
 +   /**
 +     Same as socket.
 +   */
 +   public int getSoTimeout(int timeout) throws SocketException{
 +      return proxy.proxySocket.getSoTimeout();
 +   }
 +   /**
 +     Same as socket.
 +   */
 +   public void setTcpNoDelay(boolean on) throws SocketException{
 +     proxy.proxySocket.setTcpNoDelay(on);
 +   }
 +   /**
 +     Same as socket.
 +   */
 +   public boolean getTcpNoDelay() throws SocketException{
 +     return proxy.proxySocket.getTcpNoDelay();
 +   }
 +
 +   /**
 +     Get string representation of the socket.
 +   */
 +   public String toString(){
 +      if(directSock!=null) return "Direct connection:"+directSock;
 +      return ("Proxy:"+proxy+";"+"addr:"+remoteHost+",port:"+remotePort
 +                                +",localport:"+localPort);
 +
 +   }
 +
 +//Private Methods
 +//////////////////
 +
 +   private void processReply(ProxyMessage reply)throws SocksException{
 +      localPort = reply.port;
 +      /*
 +       * If the server have assigned same host as it was contacted on
 +       * it might return an address of all zeros
 +       */
 +      if(reply.host.equals("0.0.0.0")){
 +         localIP = proxy.proxyIP;
 +         localHost = localIP.getHostName();
 +      }else{
 +         localHost = reply.host;
 +         localIP = reply.ip;
 +      }
 +   }
 +   private void doDirect()throws SocksException{
 +      try{
 +         //System.out.println("IP:"+remoteIP+":"+remotePort);
 +         directSock = new Socket(remoteIP,remotePort);
 +         proxy.out = directSock.getOutputStream();
 +         proxy.in  = directSock.getInputStream();
 +         proxy.proxySocket = directSock;
 +         localIP = directSock.getLocalAddress();
 +         localPort = directSock.getLocalPort();
 +      }catch(IOException io_ex){
 +         throw new SocksException(Proxy.SOCKS_DIRECT_FAILED,
 +                                  "Direct connect failed:"+io_ex);
 +      }
 +   }
 +
 +}
 diff --git a/src/net/sourceforge/jsocks/UDPEncapsulation.java b/src/net/sourceforge/jsocks/UDPEncapsulation.java new file mode 100644 index 0000000..efeb0ed --- /dev/null +++ b/src/net/sourceforge/jsocks/UDPEncapsulation.java @@ -0,0 +1,29 @@ +package net.sourceforge.jsocks;
 +/**
 + This interface provides for datagram encapsulation for SOCKSv5 protocol.
 + <p>
 + SOCKSv5 allows for datagrams to be encapsulated for purposes of integrity
 + and/or authenticity. How it should be done is aggreed during the 
 + authentication stage, and is authentication dependent. This interface is
 + provided to allow this encapsulation.
 + @see Authentication
 +*/
 +public interface UDPEncapsulation{
 +
 +    /**
 +    This method should provide any authentication depended transformation
 +    on datagrams being send from/to the client.
 +
 +    @param data Datagram data (including any SOCKS related bytes), to be
 +                encapsulated/decapsulated.
 +    @param out  Wether the data is being send out. If true method should 
 +                encapsulate/encrypt data, otherwise it should decapsulate/
 +                decrypt data.
 +    @throw IOException if for some reason data can be transformed correctly.
 +    @return Should return byte array containing data after transformation.
 +            It is possible to return same array as input, if transformation
 +            only involves bit mangling, and no additional data is being
 +            added or removed.
 +    */
 +    byte[] udpEncapsulate(byte[] data, boolean out) throws java.io.IOException;
 +}
 diff --git a/src/net/sourceforge/jsocks/UDPRelayServer.java b/src/net/sourceforge/jsocks/UDPRelayServer.java new file mode 100644 index 0000000..682ab17 --- /dev/null +++ b/src/net/sourceforge/jsocks/UDPRelayServer.java @@ -0,0 +1,212 @@ +package net.sourceforge.jsocks;
 +import net.sourceforge.jsocks.server.*;
 +import java.net.*;
 +import java.io.*;
 +
 +/**
 + UDP Relay server, used by ProxyServer to perform udp forwarding.
 +*/
 +class UDPRelayServer implements Runnable{
 +
 +
 +    DatagramSocket client_sock; 
 +    DatagramSocket remote_sock;
 +
 +    Socket controlConnection;
 +
 +    int relayPort;
 +    InetAddress relayIP;
 +
 +    Thread pipe_thread1,pipe_thread2;
 +    Thread master_thread;
 +
 +    ServerAuthenticator auth;
 +
 +    long lastReadTime;
 +
 +    static PrintStream log = null;
 +    static Proxy proxy = null;
 +    static int datagramSize = 0xFFFF;//64K, a bit more than max udp size
 +    static int iddleTimeout = 180000;//3 minutes
 +
 +
 +    /**
 +      Constructs UDP relay server to communicate with client
 +      on given ip and port.
 +      @param clientIP Address of the client from whom datagrams
 +      will be recieved and to whom they will be forwarded.
 +      @param clientPort Clients port.
 +      @param master_thread Thread which will be interrupted, when
 +      UDP relay server stoppes for some reason.
 +      @param controlConnection Socket which will be closed, before
 +      interrupting the master thread, it is introduced due to a bug
 +      in windows JVM which does not throw InterruptedIOException in
 +      threads which block in I/O operation.
 +    */
 +    public UDPRelayServer(InetAddress clientIP,int clientPort,
 +                          Thread master_thread,
 +                          Socket controlConnection,
 +                          ServerAuthenticator auth)
 +                          throws IOException{
 +       this.master_thread = master_thread;
 +       this.controlConnection = controlConnection;
 +       this.auth = auth;
 +
 +       client_sock = new Socks5DatagramSocket(true,auth.getUdpEncapsulation(),
 +                                              clientIP,clientPort);
 +       relayPort = client_sock.getLocalPort();
 +       relayIP   = client_sock.getLocalAddress();
 +
 +       if(relayIP.getHostAddress().equals("0.0.0.0"))
 +         relayIP   = InetAddress.getLocalHost();
 +
 +       if(proxy == null)
 +          remote_sock = new DatagramSocket();
 +       else
 +          remote_sock = new Socks5DatagramSocket(proxy,0,null);
 +    }
 +
 +
 +//Public methods
 +/////////////////
 +
 +
 +   /**
 +    Sets the timeout for UDPRelay server.<br>
 +    Zero timeout implies infinity.<br>
 +    Default timeout is 3 minutes.
 +    */
 +
 +    static public void setTimeout(int timeout){
 +      iddleTimeout = timeout;
 +    }
 +
 +
 +   /**
 +     Sets the size of the datagrams used in the UDPRelayServer.<br>
 +     Default size is 64K, a bit more than maximum possible size of the
 +     datagram.
 +    */
 +    static public void setDatagramSize(int size){
 +      datagramSize = size;
 +    }
 +
 +    /**
 +      Port to which client should send datagram for association.
 +    */
 +    public int getRelayPort(){
 +       return relayPort;
 +    }
 +    /**
 +     IP address to which client should send datagrams for association.
 +    */
 +    public InetAddress getRelayIP(){
 +       return relayIP;
 +    }
 +
 +    /**
 +      Starts udp relay server.
 +      Spawns two threads of execution and returns.
 +    */
 +    public void start() throws IOException{
 +       remote_sock.setSoTimeout(iddleTimeout);
 +       client_sock.setSoTimeout(iddleTimeout);
 +
 +       log("Starting UDP relay server on "+relayIP+":"+relayPort);
 +       log("Remote socket "+remote_sock.getLocalAddress()+":"+
 +                            remote_sock.getLocalPort());
 +
 +       pipe_thread1 = new Thread(this,"pipe1");
 +       pipe_thread2 = new Thread(this,"pipe2");
 +
 +       lastReadTime = System.currentTimeMillis();
 +
 +       pipe_thread1.start();
 +       pipe_thread2.start();
 +    }
 +
 +    /**
 +     Stops Relay server.
 +     <p>
 +     Does not close control connection, does not interrupt master_thread.
 +    */
 +    public synchronized void stop(){
 +       master_thread = null;
 +       controlConnection = null;
 +       abort();
 +    }
 +
 +//Runnable interface
 +////////////////////
 +    public void run(){
 +       try{
 +          if(Thread.currentThread().getName().equals("pipe1"))
 +             pipe(remote_sock,client_sock,false);
 +          else
 +             pipe(client_sock,remote_sock,true);
 +       }catch(IOException ioe){
 +       }finally{
 +          abort();
 +          log("UDP Pipe thread "+Thread.currentThread().getName()+" stopped.");
 +       }
 +
 +    }
 +
 +//Private methods
 +/////////////////
 +    private synchronized void abort(){
 +       if(pipe_thread1 == null) return;
 +
 +       log("Aborting UDP Relay Server");
 +
 +       remote_sock.close();
 +       client_sock.close();
 +
 +       if(controlConnection != null) 
 +          try{ controlConnection.close();} catch(IOException ioe){}
 +
 +       if(master_thread!=null) master_thread.interrupt();
 +
 +       pipe_thread1.interrupt();
 +       pipe_thread2.interrupt();
 +
 +       pipe_thread1 = null;
 +    }
 +
 +
 +    static private void log(String s){
 +      if(log != null){
 +        log.println(s);
 +        log.flush();
 +      }
 +    }
 +
 +    private void pipe(DatagramSocket from,DatagramSocket to,boolean out)
 +                             throws IOException{
 +       byte[] data = new byte[datagramSize];
 +       DatagramPacket dp = new DatagramPacket(data,data.length);
 +
 +       while(true){
 +          try{
 +            from.receive(dp);
 +            lastReadTime = System.currentTimeMillis();
 +
 +            if(auth.checkRequest(dp,out))
 +               to.send(dp);
 +
 +          }catch(UnknownHostException uhe){
 +            log("Dropping datagram for unknown host");
 +          }catch(InterruptedIOException iioe){
 +            //log("Interrupted: "+iioe);
 +            //If we were interrupted by other thread.
 +            if(iddleTimeout == 0) return;
 +
 +            //If last datagram was received, long time ago, return.
 +            long timeSinceRead = System.currentTimeMillis() - lastReadTime;
 +            if(timeSinceRead >= iddleTimeout -100) //-100 for adjustment
 +               return;
 +          }
 +          dp.setLength(data.length);
 +       }
 +    }
 +}
 diff --git a/src/net/sourceforge/jsocks/server/ServerAuthenticator.java b/src/net/sourceforge/jsocks/server/ServerAuthenticator.java new file mode 100644 index 0000000..6fb99e0 --- /dev/null +++ b/src/net/sourceforge/jsocks/server/ServerAuthenticator.java @@ -0,0 +1,120 @@ +package net.sourceforge.jsocks.server;
 +
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +import java.net.DatagramPacket;
 +import java.net.Socket;
 +
 +import net.sourceforge.jsocks.ProxyMessage;
 +import net.sourceforge.jsocks.UDPEncapsulation;
 +
 +/**
 + Classes implementing this interface should provide socks server with
 + authentication and authorization of users.
 +**/
 +public interface ServerAuthenticator{
 +
 +   /**
 +     This method is called when a new connection accepted by the server.
 +     <p>
 +     At this point no data have been extracted from the connection. It is
 +     responsibility of this method to ensure that the next byte in the
 +     stream after this method have been called is the first byte of the
 +     socks request message. For SOCKSv4 there is no authentication data and
 +     the first byte in the stream is part of the request. With SOCKSv5 however
 +     there is an authentication data first. It is expected that implementaions
 +     will process this authentication data. 
 +     <p>
 +     If authentication was successful an instance of ServerAuthentication
 +     should be returned, it later will be used by the server to perform
 +     authorization and some other things. If authentication fails null should
 +     be returned, or an exception may be thrown.
 +
 +     @param s Accepted Socket.
 +     @return An instance of ServerAuthenticator to be used for this connection
 +     or null
 +   */
 +   ServerAuthenticator startSession(Socket s) throws IOException;
 +
 +   /**
 +    This method should return input stream which should be used on the
 +    accepted socket.
 +    <p>
 +    SOCKSv5 allows to have multiple authentication methods, and these methods
 +    might require some kind of transformations being made on the data.
 +    <p>
 +    This method is called on the object returned from the startSession
 +    function.
 +   */
 +   InputStream getInputStream();
 +   /**
 +    This method should return output stream to use to write to the accepted
 +    socket.
 +    <p>
 +    SOCKSv5 allows to have multiple authentication methods, and these methods
 +    might require some kind of transformations being made on the data.
 +    <p>
 +    This method is called on the object returned from the startSession
 +    function.
 +   */
 +   OutputStream getOutputStream();
 +
 +   /**
 +    This method should return UDPEncapsulation, which should be used
 +    on the datagrams being send in/out.
 +    <p>
 +    If no transformation should be done on the datagrams, this method
 +    should return null.
 +    <p>
 +    This method is called on the object returned from the startSession
 +    function.
 +   */
 +
 +   UDPEncapsulation getUdpEncapsulation();
 +
 +   /**
 +    This method is called when a request have been read.
 +    <p>
 +    Implementation should decide wether to grant request or not. Returning
 +    true implies granting the request, false means request should be rejected.
 +    <p>
 +    This method is called on the object returned from the startSession
 +    function.
 +    @param msg Request message.
 +    @return true to grant request, false to reject it.
 +   */
 +   boolean checkRequest(ProxyMessage msg);
 +
 +   /**
 +    This method is called when datagram is received by the server.
 +    <p>
 +    Implementaions should decide wether it should be forwarded or dropped.
 +    It is expecteed that implementation will use datagram address and port
 +    information to make a decision, as well as anything else. Address and
 +    port of the datagram are always correspond to remote machine. It is
 +    either destination or source address. If out is true address is destination
 +    address, else it is a source address, address of the machine from which
 +    datagram have been received for the client.
 +    <p>
 +    Implementaions should return true if the datagram is to be forwarded, and
 +    false if the datagram should be dropped.
 +    <p>
 +    This method is called on the object returned from the startSession
 +    function.
 +
 +    @param out If true the datagram is being send out(from the client),
 +               otherwise it is an incoming datagram.
 +    @return True to forward datagram false drop it silently.
 +   */
 +   boolean checkRequest(DatagramPacket dp, boolean out);
 +
 +   /**
 +    This method is called when session is completed. Either due to normal
 +    termination or due to any error condition.
 +    <p>
 +    This method is called on the object returned from the startSession
 +    function.
 +   */
 +   void endSession();
 +}
 diff --git a/src/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java b/src/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java new file mode 100644 index 0000000..30eb265 --- /dev/null +++ b/src/net/sourceforge/jsocks/server/ServerAuthenticatorNone.java @@ -0,0 +1,169 @@ +package net.sourceforge.jsocks.server;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +import java.io.PushbackInputStream;
 +import java.net.Socket;
 +
 +import net.sourceforge.jsocks.ProxyMessage;
 +import net.sourceforge.jsocks.UDPEncapsulation;
 +
 +/**
 + An implementation of ServerAuthenticator, which does <b>not</b> do
 + any authentication.
 +<P>
 +<FONT size="+3" color ="FF0000"> Warning!!</font><br> Should not be
 +used on machines which are not behind the firewall.
 +<p>
 +It is only provided to make implementing other authentication schemes
 +easier.<br>
 +For Example: <tt><pre>
 +   class MyAuth extends socks.server.ServerAuthenticator{
 +    ...
 +    public ServerAuthenticator startSession(java.net.Socket s){
 +      if(!checkHost(s.getInetAddress()) return null;
 +      return super.startSession(s);
 +    }
 +
 +    boolean checkHost(java.net.Inetaddress addr){
 +      boolean allow;
 +      //Do it somehow
 +      return allow;
 +    }
 +   }
 +</pre></tt>
 +*/
 +public class ServerAuthenticatorNone implements ServerAuthenticator{
 +
 +   static final byte[] socks5response = {5,0};
 +
 +   InputStream in;
 +   OutputStream out;
 +
 +   /**
 +    Creates new instance of the ServerAuthenticatorNone.
 +   */
 +   public ServerAuthenticatorNone(){
 +     this.in = null;
 +     this.out = null;
 +   }
 +   /**
 +    Constructs new ServerAuthenticatorNone object suitable for returning
 +    from the startSession function.
 +    @param in Input stream to return from getInputStream method.
 +    @param out Output stream to return from getOutputStream method.
 +   */
 +   public ServerAuthenticatorNone(InputStream in, OutputStream out){
 +      this.in = in;
 +      this.out = out;
 +   }
 +   /**
 +    Grants access to everyone.Removes authentication related bytes from
 +    the stream, when a SOCKS5 connection is being made, selects an
 +    authentication NONE.
 +    */
 +   public ServerAuthenticator startSession(Socket s)
 +                                  throws IOException{
 +
 +     PushbackInputStream in =  new PushbackInputStream(s.getInputStream());
 +     OutputStream out = s.getOutputStream();
 +
 +     int version = in.read();
 +     if(version == 5){
 +       if(!selectSocks5Authentication(in,out,0))
 +          return null;
 +     }else if(version == 4){
 +       //Else it is the request message allready, version 4
 +       in.unread(version);
 +     }else
 +       return null;
 +     
 +
 +     return new ServerAuthenticatorNone(in,out);
 +   }
 +
 +   /**
 +     Get input stream.
 +     @return Input stream speciefied in the constructor.
 +   */
 +   public InputStream getInputStream(){
 +      return in;
 +   }
 +   /**
 +     Get output stream.
 +     @return Output stream speciefied in the constructor.
 +   */
 +   public OutputStream getOutputStream(){
 +      return out;
 +   }
 +   /**
 +     Allways returns null.
 +     @return null
 +   */
 +   public UDPEncapsulation getUdpEncapsulation(){
 +      return null;
 +   }
 +
 +   /**
 +    Allways returns true.
 +   */
 +   public boolean checkRequest(ProxyMessage msg){
 +     return true;
 +   }
 +
 +   /**
 +    Allways returns true.
 +   */
 +   public boolean checkRequest(java.net.DatagramPacket dp, boolean out){
 +     return true;
 +   }
 +
 +   /**
 +    Does nothing.
 +    */
 +   public void endSession(){
 +   }
 +
 +   /**
 +     Convinience routine for selecting SOCKSv5 authentication.
 +     <p>
 +     This method reads in authentication methods that client supports,
 +     checks wether it supports given method. If it does, the notification
 +     method is written back to client, that this method have been chosen
 +     for authentication. If given method was not found, authentication
 +     failure message is send to client ([5,FF]).
 +     @param in Input stream, version byte should be removed from the stream
 +               before calling this method.
 +     @param out Output stream.
 +     @param methodId Method which should be selected.
 +     @return true if methodId was found, false otherwise.
 +   */
 +   static public boolean selectSocks5Authentication(InputStream in, 
 +                                                    OutputStream out,
 +                                                    int methodId)
 +                                                    throws IOException{
 +                                                    
 +      int num_methods = in.read();
 +      if (num_methods <= 0) return false;
 +      byte method_ids[] = new byte[num_methods];
 +      byte response[] = new byte[2];
 +      boolean found = false;
 +
 +      response[0] = (byte) 5;    //SOCKS version
 +      response[1] = (byte) 0xFF; //Not found, we are pessimistic
 +
 +      int bread = 0; //bytes read so far
 +      while(bread < num_methods)
 +         bread += in.read(method_ids,bread,num_methods-bread);
 +
 +      for(int i=0;i<num_methods;++i)
 +         if(method_ids[i] == methodId){
 +           found = true;
 +           response[1] = (byte) methodId;
 +           break;
 +         }
 +
 +      out.write(response);
 +      return found;
 +   }
 +}
 diff --git a/src/org/connectbot/PortForwardListActivity.java b/src/org/connectbot/PortForwardListActivity.java index cd9ca97..777dba4 100644 --- a/src/org/connectbot/PortForwardListActivity.java +++ b/src/org/connectbot/PortForwardListActivity.java @@ -155,8 +155,13 @@ public class PortForwardListActivity extends ListActivity {  								final EditText sourcePortEdit = (EditText) portForwardView.findViewById(R.id.portforward_source);  								final EditText destEdit = (EditText) portForwardView.findViewById(R.id.portforward_destination); -								String type = ((RadioButton)portForwardView.findViewById(R.id.portforward_local)).isChecked() -									? HostDatabase.PORTFORWARD_LOCAL : HostDatabase.PORTFORWARD_REMOTE; +								String type; +								if (((RadioButton)portForwardView.findViewById(R.id.portforward_local)).isChecked()) +									type = HostDatabase.PORTFORWARD_LOCAL; +								else if (((RadioButton)portForwardView.findViewById(R.id.portforward_remote)).isChecked()) +									type = HostDatabase.PORTFORWARD_REMOTE; +								else +									type = HostDatabase.PORTFORWARD_DYNAMIC5;  								PortForwardBean pfb = new PortForwardBean(hostId,  										nicknameEdit.getText().toString(), type, @@ -201,10 +206,14 @@ public class PortForwardListActivity extends ListActivity {  				final View editTunnelView = inflater.inflate(R.layout.dia_portforward, null, false);  				final RadioButton portForwardLocal = (RadioButton) editTunnelView.findViewById(R.id.portforward_local); +				final RadioButton portForwardRemote = (RadioButton) editTunnelView.findViewById(R.id.portforward_remote); +  				if (HostDatabase.PORTFORWARD_LOCAL.equals(pfb.getType())) {  					portForwardLocal.setChecked(true); -				} else { // if (HostDatabase.PORTFORWARD_REMOTE.equals(type)) { -					((RadioButton) editTunnelView.findViewById(R.id.portforward_remote)).setChecked(true); +				} else if (HostDatabase.PORTFORWARD_REMOTE.equals(pfb.getType())) { +					portForwardRemote.setChecked(true); +				} else { +					((RadioButton) editTunnelView.findViewById(R.id.portforward_dynamic)).setChecked(true);  				}  				final EditText nicknameEdit = (EditText) editTunnelView.findViewById(R.id.nickname); @@ -225,8 +234,10 @@ public class PortForwardListActivity extends ListActivity {  								if (portForwardLocal.isChecked())  									pfb.setType(HostDatabase.PORTFORWARD_LOCAL); -								else +								else if (portForwardRemote.isChecked())  									pfb.setType(HostDatabase.PORTFORWARD_REMOTE); +								else +									pfb.setType(HostDatabase.PORTFORWARD_DYNAMIC5);  								pfb.setSourcePort(Integer.parseInt(sourcePortEdit.getText().toString()));  								pfb.setDest(destEdit.getText().toString()); diff --git a/src/org/connectbot/bean/PortForwardBean.java b/src/org/connectbot/bean/PortForwardBean.java index f5fd7ac..91ebc74 100644 --- a/src/org/connectbot/bean/PortForwardBean.java +++ b/src/org/connectbot/bean/PortForwardBean.java @@ -139,7 +139,8 @@ public class PortForwardBean {  	public void setDest(String dest) {  		String[] destSplit = dest.split(":");  		this.destAddr = destSplit[0]; -		this.destPort = Integer.parseInt(destSplit[1]); +		if (destSplit.length > 1) +			this.destPort = Integer.parseInt(destSplit[1]);  	}  	/** diff --git a/src/org/connectbot/service/TerminalBridge.java b/src/org/connectbot/service/TerminalBridge.java index 22411fa..89cd911 100644 --- a/src/org/connectbot/service/TerminalBridge.java +++ b/src/org/connectbot/service/TerminalBridge.java @@ -52,6 +52,7 @@ import android.view.View.OnKeyListener;  import com.trilead.ssh2.Connection;  import com.trilead.ssh2.ConnectionMonitor; +import com.trilead.ssh2.DynamicPortForwarder;  import com.trilead.ssh2.InteractiveCallback;  import com.trilead.ssh2.KnownHosts;  import com.trilead.ssh2.LocalPortForwarder; @@ -1060,6 +1061,19 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal  			portForward.setEnabled(false);  			return true; +		} else if (HostDatabase.PORTFORWARD_DYNAMIC5.equals(portForward.getType())) { +			DynamicPortForwarder dpf = null; +			 +			try { +				dpf = this.connection.createDynamicPortForwarder(portForward.getSourcePort()); +			} catch (IOException e) { +				Log.e(TAG, "Could not create dynamic port forward", e); +				return false; +			} +			 +			portForward.setIdentifier(dpf); +			portForward.setEnabled(true); +			return true;  		} else {  			// Unsupported type  			Log.e(TAG, String.format("attempt to forward unknown type %s", portForward.getType())); @@ -1102,6 +1116,23 @@ public class TerminalBridge implements VDUDisplay, OnKeyListener, InteractiveCal  			}  			return true; +		} else if (portForward.getType() == HostDatabase.PORTFORWARD_DYNAMIC5) { +			DynamicPortForwarder dpf = null; +			dpf = (DynamicPortForwarder)portForward.getIdentifier(); +			 +			if (!portForward.isEnabled() || dpf == null) +				return false; +			 +			portForward.setEnabled(false); +			 +			try { +				dpf.close(); +			} catch (IOException e) { +				Log.e(TAG, "Could not stop dynamic port forwarder, setting enabled to false", e); +				return false; +			} +			 +			return true;  		} else {  			return false;  		}  | 
